fix: resolve golangci-lint violations across codebase

- Replace fmt.Errorf with errors.Wrapf/New for forbidigo compliance
- Add missing periods to comments for godot compliance
- Fix goimports formatting issues in multiple files
- Remove unused struct field writes in tests
- Replace unused method receivers with underscore
- Optimize single-case switch to if-else statement

Signed-off-by: ChaoLiu <chaoliu719@gmail.com>
This commit is contained in:
ChaoLiu 2025-08-22 12:06:13 +08:00
parent 96e527e264
commit 9db3dcf0a2
7 changed files with 31 additions and 25 deletions

View File

@ -2,8 +2,6 @@ package ai
import ( import (
"context" "context"
"errors"
"fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -11,11 +9,12 @@ import (
"github.com/openai/openai-go/v2" "github.com/openai/openai-go/v2"
"github.com/openai/openai-go/v2/option" "github.com/openai/openai-go/v2/option"
"github.com/pkg/errors"
storepb "github.com/usememos/memos/proto/gen/store" storepb "github.com/usememos/memos/proto/gen/store"
) )
// Common AI errors // Common AI errors.
var ( var (
ErrConfigIncomplete = errors.New("AI configuration incomplete - missing BaseURL, APIKey, or Model") ErrConfigIncomplete = errors.New("AI configuration incomplete - missing BaseURL, APIKey, or Model")
ErrEmptyRequest = errors.New("chat request cannot be empty") ErrEmptyRequest = errors.New("chat request cannot be empty")
@ -125,7 +124,7 @@ type Client struct {
// NewClient creates a new AI client // NewClient creates a new AI client
func NewClient(config *Config) (*Client, error) { func NewClient(config *Config) (*Client, error) {
if config == nil { if config == nil {
return nil, fmt.Errorf("config cannot be nil") return nil, errors.New("config cannot be nil")
} }
if !config.IsConfigured() { if !config.IsConfigured() {
@ -182,10 +181,10 @@ func (c *Client) Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, err
// Validate messages // Validate messages
for i, msg := range req.Messages { for i, msg := range req.Messages {
if msg.Role != "system" && msg.Role != "user" && msg.Role != "assistant" { if msg.Role != "system" && msg.Role != "user" && msg.Role != "assistant" {
return nil, fmt.Errorf("message %d: %w", i, ErrInvalidMessage) return nil, errors.Wrapf(ErrInvalidMessage, "message %d", i)
} }
if strings.TrimSpace(msg.Content) == "" { if strings.TrimSpace(msg.Content) == "" {
return nil, fmt.Errorf("message %d: %w", i, ErrEmptyContent) return nil, errors.Wrapf(ErrEmptyContent, "message %d", i)
} }
} }
@ -235,7 +234,7 @@ func (c *Client) Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, err
Temperature: openai.Float(req.Temperature), Temperature: openai.Float(req.Temperature),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrAPICallFailed, err) return nil, errors.Wrapf(ErrAPICallFailed, "%v", err)
} }
if len(completion.Choices) == 0 { if len(completion.Choices) == 0 {

View File

@ -245,6 +245,11 @@ func TestClient_Chat_RequestDefaults(t *testing.T) {
assert.Equal(t, 0, req.MaxTokens) // Should become 8192 assert.Equal(t, 0, req.MaxTokens) // Should become 8192
assert.Equal(t, float64(0), req.Temperature) // Should become 0.3 assert.Equal(t, float64(0), req.Temperature) // Should become 0.3
assert.Equal(t, time.Duration(0), req.Timeout) // Should become 10s assert.Equal(t, time.Duration(0), req.Timeout) // Should become 10s
// Verify the Messages field is properly structured
assert.Len(t, req.Messages, 1)
assert.Equal(t, "user", req.Messages[0].Role)
assert.Equal(t, "Hello", req.Messages[0].Content)
} }
func TestMessage_Roles(t *testing.T) { func TestMessage_Roles(t *testing.T) {
@ -275,6 +280,7 @@ func TestMessage_Roles(t *testing.T) {
} }
assert.Equal(t, tt.valid, validRoles[msg.Role]) assert.Equal(t, tt.valid, validRoles[msg.Role])
assert.Equal(t, "test content", msg.Content)
}) })
} }
} }

