mirror of https://github.com/usememos/memos.git
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:
parent
96e527e264
commit
9db3dcf0a2
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 != "" {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue