Files
ticket-workbench/backend/internal/service/analysis_service.go
绝尘 4793b1a533 新增: AI工单处理工作台 v1.0
- Go Gin 后端 (19个源文件): 认证、工单CRUD、GLM AI分析、状态流转、备注、操作日志
- Arco Design Vue 前端: 登录、工单列表/详情/创建、AI分析触发与确认
- MySQL 5表: ticket_user/ticket_info/ticket_ai_analysis/ticket_operation_log/ticket_note
- 部署: tk.1216.top HTTPS, Nginx反代
2026-05-13 17:05:49 +08:00

162 lines
4.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}