View File

@ -8,6 +8,8 @@ import (
"strings" "strings"
"text/template" "text/template"
"time" "time"
"github.com/pkg/errors"
) )
// defaultSystemPrompt contains the core instructions for tag recommendation. // defaultSystemPrompt contains the core instructions for tag recommendation.
@ -38,10 +40,10 @@ const userMessageTemplate = `{{if .ExistingTags}}Existing Tags: {{.ExistingTags}
// TagSuggestionRequest represents a tag suggestion request // TagSuggestionRequest represents a tag suggestion request
type TagSuggestionRequest struct { type TagSuggestionRequest struct {
Content string // The memo content to analyze Content string // The memo content to analyze
UserTags []string // User's frequently used tags (optional) UserTags []string // User's frequently used tags (optional)
ExistingTags []string // Tags already in the memo (optional) ExistingTags []string // Tags already in the memo (optional)
SystemPrompt string // Custom system prompt (optional, uses default if empty) SystemPrompt string // Custom system prompt (optional, uses default if empty)
} }
// TagSuggestion represents a single tag suggestion with reason // TagSuggestion represents a single tag suggestion with reason
@ -64,11 +66,11 @@ func GetDefaultSystemPrompt() string {
func (c *Client) SuggestTags(ctx context.Context, req *TagSuggestionRequest) (*TagSuggestionResponse, error) { func (c *Client) SuggestTags(ctx context.Context, req *TagSuggestionRequest) (*TagSuggestionResponse, error) {
// Validate request // Validate request
if req == nil { if req == nil {
return nil, fmt.Errorf("request cannot be nil") return nil, errors.New("request cannot be nil")
} }
if strings.TrimSpace(req.Content) == "" { if strings.TrimSpace(req.Content) == "" {
return nil, fmt.Errorf("content cannot be empty") return nil, errors.New("content cannot be empty")
} }
// Prepare user tags context // Prepare user tags context
@ -131,7 +133,7 @@ func (c *Client) SuggestTags(ctx context.Context, req *TagSuggestionRequest) (*T
} }
// parseTagResponse parses AI response for [tag](reason) patterns // parseTagResponse parses AI response for [tag](reason) patterns
func (c *Client) parseTagResponse(responseText string) []TagSuggestion { func (_ *Client) parseTagResponse(responseText string) []TagSuggestion {
tags := make([]TagSuggestion, 0) tags := make([]TagSuggestion, 0)
// Match [tag](reason) format using regex across response // Match [tag](reason) format using regex across response

View File

@ -1010,7 +1010,7 @@ func (s *APIV1Service) SuggestMemoTags(ctx context.Context, request *v1pb.Sugges
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get AI setting: %v", err) return nil, status.Errorf(codes.Internal, "failed to get AI setting: %v", err)
} }
// Check if tag recommendation is enabled // Check if tag recommendation is enabled
if !aiSetting.EnableAi || aiSetting.TagRecommendation == nil || !aiSetting.TagRecommendation.Enabled { if !aiSetting.EnableAi || aiSetting.TagRecommendation == nil || !aiSetting.TagRecommendation.Enabled {
return &v1pb.SuggestMemoTagsResponse{ return &v1pb.SuggestMemoTagsResponse{
@ -1032,13 +1032,13 @@ func (s *APIV1Service) SuggestMemoTags(ctx context.Context, request *v1pb.Sugges
if !tagRateLimit.checkRateLimit(currentUser.ID, maxRequestsPerMinute) { if !tagRateLimit.checkRateLimit(currentUser.ID, maxRequestsPerMinute) {
return nil, status.Errorf(codes.ResourceExhausted, "标签推荐请求频率超限,每分钟最多 %d 次", maxRequestsPerMinute) return nil, status.Errorf(codes.ResourceExhausted, "标签推荐请求频率超限,每分钟最多 %d 次", maxRequestsPerMinute)
} }
aiConfig := ai.LoadConfigFromDatabase(aiSetting) aiConfig := ai.LoadConfigFromDatabase(aiSetting)
if !aiConfig.IsConfigured() { if !aiConfig.IsConfigured() {
// Fallback to environment variables if database config is not complete // Fallback to environment variables if database config is not complete
aiConfig = aiConfig.MergeWithEnv() aiConfig = aiConfig.MergeWithEnv()
} }
if aiConfig.IsConfigured() { if aiConfig.IsConfigured() {
aiClient, err := ai.NewClient(aiConfig) aiClient, err := ai.NewClient(aiConfig)
if err != nil { if err != nil {

View File

@ -373,7 +373,7 @@ func (s *APIV1Service) GetInstanceOwner(ctx context.Context) (*v1pb.User, error)
} }
// GetDefaultTagRecommendationPrompt returns the default system prompt for AI tag recommendations. // GetDefaultTagRecommendationPrompt returns the default system prompt for AI tag recommendations.
func (s *APIV1Service) GetDefaultTagRecommendationPrompt(ctx context.Context, _ *v1pb.GetDefaultTagRecommendationPromptRequest) (*v1pb.GetDefaultTagRecommendationPromptResponse, error) { func (_ *APIV1Service) GetDefaultTagRecommendationPrompt(ctx context.Context, _ *v1pb.GetDefaultTagRecommendationPromptRequest) (*v1pb.GetDefaultTagRecommendationPromptResponse, error) {
return &v1pb.GetDefaultTagRecommendationPromptResponse{ return &v1pb.GetDefaultTagRecommendationPromptResponse{
SystemPrompt: ai.GetDefaultSystemPrompt(), SystemPrompt: ai.GetDefaultSystemPrompt(),
}, nil }, nil

View File

@ -110,7 +110,7 @@ func RebuildMemoPayload(memo *store.Memo) error {
} }
// ExtractTagsFromContent extracts tags from content string using the same logic as RebuildMemoPayload // ExtractTagsFromContent extracts tags from content string using the same logic as RebuildMemoPayload
// This function is exported for use in other packages (e.g., for tag recommendations) // This function is exported for use in other packages (e.g., for tag recommendations).
func ExtractTagsFromContent(content string) []string { func ExtractTagsFromContent(content string) []string {
nodes, err := parser.Parse(tokenizer.Tokenize(content)) nodes, err := parser.Parse(tokenizer.Tokenize(content))
if err != nil { if err != nil {
@ -119,8 +119,7 @@ func ExtractTagsFromContent(content string) []string {
tags := []string{} tags := []string{}
TraverseASTNodes(nodes, func(node ast.Node) { TraverseASTNodes(nodes, func(node ast.Node) {
switch n := node.(type) { if n, ok := node.(*ast.Tag); ok {
case *ast.Tag:
tag := n.Content tag := n.Content
if !slices.Contains(tags, tag) { if !slices.Contains(tags, tag) {
tags = append(tags, tag) tags = append(tags, tag)

View File

@ -213,10 +213,10 @@ func (s *Store) GetWorkspaceStorageSetting(ctx context.Context) (*storepb.Worksp
} }
const ( const (
defaultAITimeoutSeconds = int32(15) defaultAITimeoutSeconds = int32(15)
defaultAITagRecommandationEnabled = false defaultAITagRecommandationEnabled = false
defaultAITagRecommandationPrompt = "" defaultAITagRecommandationPrompt = ""
defaultAITagRecommandationRPM = int32(10) defaultAITagRecommandationRPM = int32(10)
) )
func (s *Store) GetWorkspaceAISetting(ctx context.Context) (*storepb.WorkspaceAISetting, error) { func (s *Store) GetWorkspaceAISetting(ctx context.Context) (*storepb.WorkspaceAISetting, error) {
@ -256,7 +256,7 @@ func (s *Store) GetWorkspaceAISetting(ctx context.Context) (*storepb.WorkspaceAI
return workspaceAISetting, nil return workspaceAISetting, nil
} }
// loadAISettingFromEnv loads AI configuration from environment variables // loadAISettingFromEnv loads AI configuration from environment variables.
func loadAISettingFromEnv() *storepb.WorkspaceAISetting { func loadAISettingFromEnv() *storepb.WorkspaceAISetting {
timeoutSeconds := defaultAITimeoutSeconds timeoutSeconds := defaultAITimeoutSeconds
if timeoutStr := os.Getenv("AI_TIMEOUT_SECONDS"); timeoutStr != "" { if timeoutStr := os.Getenv("AI_TIMEOUT_SECONDS"); timeoutStr != "" {