refactor: 将客服后端重构为宿主可嵌入模块

- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。

- 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。

- 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。

- 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
This commit is contained in:
t
2026-08-28 22:23:13 +08:00
parent 6845c728f8
commit 18c9354095
377 changed files with 13199 additions and 22881 deletions
@@ -3,8 +3,6 @@ package dashboard
import (
"code.tczkiot.com/wlw/ai-agent/internal/builders"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/httpx"
"encoding/json"
"strings"
"code.tczkiot.com/wlw/ai-agent/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/constants"
@@ -12,7 +10,6 @@ import (
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto/response"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/toolx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/utils"
"code.tczkiot.com/wlw/ai-agent/internal/services"
@@ -240,6 +237,7 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
ret := response.AIAgentResponse{
ID: item.ID,
Name: item.Name,
Avatar: item.Avatar,
Description: item.Description,
Status: item.Status,
StatusName: enums.GetStatusLabel(item.Status),
@@ -261,11 +259,7 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
FallbackModeName: enums.GetAIAgentFallbackModeLabel(item.FallbackMode),
FallbackMessage: item.FallbackMessage,
KnowledgeBaseIDs: utils.SplitInt64s(item.KnowledgeIDs),
SkillIDs: utils.SplitInt64s(item.SkillIDs),
Skills: make([]response.AIAgentSkillResponse, 0),
Teams: make([]response.AIAgentTeamResponse, 0),
MCPTools: make([]response.AIAgentMCPToolResponse, 0),
WorkflowBindings: make([]response.AIAgentWorkflowBindingResponse, 0),
PublishedRevisionID: item.PublishedRevisionID,
SortNo: item.SortNo,
CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"),
@@ -284,69 +278,5 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
})
}
}
for _, id := range ret.SkillIDs {
if skill := services.SkillDefinitionService.Get(id); skill != nil {
ret.Skills = append(ret.Skills, response.AIAgentSkillResponse{
ID: skill.ID,
Name: skill.Name,
})
}
}
if raw := strings.TrimSpace(item.AllowedMCPTools); raw != "" {
var mcpTools []request.AIAgentMCPToolRequest
if err := json.Unmarshal([]byte(raw), &mcpTools); err == nil {
for _, tool := range mcpTools {
toolCode := strings.TrimSpace(tool.ToolCode)
if toolCode == "" {
toolCode = toolx.BuildMCPToolCode(tool.ServerCode, tool.ToolName)
}
toolCode = toolx.NormalizeToolCodeAlias(toolCode)
if toolx.ResolveToolSourceType(toolCode) != enums.ToolSourceTypeMCP {
continue
}
serverCode := strings.TrimSpace(tool.ServerCode)
toolName := strings.TrimSpace(tool.ToolName)
if registeredServerCode, registeredToolName, ok := toolx.GetRegisteredToolIdentity(toolCode); ok {
serverCode = registeredServerCode
toolName = registeredToolName
} else if parsedServerCode, parsedToolName := toolx.SplitMCPToolCode(toolCode); parsedServerCode != "" && parsedToolName != "" {
serverCode = parsedServerCode
toolName = parsedToolName
}
title := strings.TrimSpace(tool.Title)
if title == "" {
if registeredTitle := toolx.GetRegisteredToolTitleLocale(toolCode, locale); registeredTitle != "" {
title = registeredTitle
}
}
description := strings.TrimSpace(tool.Description)
if description == "" {
if registeredDescription := toolx.GetRegisteredToolDescriptionLocale(toolCode, locale); registeredDescription != "" {
description = registeredDescription
}
}
ret.MCPTools = append(ret.MCPTools, response.AIAgentMCPToolResponse{
ToolCode: toolCode,
ServerCode: serverCode,
ToolName: toolName,
Title: title,
Description: description,
RiskLevel: tool.RiskLevel,
RequireConfirmation: tool.RequireConfirmation,
Arguments: tool.Arguments,
})
}
}
}
for _, binding := range services.AIAgentService.ListWorkflowBindings(item.ID) {
if binding.Workflow == nil || binding.Version == nil {
continue
}
ret.WorkflowBindings = append(ret.WorkflowBindings, response.AIAgentWorkflowBindingResponse{
ID: binding.Binding.ID, WorkflowID: binding.Binding.WorkflowID, WorkflowVersionID: binding.Binding.WorkflowVersionID,
WorkflowName: binding.Workflow.Name, WorkflowVersion: binding.Version.Version, ToolName: binding.Binding.ToolName,
TriggerInstruction: binding.Binding.TriggerInstruction, Priority: binding.Binding.Priority, Enabled: binding.Binding.Enabled,
})
}
return ret
}