package service import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" "github.com/casehub/ticket-workbench/internal/model" "gorm.io/gorm" ) type GLMRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` } type Message struct { Role string `json:"role"` Content string `json:"content"` } type GLMResponse struct { Choices []Choice `json:"choices"` } type Choice struct { Message Message `json:"message"` } type AnalysisResult struct { Category string `json:"category"` Priority json.Number `json:"priority"` Summary string `json:"summary"` SuggestRole string `json:"suggest_role"` } func AnalyzeTicket(db *gorm.DB, ticketid int, apikey, baseURL, glmModel, title, content, contactname, contactphone string) (*model.TicketAiAnalysis, error) { prompt := fmt.Sprintf(`你是一个客服工单分析助手。请分析以下工单内容,返回JSON格式的分析结果: { "category": "分类(refund/login/invoice/logistics/account/inquiry/other)", "priority": "优先级(0=P0紧急,1=P1高,2=P2中,3=P3低)", "summary": "一句话摘要", "suggest_role": "建议处理团队(refund_team/tech_support/finance_team/logistics_team/customer_service)" } 工单标题: %s 工单内容: %s 联系人: %s 联系电话: %s 请只返回JSON,不要其他内容。`, title, content, contactname, contactphone) reqBody := GLMRequest{ Model: glmModel, Messages: []Message{ {Role: "system", Content: "你是一个专业的客服工单分析助手,擅长对工单进行分类、优先级判断和智能分派。"}, {Role: "user", Content: prompt}, }, } jsonData, _ := json.Marshal(reqBody) httpReq, err := http.NewRequest("POST", baseURL+"/chat/completions", bytes.NewBuffer(jsonData)) if err != nil { return nil, err } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Authorization", "Bearer "+apikey) client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(httpReq) if err != nil { return nil, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var glmResp GLMResponse if err := json.Unmarshal(body, &glmResp); err != nil { return nil, err } if len(glmResp.Choices) == 0 { return nil, fmt.Errorf("AI返回为空") } // Clean markdown code block wrapping from AI response aiContent := strings.TrimSpace(glmResp.Choices[0].Message.Content) aiContent = strings.TrimPrefix(aiContent, "```json") aiContent = strings.TrimPrefix(aiContent, "```") aiContent = strings.TrimSuffix(aiContent, "```") aiContent = strings.TrimSpace(aiContent) var result AnalysisResult if err := json.Unmarshal([]byte(aiContent), &result); err != nil { return nil, fmt.Errorf("解析AI响应失败: %v, 原始内容: %s", err, aiContent) } priority := int16(2) // default P2 if n, err := result.Priority.Int64(); err == nil { priority = int16(n) } analysis := &model.TicketAiAnalysis{ Ticketid: ticketid, Category: result.Category, Priority: priority, Summary: result.Summary, Suggestrole: result.SuggestRole, Rawresponse: glmResp.Choices[0].Message.Content, Confirmed: 0, Createtime: time.Now(), } if err := db.Create(analysis).Error; err != nil { return nil, err } AddOperationLog(db, ticketid, 0, "analyze", "AI分析: "+result.Summary) return analysis, nil } func GetAnalysisByTicketID(db *gorm.DB, ticketid int) ([]model.TicketAiAnalysis, error) { var analyses []model.TicketAiAnalysis err := db.Where("ticketid = ?", ticketid).Order("createtime DESC").Find(&analyses).Error return analyses, err } func ConfirmAnalysis(db *gorm.DB, analysisid int, category string, priority int16, summary string, confirmedby int) error { updates := map[string]interface{}{ "confirmed": 1, "confirmedby": confirmedby, "confirmedat": time.Now(), } if category != "" { updates["category"] = category } if priority >= 0 { updates["priority"] = priority } if summary != "" { updates["summary"] = summary } if err := db.Model(&model.TicketAiAnalysis{}).Where("analysisid = ?", analysisid).Updates(updates).Error; err != nil { return err } var analysis model.TicketAiAnalysis db.Where("analysisid = ?", analysisid).First(&analysis) AddOperationLog(db, analysis.Ticketid, confirmedby, "confirm_analysis", "确认AI分析结果") return nil }