package handler import ( "fmt" "log" "net/http" "strings" "time" "github.com/gin-gonic/gin" "infogenie-backend/config" "infogenie-backend/internal/service" ) type AIModelHandler struct{} func NewAIModelHandler() *AIModelHandler { return &AIModelHandler{} } // 输入长度限制 const ( maxInputLen = 5000 maxChatMsgCount = 20 ) // 允许用户选择的模型白名单 var allowedModels = map[string]map[string]bool{ "deepseek": { "deepseek-chat": true, "deepseek-reasoner": true, }, "kimi": { "kimi-k2-0905-preview": true, "kimi-k2-0711-preview": true, }, } func safeAIError(err error) string { log.Printf("AI调用失败: %v", err) return "AI 服务暂时不可用,请稍后重试" } func validateTextLen(text string, label string) (string, error) { t := strings.TrimSpace(text) if t == "" { return "", fmt.Errorf("%s不能为空", label) } if len(t) > maxInputLen { return "", fmt.Errorf("%s超出长度限制(最大 %d 字符)", label, maxInputLen) } return t, nil } // POST /api/aimodelapp/chat func (h *AIModelHandler) Chat(c *gin.Context) { var req struct { Messages []service.ChatMessage `json:"messages"` Provider string `json:"provider"` Model string `json:"model"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求数据为空"}) return } if req.Provider == "" { req.Provider = "deepseek" } if req.Model == "" { req.Model = "deepseek-chat" } // 模型白名单校验 if models, ok := allowedModels[req.Provider]; !ok || !models[req.Model] { c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的模型"}) return } if len(req.Messages) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "消息内容不能为空"}) return } if len(req.Messages) > maxChatMsgCount { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("消息数量不能超过 %d 条", maxChatMsgCount)}) return } // 校验每条消息的长度 for _, m := range req.Messages { if len(m.Content) > maxInputLen { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("单条消息长度不能超过 %d 字符", maxInputLen)}) return } } content, err := service.CallAI(req.Provider, req.Model, req.Messages) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "content": content, "provider": req.Provider, "model": req.Model, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/name-analysis func (h *AIModelHandler) NameAnalysis(c *gin.Context) { var req struct { Name string `json:"name"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "姓名不能为空"}) return } name, err := validateTextLen(req.Name, "姓名") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } prompt := fmt.Sprintf(`你是一位专业的姓名学专家和语言学家,请对输入的姓名进行全面分析。请直接输出分析结果,不要包含任何思考过程或标签。 姓名:%s 请按照以下格式严格输出分析结果: 【稀有度评分】 评分:X%% 评价:[对稀有度的详细说明,包括姓氏和名字的常见程度分析] 【音韵评价】 评分:X%% 评价:[对音韵美感的分析,包括声调搭配、读音流畅度、音律和谐度等] 【含义解读】 [详细分析姓名的寓意内涵,包括: 1. 姓氏的历史渊源和文化背景 2. 名字各字的含义和象征 3. 整体姓名的寓意组合 4. 可能体现的父母期望或文化内涵 5. 与传统文化、诗词典故的关联等] 要求: 1. 评分必须是1-100的整数百分比,要有明显区分度,避免雷同 2. 分析要专业、客观、有依据,评分要根据实际情况有所差异 3. 含义解读要详细深入,至少150字 4. 严格按照上述格式输出,不要添加思考过程、标签或其他内容 5. 如果是生僻字或罕见姓名,要特别说明 6. 直接输出最终结果,不要显示推理过程`, name) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "analysis": content, "name": name, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/variable-naming func (h *AIModelHandler) VariableNaming(c *gin.Context) { var req struct { Description string `json:"description"` Language string `json:"language"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "变量描述不能为空"}) return } desc, err := validateTextLen(req.Description, "变量描述") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } lang := req.Language if lang == "" { lang = "javascript" } prompt := fmt.Sprintf(`你是一个专业的变量命名助手。请根据以下描述为变量生成合适的名称: 描述:%s 请为每种命名规范生成3个变量名建议: 1. camelCase (驼峰命名法) 2. PascalCase (帕斯卡命名法) 3. snake_case (下划线命名法) 4. kebab-case (短横线命名法) 5. CONSTANT_CASE (常量命名法) 请按JSON格式返回: {"suggestions":{"camelCase":[{"name":"变量名","description":"说明"}],"PascalCase":[{"name":"变量名","description":"说明"}],"snake_case":[{"name":"变量名","description":"说明"}],"kebab-case":[{"name":"变量名","description":"说明"}],"CONSTANT_CASE":[{"name":"变量名","description":"说明"}]}} 只返回JSON格式的结果,不要包含其他文字。`, desc) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "suggestions": extractOrRaw(content), "description": desc, "language": lang, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/poetry func (h *AIModelHandler) Poetry(c *gin.Context) { var req struct { Theme string `json:"theme"` Style string `json:"style"` Mood string `json:"mood"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "诗歌主题不能为空"}) return } theme, err := validateTextLen(req.Theme, "诗歌主题") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } style := req.Style if style == "" { style = "现代诗" } mood := req.Mood if mood == "" { mood = "自由发挥" } prompt := fmt.Sprintf(`你是一位才华横溢的诗人,请根据以下要求创作一首诗歌。 主题:%s 风格:%s 情感基调:%s 创作要求: 1. 紧扣主题,情感真挚 2. 语言优美,意境深远 3. 符合指定的诗歌风格 4. 长度适中,朗朗上口 5. 如果是古体诗,注意平仄和韵律 请直接输出诗歌作品,不需要额外的解释或分析。`, theme, style, mood) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "poem": content, "theme": theme, "style": style, "mood": mood, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/translation func (h *AIModelHandler) Translation(c *gin.Context) { var req struct { SourceText string `json:"source_text"` TargetLanguage string `json:"target_language"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "翻译内容不能为空"}) return } text, err := validateTextLen(req.SourceText, "翻译内容") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } targetLang := req.TargetLanguage if targetLang == "" { targetLang = "zh-CN" } langMap := map[string]string{ "zh-CN": "中文(简体)", "zh-TW": "中文(繁体)", "en": "英语", "ja": "日语", "ko": "韩语", "fr": "法语", "de": "德语", "es": "西班牙语", "it": "意大利语", "pt": "葡萄牙语", "ru": "俄语", "ar": "阿拉伯语", "hi": "印地语", "th": "泰语", "vi": "越南语", } langName := langMap[targetLang] if langName == "" { langName = targetLang } prompt := fmt.Sprintf(`你是一位专业的翻译专家,精通多种语言的翻译工作。请将以下文本翻译成%s。 原文:%s 翻译要求: 1. 【信】- 忠实原文,准确传达原意 2. 【达】- 译文通顺流畅,符合目标语言的表达习惯 3. 【雅】- 用词优美得体,风格与原文相符 请按以下JSON格式返回翻译结果: {"detected_language":"检测到的源语言","target_language":"%s","translation":"翻译结果","alternative_translations":["备选翻译1","备选翻译2"],"explanation":"翻译说明","pronunciation":"发音指导"} 只返回JSON格式的结果,不要包含其他文字。`, langName, text, langName) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "translation_result": content, "source_text": text, "target_language": targetLang, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/classical_conversion func (h *AIModelHandler) ClassicalConversion(c *gin.Context) { var req struct { ModernText string `json:"modern_text"` Style string `json:"style"` ArticleType string `json:"article_type"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "现代文内容不能为空"}) return } text, err := validateTextLen(req.ModernText, "现代文内容") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } style := req.Style if style == "" { style = "古雅" } artType := req.ArticleType if artType == "" { artType = "散文" } prompt := fmt.Sprintf(`你是一位精通古代文言文的文学大师,擅长将现代文转换为优美的文言文。请将以下现代文转换为文言文。 现代文:%s 风格:%s 文体:%s 请按以下JSON格式返回转换结果: {"classical_text":"转换后的文言文","translation_notes":"转换说明","style_analysis":"风格分析","difficulty_level":"难度等级","key_phrases":[{"modern":"现代词汇","classical":"文言文词汇","explanation":"转换说明"}],"cultural_elements":"文化内涵说明"} 只返回JSON格式的结果,不要包含其他文字。`, text, style, artType) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "conversion_result": content, "modern_text": text, "style": style, "article_type": artType, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/expression-maker func (h *AIModelHandler) ExpressionMaker(c *gin.Context) { var req struct { Text string `json:"text"` Style string `json:"style"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "文字内容不能为空"}) return } text, err := validateTextLen(req.Text, "文字内容") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } style := req.Style if style == "" { style = "mixed" } styleMap := map[string]string{ "mixed": "混合使用Emoji表情和颜文字", "emoji": "仅使用Emoji表情符号", "kaomoji": "仅使用颜文字", "cute": "使用可爱风格的表情符号", "cool": "使用酷炫风格的表情符号", } styleDesc := styleMap[style] if styleDesc == "" { styleDesc = styleMap["mixed"] } prompt := fmt.Sprintf(`你是一个专业的表情符号专家。请根据以下文字内容生成相应的表情符号: 文字内容:%s 表情风格:%s 请按JSON格式返回: {"expressions":{"emoji":[{"symbol":"😊","description":"场景说明","intensity":"中等","usage":"使用建议"}],"kaomoji":[{"symbol":"(^_^)","description":"场景说明","intensity":"轻微","usage":"使用建议"}],"combination":[{"symbol":"🎉✨","description":"场景说明","intensity":"强烈","usage":"使用建议"}]},"summary":{"emotion_analysis":"情感分析","recommended_usage":"推荐使用场景","style_notes":"风格特点"}} 只返回JSON格式的结果,不要包含其他文字。`, text, styleDesc) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "expressions": extractOrRaw(content), "text": text, "style": style, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/linux-command func (h *AIModelHandler) LinuxCommand(c *gin.Context) { var req struct { TaskDescription string `json:"task_description"` DifficultyLevel string `json:"difficulty_level"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "任务描述不能为空"}) return } desc, err := validateTextLen(req.TaskDescription, "任务描述") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } level := req.DifficultyLevel if level == "" { level = "beginner" } prompt := fmt.Sprintf(`你是一位Linux系统专家,请根据用户的任务描述生成相应的Linux命令。 任务描述:%s 用户水平:%s 请按JSON格式返回: {"commands":[{"command":"具体命令","description":"说明","safety_level":"safe","explanation":"解释","example_output":"示例输出","alternatives":["替代命令"]}],"safety_warnings":["安全提示"],"prerequisites":["前置条件"],"related_concepts":["相关概念"]} 只返回JSON格式的结果,不要包含其他文字。`, desc, level) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "command_result": content, "task_description": desc, "difficulty_level": level, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/markdown_formatting func (h *AIModelHandler) MarkdownFormatting(c *gin.Context) { var req struct { ArticleText string `json:"article_text"` EmojiStyle string `json:"emoji_style"` MarkdownOption string `json:"markdown_option"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "文章内容不能为空"}) return } text, err := validateTextLen(req.ArticleText, "文章内容") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } emojiStyle := req.EmojiStyle if emojiStyle == "" { emojiStyle = "balanced" } mdOption := req.MarkdownOption if mdOption == "" { mdOption = "standard" } prompt := fmt.Sprintf(`你是一位专业的文档排版助手。请将用户提供的全文按"标准Markdown格式"进行排版,并在不改变任何原文内容的前提下进行结构化呈现。严格遵守以下规则: 1) 保留所有原始内容,严禁改写、删减或添加新内容。 2) 使用合理的Markdown结构(标题、分节、段落、列表、引用、表格如有必要、代码块仅当原文包含)。 3) 智能添加适量Emoji以增强可读性(%s),在标题、关键句、列表项等处点缀;避免过度使用,保持专业。 4) 保持语言与语气不变,只优化排版和表现形式。 5) 输出"纯Markdown文本",不要包含任何JSON、HTML、XML、解释文字、或代码块围栏标记。 如果原文本较长,可在开头自动生成简洁的"目录"以便阅读。 原文如下: %s`, emojiStyle, text) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "formatted_markdown": content, "source_text": text, "emoji_style": emojiStyle, "markdown_option": mdOption, "timestamp": time.Now().Format(time.RFC3339), }) } // POST /api/aimodelapp/kinship-calculator func (h *AIModelHandler) KinshipCalculator(c *gin.Context) { var req struct { RelationChain string `json:"relation_chain"` Dialects []string `json:"dialects"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "亲属关系链不能为空"}) return } chain, err := validateTextLen(req.RelationChain, "亲属关系链") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } dialects := req.Dialects if len(dialects) == 0 { dialects = []string{"粤语", "闽南语", "上海话", "四川话", "东北话", "客家话"} } prompt := fmt.Sprintf(`你是一位中国亲属称呼专家。请解析下面的亲属关系链,给出最终的亲属称呼。 请遵循: 1) 以中国大陆通行的标准普通话称呼为准。 2) 同时给出若干方言的对应称呼:%s。 3) 如存在地区差异或性别歧义,请在notes中说明。 4) 不要展示推理过程;只输出JSON。 严格按以下JSON结构输出: {"mandarin_title":"标准普通话称呼","dialect_titles":{"粤语":{"title":"称呼","romanization":"粤拼","notes":"说明"}},"notes":"总体说明"} 关系链:%s`, strings.Join(dialects, "、"), chain) messages := []service.ChatMessage{{Role: "user", Content: prompt}} content, err := service.CallDeepSeek(messages, "", 3) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "kinship_result": content, "relation_chain": chain, "timestamp": time.Now().Format(time.RFC3339), }) } // GET /api/aimodelapp/models — 仅返回允许的模型列表 func (h *AIModelHandler) GetModels(c *gin.Context) { models := make(map[string][]string) for provider, modelSet := range allowedModels { for m := range modelSet { models[provider] = append(models[provider], m) } } c.JSON(http.StatusOK, gin.H{ "success": true, "models": models, "default_provider": "deepseek", "default_model": "deepseek-chat", }) } func extractOrRaw(content string) interface{} { return content } // Ping 用于健康检查(从 config 引用原始配置可选) func Ping(c *gin.Context) { _ = config.Cfg c.JSON(http.StatusOK, gin.H{ "status": "running", "timestamp": time.Now().Format(time.RFC3339), }) }