Refactor error handling in services to use internationalized error messages
- Updated OSSStorage validation errors to use internationalized messages. - Changed error messages in provider.go for unsupported file storage types. - Refactored tag_service.go to replace hardcoded error messages with internationalized versions. - Updated ticket_service.go to use internationalized error messages for various validation checks. - Refactored ticket_tag_service.go to use internationalized error messages for tag validation. - Changed ticket_view_service.go to use internationalized error messages for view validation. - Updated tool_catalog_service.go to use internationalized error messages for tool code validation. - Refactored user_service.go to replace error messages with internationalized versions. - Updated ws_service.go to use internationalized error messages for WebSocket handling. - Refactored wxwork_kf_inbound_service.go to use internationalized error messages for message handling. - Updated wxwork_kf_outbound_service.go to use internationalized error messages for outbound message handling. - Refactored wxwork_login_service.go to use internationalized error messages for login handling. - Updated login.go in wxwork package to use internationalized error messages for login state and ticket validation.
This commit is contained in:
@@ -6,6 +6,7 @@ require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/cloudwego/eino v0.8.7
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.11
|
||||
github.com/coreos/go-oidc/v3 v3.18.0
|
||||
github.com/eino-contrib/jsonschema v1.0.3
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
@@ -19,10 +20,8 @@ require (
|
||||
github.com/mlogclub/codegen v1.0.3
|
||||
github.com/mlogclub/simple v1.2.40
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/openai/openai-go/v3 v3.28.0
|
||||
github.com/panjf2000/ants/v2 v2.12.0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/qdrant/go-client v1.17.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/silenceper/wechat/v2 v2.1.12
|
||||
@@ -32,6 +31,7 @@ require (
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/text v0.35.0
|
||||
golang.org/x/tools v0.42.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -51,7 +51,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.15 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.18.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/evanphx/json-patch v0.5.2 // indirect
|
||||
@@ -83,6 +82,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nikolalohinski/gonja v1.5.3 // indirect
|
||||
github.com/nxadm/tail v1.4.11 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
@@ -109,7 +109,6 @@ require (
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
@@ -197,8 +195,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||
github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
|
||||
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@@ -334,8 +330,6 @@ go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4Len
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s *embedding) GetModel(ctx context.Context) (*models.AIConfig, error) {
|
||||
|
||||
func (s *embedding) GenerateEmbedding(ctx context.Context, text string) (*EmbeddingResult, error) {
|
||||
if text == "" {
|
||||
return nil, errorsx.InvalidParam("文本内容不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0215")
|
||||
}
|
||||
|
||||
result, err := s.callEmbeddingAPI(ctx, text)
|
||||
@@ -45,7 +45,7 @@ func (s *embedding) GenerateEmbedding(ctx context.Context, text string) (*Embedd
|
||||
|
||||
func (s *embedding) GenerateBatchEmbeddings(ctx context.Context, texts []string) ([]EmbeddingResult, error) {
|
||||
if len(texts) == 0 {
|
||||
return nil, errorsx.InvalidParam("文本列表不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0216")
|
||||
}
|
||||
|
||||
results := make([]EmbeddingResult, 0, len(texts))
|
||||
|
||||
@@ -2,12 +2,12 @@ package mcps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
@@ -52,7 +52,7 @@ func (c *Client) ListTools(ctx context.Context, cfg ServerConfig) ([]ToolInfo, e
|
||||
|
||||
result, err := session.ListTools(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("列出 MCP 工具失败: %w", err)
|
||||
return nil, i18nx.Errorf("error.mcp.listToolsFailed", err)
|
||||
}
|
||||
ret := make([]ToolInfo, 0, len(result.Tools))
|
||||
for _, tool := range result.Tools {
|
||||
@@ -70,7 +70,7 @@ func (c *Client) ListTools(ctx context.Context, cfg ServerConfig) ([]ToolInfo, e
|
||||
func (c *Client) CallTool(ctx context.Context, cfg ServerConfig, toolName string, arguments map[string]any) (*ToolCallResult, error) {
|
||||
toolName = strings.TrimSpace(toolName)
|
||||
if toolName == "" {
|
||||
return nil, errorsx.InvalidParam("toolName不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0076")
|
||||
}
|
||||
|
||||
session, closeFn, err := c.connect(ctx, cfg)
|
||||
@@ -84,7 +84,7 @@ func (c *Client) CallTool(ctx context.Context, cfg ServerConfig, toolName string
|
||||
Arguments: arguments,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("调用 MCP 工具失败: %w", err)
|
||||
return nil, i18nx.Errorf("error.mcp.callToolFailed", err)
|
||||
}
|
||||
return &ToolCallResult{
|
||||
ServerCode: cfg.Code,
|
||||
@@ -97,10 +97,10 @@ func (c *Client) CallTool(ctx context.Context, cfg ServerConfig, toolName string
|
||||
|
||||
func (c *Client) connect(ctx context.Context, cfg ServerConfig) (*mcp.ClientSession, func(), error) {
|
||||
if strings.TrimSpace(cfg.Code) == "" {
|
||||
return nil, nil, errorsx.InvalidParam("serverCode不能为空")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0070")
|
||||
}
|
||||
if strings.TrimSpace(cfg.Endpoint) == "" {
|
||||
return nil, nil, errorsx.InvalidParam("MCP endpoint不能为空")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0032")
|
||||
}
|
||||
|
||||
timeout := time.Duration(cfg.TimeoutMS) * time.Millisecond
|
||||
@@ -128,7 +128,7 @@ func (c *Client) connect(ctx context.Context, cfg ServerConfig) (*mcp.ClientSess
|
||||
session, err := client.Connect(connCtx, transport, nil)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, nil, fmt.Errorf("连接 MCP Server 失败: %w", err)
|
||||
return nil, nil, i18nx.Errorf("error.mcp.connectServerFailed", err)
|
||||
}
|
||||
return session, func() {
|
||||
_ = session.Close()
|
||||
|
||||
@@ -39,18 +39,18 @@ func (s *RuntimeService) ListTools(ctx context.Context, serverCode string) ([]To
|
||||
func (s *RuntimeService) resolveServer(serverCode string) (ServerConfig, error) {
|
||||
cfg := config.Current()
|
||||
if !cfg.MCP.Enabled {
|
||||
return ServerConfig{}, errorsx.InvalidParam("MCP未启用")
|
||||
return ServerConfig{}, errorsx.InvalidParamI18n("error.e0035")
|
||||
}
|
||||
serverCode = strings.TrimSpace(serverCode)
|
||||
if serverCode == "" {
|
||||
return ServerConfig{}, errorsx.InvalidParam("serverCode不能为空")
|
||||
return ServerConfig{}, errorsx.InvalidParamI18n("error.e0070")
|
||||
}
|
||||
server, ok := cfg.MCP.Servers[serverCode]
|
||||
if !ok {
|
||||
return ServerConfig{}, errorsx.InvalidParam("MCP服务配置不存在")
|
||||
return ServerConfig{}, errorsx.InvalidParamI18n("error.e0034")
|
||||
}
|
||||
if !server.Enabled {
|
||||
return ServerConfig{}, errorsx.InvalidParam("MCP服务未启用")
|
||||
return ServerConfig{}, errorsx.InvalidParamI18n("error.e0033")
|
||||
}
|
||||
return ServerConfig{
|
||||
Code: serverCode,
|
||||
|
||||
@@ -25,7 +25,7 @@ var Answer = &answer{}
|
||||
|
||||
func (s *answer) DebugSearch(ctx context.Context, req request.KnowledgeSearchRequest) (*response.KnowledgeSearchResponse, error) {
|
||||
if strings.TrimSpace(req.Question) == "" {
|
||||
return nil, errorsx.InvalidParam("问题不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0340")
|
||||
}
|
||||
startedAt := time.Now()
|
||||
results, err := s.retrieve(req, ctx)
|
||||
@@ -60,7 +60,7 @@ func (s *answer) DebugSearch(ctx context.Context, req request.KnowledgeSearchReq
|
||||
|
||||
func (s *answer) DebugAnswer(ctx context.Context, req request.KnowledgeAnswerRequest, operator *dto.AuthPrincipal) (*response.KnowledgeAnswerResponse, error) {
|
||||
if strings.TrimSpace(req.Question) == "" {
|
||||
return nil, errorsx.InvalidParam("问题不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0340")
|
||||
}
|
||||
startedAt := time.Now()
|
||||
|
||||
@@ -276,7 +276,7 @@ func (s *answer) BuildDocumentIndex(ctx context.Context, documentID int64) error
|
||||
|
||||
func (s *answer) retrieve(req request.KnowledgeSearchRequest, ctx context.Context) ([]RetrieveResult, error) {
|
||||
if len(normalizeKnowledgeBaseIDs(req.KnowledgeBaseIDs)) == 0 {
|
||||
return nil, errorsx.InvalidParam("知识库不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0284")
|
||||
}
|
||||
knowledgeBases := s.loadKnowledgeBases(req.KnowledgeBaseIDs)
|
||||
|
||||
|
||||
@@ -22,16 +22,16 @@ func init() {
|
||||
func DebugRunSkill(ctx context.Context, req request.SkillDebugRunRequest) (*response.SkillDebugRunResponse, error) {
|
||||
aiAgent := svc.AIAgentService.Get(req.AIAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("AI Agent不存在或未启用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0007")
|
||||
}
|
||||
aiConfig := svc.AIConfigService.Get(aiAgent.AIConfigID)
|
||||
if aiConfig == nil {
|
||||
return nil, errorsx.InvalidParam("AI Agent关联的AI配置不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0008")
|
||||
}
|
||||
var conversation *models.Conversation
|
||||
if req.ConversationID > 0 {
|
||||
if conversation = svc.ConversationService.Get(req.ConversationID); conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
} else {
|
||||
conversation = &models.Conversation{ID: req.ConversationID, AIAgentID: req.AIAgentID}
|
||||
@@ -57,32 +57,32 @@ func DebugRunSkill(ctx context.Context, req request.SkillDebugRunRequest) (*resp
|
||||
func DebugResumeSkill(ctx context.Context, req request.SkillDebugResumeRequest) (*response.SkillDebugRunResponse, error) {
|
||||
aiAgent := svc.AIAgentService.Get(req.AIAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("AI Agent不存在或未启用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0007")
|
||||
}
|
||||
aiConfig := svc.AIConfigService.Get(aiAgent.AIConfigID)
|
||||
if aiConfig == nil {
|
||||
return nil, errorsx.InvalidParam("AI Agent关联的AI配置不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0008")
|
||||
}
|
||||
pendingInterrupt := svc.ConversationInterruptService.GetByCheckPointID(strings.TrimSpace(req.CheckPointID))
|
||||
if pendingInterrupt == nil {
|
||||
return nil, errorsx.InvalidParam("CheckPoint 不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0014")
|
||||
}
|
||||
if pendingInterrupt.AIAgentID > 0 && pendingInterrupt.AIAgentID != req.AIAgentID {
|
||||
return nil, errorsx.InvalidParam("CheckPoint 与 AI Agent 不匹配")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0015")
|
||||
}
|
||||
conversationID := req.ConversationID
|
||||
if conversationID <= 0 {
|
||||
conversationID = pendingInterrupt.ConversationID
|
||||
}
|
||||
if conversationID <= 0 {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
conversation := svc.ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.AIAgentID > 0 && conversation.AIAgentID != req.AIAgentID {
|
||||
return nil, errorsx.InvalidParam("会话与 AI Agent 不匹配")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0117")
|
||||
}
|
||||
resumeText := strings.TrimSpace(req.UserMessage)
|
||||
summary, err := Service.Resume(ctx, applicationruntime.ResumeRequest{
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"agent-desk/internal/ai/mcps"
|
||||
impladapter "agent-desk/internal/ai/runtime/internal/impl/adapter"
|
||||
"agent-desk/internal/ai/runtime/registry"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/pkg/toolx"
|
||||
|
||||
einotool "github.com/cloudwego/eino/components/tool"
|
||||
@@ -161,10 +162,10 @@ func (t *ToolSearchTool) invokeTargetTool(ctx context.Context, toolCode string,
|
||||
toolCode = strings.TrimSpace(toolCode)
|
||||
serverCode, toolName := toolx.SplitMCPToolCode(toolCode)
|
||||
if serverCode == "" || toolName == "" {
|
||||
return "", fmt.Errorf("tool_search 只支持调用 MCP toolCode")
|
||||
return "", i18nx.Errorf("error.e0077")
|
||||
}
|
||||
if !containsToolCode(t.allowedToolCodes, toolCode) {
|
||||
return "", fmt.Errorf("目标工具未被当前会话授权")
|
||||
return "", i18nx.Errorf("error.e0279")
|
||||
}
|
||||
result, err := mcps.Runtime.CallTool(ctx, serverCode, toolName, cloneArguments(arguments))
|
||||
if err != nil {
|
||||
|
||||
@@ -21,7 +21,7 @@ func MatchSkill(execCtx context.Context, ctx RuntimeContext) (*models.SkillDefin
|
||||
if strs.IsNotBlank(ctx.ManualSkillCode) {
|
||||
skill := loader.findManualSkillDefinition(ctx.ManualSkillCode)
|
||||
if skill == nil || skill.Status != enums.StatusOk {
|
||||
return nil, "", nil, errorsx.InvalidParam("Skill 不存在或未启用")
|
||||
return nil, "", nil, errorsx.InvalidParamI18n("error.e0054")
|
||||
}
|
||||
return skill, "manual_skill_code", &RouteTrace{
|
||||
Status: "manual_selected",
|
||||
|
||||
@@ -55,7 +55,7 @@ func NewServer() (*gin.Engine, error) {
|
||||
},
|
||||
NotFoundPrefixes: notFoundPrefixes,
|
||||
NotFoundHandler: func(ctx *gin.Context) {
|
||||
httpx.WriteHttpStatusJSON(ctx, http.StatusNotFound, web.JsonErrorCode(http.StatusNotFound, i18nx.T(ctx, "error.notFound", nil)))
|
||||
httpx.WriteHttpStatusJSON(ctx, http.StatusNotFound, web.JsonErrorCode(http.StatusNotFound, i18nx.T(ctx, "error.notFound")))
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -8,13 +8,12 @@ import (
|
||||
"agent-desk/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func ChannelAnyConfig(ctx *gin.Context) {
|
||||
channel := services.ChannelService.GetEnabledChannel(ctx)
|
||||
if channel == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
cfg, err := resolveWidgetConfig(channel.ChannelType, channel.ConfigJSON)
|
||||
@@ -70,6 +69,6 @@ func resolveWidgetConfig(channelType, rawConfig string) (*webLikeWidgetConfig, e
|
||||
Width: "100%",
|
||||
}, nil
|
||||
default:
|
||||
return nil, errorsx.InvalidParam("该渠道不支持开放客服配置")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0313")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func ConversationGetBy(ctx *gin.Context) {
|
||||
@@ -20,22 +19,22 @@ func ConversationGetBy(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
item := services.ConversationService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0116"))
|
||||
return
|
||||
}
|
||||
if !services.ConversationService.IsCustomerConversationOwner(item, *external) {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("无权访问该会话"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0222"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,12 +48,12 @@ func ConversationGetBy(ctx *gin.Context) {
|
||||
func ConversationPostCreate_or_match(ctx *gin.Context) {
|
||||
channel := services.ChannelService.GetEnabledChannel(ctx)
|
||||
if channel == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,12 +67,12 @@ func ConversationPostCreate_or_match(ctx *gin.Context) {
|
||||
|
||||
func ConversationPostClose(ctx *gin.Context) {
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@ import (
|
||||
"agent-desk/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func CustomerPostSession_exchange(ctx *gin.Context) {
|
||||
channel := services.ChannelService.GetEnabledChannel(ctx)
|
||||
if channel == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道不存在或已停用"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0209"))
|
||||
return
|
||||
}
|
||||
externalUser, err := openidentity.GetExternalUser(ctx, services.ChannelService.GetUserTokenSecret(channel))
|
||||
|
||||
@@ -13,33 +13,32 @@ import (
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func MessageAnyList(ctx *gin.Context) {
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
conversationID, _ := params.GetInt64(ctx, "conversationId")
|
||||
if conversationID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
conversation := services.ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0116"))
|
||||
return
|
||||
}
|
||||
if !services.ConversationService.IsCustomerConversationOwner(conversation, *external) {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("无权访问该会话"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0222"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,12 +57,12 @@ func MessageAnyList(ctx *gin.Context) {
|
||||
|
||||
func MessagePostSend(ctx *gin.Context) {
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,12 +82,12 @@ func MessagePostSend(ctx *gin.Context) {
|
||||
|
||||
func MessagePostRead(ctx *gin.Context) {
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -106,32 +105,32 @@ func MessagePostRead(ctx *gin.Context) {
|
||||
|
||||
func MessagePostUpload_image(ctx *gin.Context) {
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
rawConv := strings.TrimSpace(params.FormValue(ctx, "conversationId"))
|
||||
if rawConv == "" {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
conversationID, err := strconv.ParseInt(rawConv, 10, 64)
|
||||
if err != nil || conversationID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
conversation := services.ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0116"))
|
||||
return
|
||||
}
|
||||
if !services.ConversationService.IsCustomerConversationOwner(conversation, *external) {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("无权访问该会话"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0222"))
|
||||
return
|
||||
}
|
||||
if _, err := services.MessageService.ValidateConversationSender(conversationID, enums.IMSenderTypeCustomer, nil, external); err != nil {
|
||||
@@ -141,11 +140,11 @@ func MessagePostUpload_image(ctx *gin.Context) {
|
||||
|
||||
header, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("请选择上传图片"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0322"))
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(strings.ToLower(header.Header.Get("Content-Type")), "image/") {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("仅支持上传图片文件"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0090"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,23 +158,23 @@ func MessagePostUpload_image(ctx *gin.Context) {
|
||||
|
||||
func MessagePostUpload_attachment(ctx *gin.Context) {
|
||||
if services.ChannelService.GetEnabledChannel(ctx) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("接入渠道未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0211"))
|
||||
return
|
||||
}
|
||||
external := httpx.GetExternalUser(ctx)
|
||||
if external == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("外部身份未初始化"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0150"))
|
||||
return
|
||||
}
|
||||
|
||||
rawConv := strings.TrimSpace(params.FormValue(ctx, "conversationId"))
|
||||
if rawConv == "" {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
conversationID, err := strconv.ParseInt(rawConv, 10, 64)
|
||||
if err != nil || conversationID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
if _, err := services.MessageService.ValidateConversationSender(conversationID, enums.IMSenderTypeCustomer, nil, external); err != nil {
|
||||
@@ -185,7 +184,7 @@ func MessagePostUpload_attachment(ctx *gin.Context) {
|
||||
|
||||
header, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("请选择上传附件"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0324"))
|
||||
return
|
||||
}
|
||||
item, err := services.AssetService.UploadFile(header, "attachments", nil)
|
||||
|
||||
@@ -55,7 +55,7 @@ func AgentGetBy(ctx *gin.Context) {
|
||||
}
|
||||
item := services.AgentProfileService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("客服档案不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0164"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, builders.BuildAgentProfileResponse(item))
|
||||
|
||||
@@ -59,7 +59,7 @@ func AgentRunLogGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.AgentRunLogService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Agent 运行日志不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0013"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, builders.BuildAgentRunLog(item))
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func AgentTeamAnyList(ctx *gin.Context) {
|
||||
@@ -61,7 +60,7 @@ func AgentTeamGetBy(ctx *gin.Context) {
|
||||
}
|
||||
item := services.AgentTeamService.Get(id)
|
||||
if item == nil || item.Status == enums.StatusDeleted {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("客服组不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0169"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, buildAgentTeamResponse(item))
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/dto/response"
|
||||
"agent-desk/internal/pkg/httpx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/services"
|
||||
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
@@ -66,6 +67,7 @@ func AgentTeamSchedulePostBatch_preview(ctx *gin.Context) {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
req.Locale = i18nx.Locale(ctx)
|
||||
ret, err := services.AgentTeamScheduleService.BatchPreview(req, operator)
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
@@ -85,6 +87,7 @@ func AgentTeamSchedulePostBatch_generate(ctx *gin.Context) {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
req.Locale = i18nx.Locale(ctx)
|
||||
ret, err := services.AgentTeamScheduleService.BatchGenerate(req, operator)
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
@@ -104,7 +107,7 @@ func AgentTeamScheduleGetBy(ctx *gin.Context) {
|
||||
}
|
||||
item := services.AgentTeamScheduleService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("客服组排班不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0172"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, buildAgentTeamScheduleResponse(item))
|
||||
|
||||
@@ -64,7 +64,7 @@ func AIAgentGetBy(ctx *gin.Context) {
|
||||
}
|
||||
item := services.AIAgentService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("AI Agent 不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0002"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, buildAIAgentResponseWithLocale(item, i18nx.Locale(ctx)))
|
||||
|
||||
@@ -62,7 +62,7 @@ func AIConfigGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.AIConfigService.Get(id)
|
||||
if item == nil || item.Status == enums.StatusDeleted {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("AI配置不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0012"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, response.BuildAIConfigResponse(item))
|
||||
|
||||
@@ -51,7 +51,7 @@ func AssetGetBy(ctx *gin.Context) {
|
||||
}
|
||||
item := services.AssetService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("文件不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0214"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, builders.BuildAsset(item))
|
||||
@@ -71,7 +71,7 @@ func AssetPostCreate(ctx *gin.Context) {
|
||||
}
|
||||
header, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("请选择上传文件"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0323"))
|
||||
return
|
||||
}
|
||||
item, err := services.AssetService.UploadFile(header, req.Prefix, operator)
|
||||
|
||||
@@ -44,7 +44,7 @@ func ChannelGetBy(ctx *gin.Context) {
|
||||
}
|
||||
item := services.ChannelService.Get(id)
|
||||
if item == nil || item.Status == enums.StatusDeleted {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("channel not found"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0062"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, buildChannelResponse(item))
|
||||
|
||||
@@ -112,7 +112,7 @@ func ConversationGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.ConversationService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0116"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ func ConversationAnyMessage_list(ctx *gin.Context) {
|
||||
limit, _ = params.GetInt(ctx, "limit")
|
||||
)
|
||||
if conversation := services.ConversationService.Get(conversationID); conversation == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0116"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -311,12 +311,12 @@ func ConversationPostUpload_image(ctx *gin.Context) {
|
||||
|
||||
rawConv := strings.TrimSpace(params.FormValue(ctx, "conversationId"))
|
||||
if rawConv == "" {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
conversationID, err := strconv.ParseInt(rawConv, 10, 64)
|
||||
if err != nil || conversationID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
if _, err := services.MessageService.ValidateConversationSender(conversationID, enums.IMSenderTypeAgent, operator, nil); err != nil {
|
||||
@@ -326,11 +326,11 @@ func ConversationPostUpload_image(ctx *gin.Context) {
|
||||
|
||||
header, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("请选择上传图片"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0322"))
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(strings.ToLower(header.Header.Get("Content-Type")), "image/") {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("仅支持上传图片文件"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0090"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -351,12 +351,12 @@ func ConversationPostUpload_attachment(ctx *gin.Context) {
|
||||
|
||||
rawConv := strings.TrimSpace(params.FormValue(ctx, "conversationId"))
|
||||
if rawConv == "" {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
conversationID, err := strconv.ParseInt(rawConv, 10, 64)
|
||||
if err != nil || conversationID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("conversationId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0064"))
|
||||
return
|
||||
}
|
||||
if _, err := services.MessageService.ValidateConversationSender(conversationID, enums.IMSenderTypeAgent, operator, nil); err != nil {
|
||||
@@ -366,7 +366,7 @@ func ConversationPostUpload_attachment(ctx *gin.Context) {
|
||||
|
||||
header, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("请选择上传附件"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0324"))
|
||||
return
|
||||
}
|
||||
item, err := services.AssetService.UploadFile(header, "attachments", operator)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
// AnyList GET/POST /customer-contact/list?customerId=
|
||||
@@ -21,7 +20,7 @@ func CustomerContactAnyList(ctx *gin.Context) {
|
||||
}
|
||||
customerID, _ := params.GetInt64(ctx, "customerId")
|
||||
if customerID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("customerId 必填"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0065"))
|
||||
return
|
||||
}
|
||||
list := services.CustomerContactService.FindActiveByCustomerID(customerID)
|
||||
|
||||
@@ -72,7 +72,7 @@ func KnowledgeBaseGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.KnowledgeBaseService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("知识库不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0283"))
|
||||
return
|
||||
}
|
||||
resp := builders.BuildKnowledgeBase(item)
|
||||
@@ -169,7 +169,7 @@ func KnowledgeBasePostRebuild_index(ctx *gin.Context) {
|
||||
|
||||
knowledgeBase := services.KnowledgeBaseService.Get(req.ID)
|
||||
if knowledgeBase == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("知识库不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0283"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ func KnowledgeDirectoryGetList_all(ctx *gin.Context) {
|
||||
}
|
||||
knowledgeBaseID, ok := params.GetInt64(ctx, "knowledgeBaseId")
|
||||
if !ok || knowledgeBaseID <= 0 {
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParam("知识库不存在"))
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0283"))
|
||||
return
|
||||
}
|
||||
list := services.KnowledgeDirectoryService.FindAllByKnowledgeBaseID(knowledgeBaseID)
|
||||
|
||||
@@ -37,7 +37,7 @@ func KnowledgeDocumentAnyList(ctx *gin.Context) {
|
||||
}
|
||||
if indexStatus, ok := params.Get(ctx, "indexStatus"); ok {
|
||||
if !enums.IsValidKnowledgeDocumentIndexStatus(indexStatus) {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("indexStatus参数不合法"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0067"))
|
||||
return
|
||||
}
|
||||
cnd.Where("index_status = ?", indexStatus)
|
||||
@@ -66,7 +66,7 @@ func KnowledgeDocumentGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.KnowledgeDocumentService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("文档不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0218"))
|
||||
return
|
||||
}
|
||||
resp := builders.BuildKnowledgeDocument(item)
|
||||
|
||||
@@ -38,7 +38,7 @@ func KnowledgeFAQGetExport(ctx *gin.Context) {
|
||||
}
|
||||
knowledgeBaseID, ok := params.GetInt64(ctx, "knowledgeBaseId")
|
||||
if !ok || knowledgeBaseID <= 0 {
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParam("知识库不存在"))
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0283"))
|
||||
return
|
||||
}
|
||||
file, err := services.KnowledgeFAQService.ExportKnowledgeFAQs(knowledgeBaseID)
|
||||
@@ -64,17 +64,17 @@ func KnowledgeFAQPostImport(ctx *gin.Context) {
|
||||
}
|
||||
knowledgeBaseID, ok := params.GetInt64(ctx, "knowledgeBaseId")
|
||||
if !ok || knowledgeBaseID <= 0 {
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParam("知识库不存在"))
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0283"))
|
||||
return
|
||||
}
|
||||
header, err := ctx.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParam("请选择导入文件"))
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0327"))
|
||||
return
|
||||
}
|
||||
file, err := header.Open()
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParam("导入文件读取失败"))
|
||||
httpx.WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0176"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
@@ -83,12 +83,12 @@ func KnowledgeFAQPostImport(ctx *gin.Context) {
|
||||
Mode: mode,
|
||||
Filename: header.Filename,
|
||||
Reader: file,
|
||||
Locale: i18nx.Locale(ctx),
|
||||
}, operator)
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
translateKnowledgeFAQImportResult(i18nx.Locale(ctx), result)
|
||||
httpx.WriteJSON(ctx, result)
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func KnowledgeFAQGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.KnowledgeFAQService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("FAQ不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0025"))
|
||||
return
|
||||
}
|
||||
resp := builders.BuildKnowledgeFAQ(item)
|
||||
@@ -235,15 +235,6 @@ func writeKnowledgeFAQExcelFile(ctx *gin.Context, file *response.KnowledgeFAQExp
|
||||
ctx.Data(http.StatusOK, file.ContentType, file.Data)
|
||||
}
|
||||
|
||||
func translateKnowledgeFAQImportResult(locale string, result *response.KnowledgeFAQImportResult) {
|
||||
if result == nil || len(result.Errors) == 0 {
|
||||
return
|
||||
}
|
||||
for i := range result.Errors {
|
||||
result.Errors[i].Message = i18nx.TranslateKnownMessage(locale, result.Errors[i].Message)
|
||||
}
|
||||
}
|
||||
|
||||
func fillKnowledgeFAQDirectory(resp *response.KnowledgeFAQResponse, directoryPaths map[int64]string) {
|
||||
resp.DirectoryPath = directoryPaths[resp.DirectoryID]
|
||||
resp.DirectoryName = resp.DirectoryPath
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func KnowledgeRetrievePostDebugSearch(ctx *gin.Context) {
|
||||
@@ -92,5 +91,5 @@ func KnowledgeRetrievePostBuild(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("documentId或faqId不能为空"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0066"))
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func KnowledgeRetrieveLogGetBy(ctx *gin.Context) {
|
||||
|
||||
logItem := services.KnowledgeRetrieveLogService.Get(id)
|
||||
if logItem == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("检索日志不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0241"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ func PermissionGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.PermissionService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("权限不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0236"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, &response.PermissionResponse{
|
||||
|
||||
@@ -44,7 +44,7 @@ func QuickReplyGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.QuickReplyService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("快捷回复不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0203"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, builders.BuildQuickReplyResponse(item))
|
||||
|
||||
@@ -71,7 +71,7 @@ func RoleGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.RoleService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("角色不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0305"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ func SkillDefinitionGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.SkillDefinitionService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill 不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0053"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, builders.BuildSkillDefinitionResponse(item))
|
||||
@@ -131,24 +131,24 @@ func SkillDefinitionPostUpdate_status(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill ID 不合法"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0052"))
|
||||
return
|
||||
}
|
||||
if !enums.IsValidStatus(req.Status) {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("状态值不合法"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0254"))
|
||||
return
|
||||
}
|
||||
item := services.SkillDefinitionService.Get(req.ID)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill 不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0053"))
|
||||
return
|
||||
}
|
||||
if item.Status == enums.StatusDeleted {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("已删除的 Skill 不能直接修改状态,请先恢复"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0184"))
|
||||
return
|
||||
}
|
||||
if req.Status == int(enums.StatusDeleted) {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("请使用删除接口处理删除状态"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0319"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,11 +177,11 @@ func SkillDefinitionPostDelete(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill ID 不合法"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0052"))
|
||||
return
|
||||
}
|
||||
if services.SkillDefinitionService.Get(req.ID) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill 不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0053"))
|
||||
return
|
||||
}
|
||||
if err := services.SkillDefinitionService.Updates(req.ID, map[string]any{
|
||||
@@ -209,17 +209,17 @@ func SkillDefinitionPostRestore(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill ID 不合法"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0052"))
|
||||
return
|
||||
}
|
||||
|
||||
item := services.SkillDefinitionService.Get(req.ID)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("Skill 不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0053"))
|
||||
return
|
||||
}
|
||||
if item.Status != enums.StatusDeleted {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("仅已删除的 Skill 支持恢复"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0088"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ func TagGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.TagService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("标签不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0238"))
|
||||
return
|
||||
}
|
||||
result := builders.BuildTagResponse(item)
|
||||
|
||||
@@ -252,11 +252,11 @@ func TicketAnyProgressList(ctx *gin.Context) {
|
||||
}
|
||||
ticketID, _ := params.GetInt64(ctx, "ticketId")
|
||||
if ticketID <= 0 {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("工单不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0178"))
|
||||
return
|
||||
}
|
||||
if services.TicketService.Get(ticketID) == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("工单不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0178"))
|
||||
return
|
||||
}
|
||||
progresses := services.TicketProgressService.Find(sqls.NewCnd().Eq("ticket_id", ticketID).Asc("id"))
|
||||
|
||||
@@ -68,7 +68,7 @@ func UserGetBy(ctx *gin.Context) {
|
||||
|
||||
item := services.UserService.Get(id)
|
||||
if item == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("用户不存在"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0255"))
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, builders.BuildUserResponse(item, builders.UserBuildOptions{
|
||||
@@ -191,7 +191,7 @@ func UserPostChange_password(ctx *gin.Context) {
|
||||
principal = services.AuthService.GetAuthPrincipal(ctx)
|
||||
}
|
||||
if principal == nil {
|
||||
httpx.WriteJSON(ctx, web.JsonErrorMsg("未登录或登录已过期"))
|
||||
httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.auth.expired"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func AuthMiddleware(ctx *gin.Context) {
|
||||
func authenticateRequest(ctx *gin.Context) bool {
|
||||
if _, err := services.AuthService.Authenticate(ctx); err != nil {
|
||||
result := web.JsonError(err)
|
||||
result.Message = i18nx.T(ctx, "error.auth.expired", nil)
|
||||
result.Message = i18nx.T(ctx, "error.auth.expired")
|
||||
ctx.JSON(200, result)
|
||||
ctx.Abort()
|
||||
return false
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
func ExternalUserMiddleware(ctx *gin.Context) {
|
||||
channel := services.ChannelService.GetEnabledChannel(ctx)
|
||||
if channel == nil {
|
||||
ctx.JSON(200, web.JsonErrorMsg("接入渠道异常"))
|
||||
ctx.JSON(200, httpx.JsonErrorMsg(ctx, "error.e0210"))
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"agent-desk/internal/pkg/config"
|
||||
"agent-desk/internal/pkg/dto/response"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -66,16 +66,16 @@ func Init(ctx context.Context) error {
|
||||
}
|
||||
oidcCfg = cfg
|
||||
if strings.TrimSpace(cfg.Issuer) == "" {
|
||||
return fmt.Errorf("OIDC issuer 未配置")
|
||||
return i18nx.Errorf("error.e0039")
|
||||
}
|
||||
if strings.TrimSpace(cfg.ClientID) == "" {
|
||||
return fmt.Errorf("OIDC clientId 未配置")
|
||||
return i18nx.Errorf("error.e0036")
|
||||
}
|
||||
if strings.TrimSpace(cfg.ClientSecret) == "" {
|
||||
return fmt.Errorf("OIDC clientSecret 未配置")
|
||||
return i18nx.Errorf("error.e0037")
|
||||
}
|
||||
if strings.TrimSpace(cfg.RedirectURL) == "" {
|
||||
return fmt.Errorf("OIDC redirectUrl 未配置")
|
||||
return i18nx.Errorf("error.e0040")
|
||||
}
|
||||
|
||||
p, err := gooidc.NewProvider(ctx, strings.TrimSpace(cfg.Issuer))
|
||||
@@ -119,7 +119,7 @@ func ExchangeCode(ctx context.Context, code string) (*Profile, error) {
|
||||
}
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return nil, errorsx.InvalidParam("OIDC 授权 code 不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0041")
|
||||
}
|
||||
token, err := oauthConfig.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
@@ -127,7 +127,7 @@ func ExchangeCode(ctx context.Context, code string) (*Profile, error) {
|
||||
}
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok || strings.TrimSpace(rawIDToken) == "" {
|
||||
return nil, errorsx.Unauthorized("OIDC id_token 不存在")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0038")
|
||||
}
|
||||
idToken, err := idTokenVerifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
@@ -171,32 +171,32 @@ func CreateState(next string) (string, error) {
|
||||
func ParseState(state string) (string, error) {
|
||||
secret := stateSecret()
|
||||
if secret == "" {
|
||||
return "", errorsx.Unauthorized("OIDC 登录状态无效或已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.e0046")
|
||||
}
|
||||
parts := strings.Split(strings.TrimSpace(state), ".")
|
||||
if len(parts) != 2 {
|
||||
return "", errorsx.Unauthorized("OIDC 登录状态无效或已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.e0046")
|
||||
}
|
||||
if !hmac.Equal([]byte(parts[1]), []byte(signState(parts[0], secret))) {
|
||||
return "", errorsx.Unauthorized("OIDC 登录状态无效或已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.e0046")
|
||||
}
|
||||
body, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||
if err != nil {
|
||||
return "", errorsx.Unauthorized("OIDC 登录状态无效或已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.e0046")
|
||||
}
|
||||
payload := statePayload{}
|
||||
if err = json.Unmarshal(body, &payload); err != nil {
|
||||
return "", errorsx.Unauthorized("OIDC 登录状态无效或已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.e0046")
|
||||
}
|
||||
if payload.ExpiredAt <= time.Now().Unix() {
|
||||
return "", errorsx.Unauthorized("OIDC 登录状态无效或已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.e0046")
|
||||
}
|
||||
return sanitizeNextPath(payload.Next), nil
|
||||
}
|
||||
|
||||
func IssueLoginTicket(loginResp *response.LoginResponse) (string, error) {
|
||||
if loginResp == nil {
|
||||
return "", fmt.Errorf("登录结果不能为空")
|
||||
return "", i18nx.Errorf("error.e0272")
|
||||
}
|
||||
ticket, err := randomToken("olt_")
|
||||
if err != nil {
|
||||
@@ -213,15 +213,15 @@ func IssueLoginTicket(loginResp *response.LoginResponse) (string, error) {
|
||||
func ConsumeLoginTicket(ticket string) (*response.LoginResponse, error) {
|
||||
ticket = strings.TrimSpace(ticket)
|
||||
if ticket == "" {
|
||||
return nil, errorsx.InvalidParam("ticket 不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0072")
|
||||
}
|
||||
value, ok := loginTicketStore.LoadAndDelete(ticket)
|
||||
if !ok {
|
||||
return nil, errorsx.Unauthorized("登录票据无效或已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0271")
|
||||
}
|
||||
record, ok := value.(loginTicket)
|
||||
if !ok || record.Response == nil || time.Now().After(record.ExpiredAt) {
|
||||
return nil, errorsx.Unauthorized("登录票据无效或已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0271")
|
||||
}
|
||||
return record.Response, nil
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func profileFromIDToken(idToken *gooidc.IDToken) (*Profile, error) {
|
||||
RawProfile: string(raw),
|
||||
}
|
||||
if strings.TrimSpace(profile.Subject) == "" {
|
||||
return nil, errorsx.Unauthorized("OIDC 用户标识不存在")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0043")
|
||||
}
|
||||
return profile, nil
|
||||
}
|
||||
@@ -272,7 +272,7 @@ func profileFromUserInfo(userInfo *gooidc.UserInfo, fallback *Profile) (*Profile
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(profile.Subject) == "" {
|
||||
return nil, errorsx.Unauthorized("OIDC 用户标识不存在")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0043")
|
||||
}
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
@@ -76,4 +76,5 @@ type AgentTeamScheduleBatchRequest struct {
|
||||
StartTime string `json:"startTime"`
|
||||
EndTime string `json:"endTime"`
|
||||
Remark string `json:"remark"`
|
||||
Locale string `json:"-"`
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ type ImportKnowledgeFAQRequest struct {
|
||||
Mode KnowledgeFAQImportMode
|
||||
Filename string
|
||||
Reader io.Reader
|
||||
Locale string
|
||||
}
|
||||
|
||||
type KnowledgeSearchRequest struct {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package errorsx
|
||||
|
||||
import "github.com/mlogclub/simple/web"
|
||||
import (
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
const (
|
||||
CodeInvalidParam = 1000
|
||||
@@ -16,26 +20,92 @@ func InvalidParam(message string) error {
|
||||
return web.NewError(CodeInvalidParam, message)
|
||||
}
|
||||
|
||||
func InvalidParamI18n(key string, args ...any) error {
|
||||
return NewI18nError(CodeInvalidParam, key, args...)
|
||||
}
|
||||
|
||||
func BusinessError(code int, message string) error {
|
||||
return web.NewError(CodeBusinessError+code, message)
|
||||
}
|
||||
|
||||
func BusinessErrorI18n(code int, key string, args ...any) error {
|
||||
return NewI18nError(CodeBusinessError+code, key, args...)
|
||||
}
|
||||
|
||||
func Unauthorized(message string) error {
|
||||
return web.NewError(CodeAuthUnauthorized, message)
|
||||
}
|
||||
|
||||
func UnauthorizedI18n(key string, args ...any) error {
|
||||
return NewI18nError(CodeAuthUnauthorized, key, args...)
|
||||
}
|
||||
|
||||
func Forbidden(message string) error {
|
||||
return web.NewError(CodeAuthForbidden, message)
|
||||
}
|
||||
|
||||
func ForbiddenI18n(key string, args ...any) error {
|
||||
return NewI18nError(CodeAuthForbidden, key, args...)
|
||||
}
|
||||
|
||||
func InvalidToken(message string) error {
|
||||
return web.NewError(CodeAuthInvalidToken, message)
|
||||
}
|
||||
|
||||
func InvalidTokenI18n(key string, args ...any) error {
|
||||
return NewI18nError(CodeAuthInvalidToken, key, args...)
|
||||
}
|
||||
|
||||
func InvalidAccount(message string) error {
|
||||
return web.NewError(CodeAuthInvalidAccount, message)
|
||||
}
|
||||
|
||||
func InvalidAccountI18n(key string, args ...any) error {
|
||||
return NewI18nError(CodeAuthInvalidAccount, key, args...)
|
||||
}
|
||||
|
||||
func CredentialLocked(message string) error {
|
||||
return web.NewError(CodeAuthCredentialLocked, message)
|
||||
}
|
||||
|
||||
func CredentialLockedI18n(key string, args ...any) error {
|
||||
return NewI18nError(CodeAuthCredentialLocked, key, args...)
|
||||
}
|
||||
|
||||
type I18nError struct {
|
||||
Code int
|
||||
Key string
|
||||
Args []any
|
||||
}
|
||||
|
||||
func NewI18nError(code int, key string, args ...any) *I18nError {
|
||||
return &I18nError{Code: code, Key: key, Args: args}
|
||||
}
|
||||
|
||||
func (e *I18nError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Message(i18nx.LocaleZhCN)
|
||||
}
|
||||
|
||||
func (e *I18nError) Unwrap() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return web.NewError(e.Code, e.Error())
|
||||
}
|
||||
|
||||
func (e *I18nError) Message(locale string) string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return i18nx.Getf(locale, e.Key, e.Args...)
|
||||
}
|
||||
|
||||
func (e *I18nError) JsonResult(locale string) *web.JsonResult {
|
||||
if e == nil {
|
||||
return web.JsonSuccess()
|
||||
}
|
||||
return web.JsonErrorCode(e.Code, e.Message(locale))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func JsonErrorMsg(ctx *gin.Context, key string, args ...any) *web.JsonResult {
|
||||
return web.JsonErrorMsg(i18nx.T(ctx, key, args...))
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func GetPathInt64(ctx *gin.Context, name string) (int64, bool) {
|
||||
value, err := strconv.ParseInt(ctx.Param(name), 10, 64)
|
||||
if err != nil {
|
||||
WriteHttpStatusJSON(ctx, http.StatusBadRequest, web.JsonErrorMsg(i18nx.T(ctx, "error.path.invalid", nil)))
|
||||
WriteHttpStatusJSON(ctx, http.StatusBadRequest, web.JsonErrorMsg(i18nx.T(ctx, "error.path.invalid")))
|
||||
return 0, false
|
||||
}
|
||||
return value, true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"net/http"
|
||||
|
||||
@@ -20,6 +21,10 @@ type pageData struct {
|
||||
paging *sqls.Paging
|
||||
}
|
||||
|
||||
type localizedError interface {
|
||||
Message(locale string) string
|
||||
}
|
||||
|
||||
func CursorData(results any, cursor string, hasMore bool) any {
|
||||
return cursorData{results: results, cursor: cursor, hasMore: hasMore}
|
||||
}
|
||||
@@ -29,22 +34,14 @@ func PageData(results any, paging *sqls.Paging) any {
|
||||
}
|
||||
|
||||
func WriteJSON(ctx *gin.Context, result any) {
|
||||
ctx.JSON(http.StatusOK, localizeJSONResult(ctx, buildJSONResult(result)))
|
||||
ctx.JSON(http.StatusOK, buildJSONResult(ctx, result))
|
||||
}
|
||||
|
||||
func WriteHttpStatusJSON(ctx *gin.Context, statusCode int, result any) {
|
||||
ctx.JSON(statusCode, localizeJSONResult(ctx, buildJSONResult(result)))
|
||||
ctx.JSON(statusCode, buildJSONResult(ctx, result))
|
||||
}
|
||||
|
||||
func localizeJSONResult(ctx *gin.Context, result *web.JsonResult) *web.JsonResult {
|
||||
if result == nil || result.Success || result.Message == "" {
|
||||
return result
|
||||
}
|
||||
result.Message = i18nx.TranslateKnownMessage(i18nx.Locale(ctx), result.Message)
|
||||
return result
|
||||
}
|
||||
|
||||
func buildJSONResult(result any) *web.JsonResult {
|
||||
func buildJSONResult(ctx *gin.Context, result any) *web.JsonResult {
|
||||
switch value := result.(type) {
|
||||
case nil:
|
||||
return web.JsonSuccess()
|
||||
@@ -56,6 +53,12 @@ func buildJSONResult(result any) *web.JsonResult {
|
||||
return web.JsonError(value)
|
||||
case web.CodeError:
|
||||
return web.JsonError(&value)
|
||||
case *errorsx.I18nError:
|
||||
return value.JsonResult(i18nx.Locale(ctx))
|
||||
case errorsx.I18nError:
|
||||
return value.JsonResult(i18nx.Locale(ctx))
|
||||
case localizedError:
|
||||
return web.JsonErrorMsg(value.Message(i18nx.Locale(ctx)))
|
||||
case error:
|
||||
return web.JsonError(value)
|
||||
case cursorData:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -68,12 +69,12 @@ func TestWriteJSONWrapsCommonResultTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteJSONLocalizesKnownErrorMessages(t *testing.T) {
|
||||
func TestWriteJSONLocalizesI18nErrors(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctx, recorder := testContext()
|
||||
i18nx.SetLocale(ctx, i18nx.LocaleEnUS)
|
||||
|
||||
WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
|
||||
WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0116"))
|
||||
|
||||
var got web.JsonResult
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &got); err != nil {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package i18nx
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
//go:embed locales/*.toml
|
||||
var messageFiles embed.FS
|
||||
|
||||
var (
|
||||
bundleOnce sync.Once
|
||||
bundle *i18n.Bundle
|
||||
)
|
||||
|
||||
func Bundle() *i18n.Bundle {
|
||||
bundleOnce.Do(func() {
|
||||
b := i18n.NewBundle(language.AmericanEnglish)
|
||||
b.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
for _, name := range []string{
|
||||
"locales/active.zh-CN.toml",
|
||||
"locales/active.en-US.toml",
|
||||
} {
|
||||
if _, err := b.LoadMessageFileFS(messageFiles, name); err != nil {
|
||||
slog.Error("load i18n message file failed", "file", name, "err", err)
|
||||
}
|
||||
}
|
||||
bundle = b
|
||||
})
|
||||
return bundle
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package i18nx
|
||||
|
||||
type Error struct {
|
||||
Key string
|
||||
Args []any
|
||||
Cause error
|
||||
}
|
||||
|
||||
func Errorf(key string, args ...any) *Error {
|
||||
err := &Error{Key: key, Args: args}
|
||||
for _, arg := range args {
|
||||
if cause, ok := arg.(error); ok {
|
||||
err.Cause = cause
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Message(LocaleZhCN)
|
||||
}
|
||||
|
||||
func (e *Error) Message(locale string) string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return Getf(locale, e.Key, e.Args...)
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Cause
|
||||
}
|
||||
@@ -63,111 +63,63 @@ func TestResolveLocalePrefersXLocale(t *testing.T) {
|
||||
func TestTranslateFallsBackToEnglish(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := TLocale(LocaleEnUS, "error.auth.expired", nil); got != "Your session has expired. Please sign in again." {
|
||||
if got := TLocale(LocaleEnUS, "error.auth.expired"); got != "Your session has expired. Please sign in again." {
|
||||
t.Fatalf("english translation = %q", got)
|
||||
}
|
||||
if got := TLocale("fr-FR", "error.auth.expired", nil); got != "Your session has expired. Please sign in again." {
|
||||
if got := TLocale("fr-FR", "error.auth.expired"); got != "Your session has expired. Please sign in again." {
|
||||
t.Fatalf("fallback translation = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateKnownMessageSupportsFormattedMessages(t *testing.T) {
|
||||
func TestGetfSupportsLocaleKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
want string
|
||||
name string
|
||||
locale string
|
||||
key string
|
||||
args []any
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "batch limit",
|
||||
message: "单次最多生成 31 条排班",
|
||||
want: "You can generate at most 31 schedule entries at a time.",
|
||||
name: "batch limit",
|
||||
locale: LocaleEnUS,
|
||||
key: "error.agentTeamSchedule.batchLimit",
|
||||
args: []any{31},
|
||||
want: "You can generate at most 31 schedule entries at a time.",
|
||||
},
|
||||
{
|
||||
name: "start date",
|
||||
message: "开始时间格式错误,请使用 yyyy-MM-dd",
|
||||
want: "Invalid start time format. Use yyyy-MM-dd.",
|
||||
name: "directory not found",
|
||||
locale: LocaleEnUS,
|
||||
key: "error.e0273",
|
||||
want: "Directory not found.",
|
||||
},
|
||||
{
|
||||
name: "end date time",
|
||||
message: "结束时间格式错误,请使用 yyyy-MM-dd HH:mm:ss 或 RFC3339",
|
||||
want: "Invalid end time format. Use yyyy-MM-dd HH:mm:ss or RFC3339.",
|
||||
name: "single knowledge base reference",
|
||||
locale: LocaleEnUS,
|
||||
key: "error.knowledgeBase.referencedByAgent",
|
||||
args: []any{"SalesBot"},
|
||||
want: "This knowledge base is referenced by AI Agent \"SalesBot\". Remove the binding first.",
|
||||
},
|
||||
{
|
||||
name: "oidc issuer config",
|
||||
message: "OIDC issuer 未配置",
|
||||
want: "OIDC issuer is not configured.",
|
||||
name: "multiple knowledge base references",
|
||||
locale: LocaleEnUS,
|
||||
key: "error.knowledgeBase.referencedByAgents",
|
||||
args: []any{3},
|
||||
want: "This knowledge base is referenced by 3 AI Agents. Remove the bindings first.",
|
||||
},
|
||||
{
|
||||
name: "oidc login result",
|
||||
message: "登录结果不能为空",
|
||||
want: "Sign-in result is required.",
|
||||
name: "faq import duplicated question",
|
||||
locale: LocaleEnUS,
|
||||
key: "error.knowledgeFAQImport.duplicateQuestionInFile",
|
||||
args: []any{12},
|
||||
want: "The standard question is duplicated in the same file. It first appeared on row 12.",
|
||||
},
|
||||
{
|
||||
name: "directory not found",
|
||||
message: "目录不存在",
|
||||
want: "Directory not found.",
|
||||
},
|
||||
{
|
||||
name: "directory has docs",
|
||||
message: "该目录下存在文档,无法删除",
|
||||
want: "This directory contains documents and cannot be deleted.",
|
||||
},
|
||||
{
|
||||
name: "move documents",
|
||||
message: "请选择要移动的文档",
|
||||
want: "Select documents to move.",
|
||||
},
|
||||
{
|
||||
name: "move faqs across knowledge bases",
|
||||
message: "只能移动当前知识库下的FAQ",
|
||||
want: "Only FAQs in the current knowledge base can be moved.",
|
||||
},
|
||||
{
|
||||
name: "single knowledge base reference",
|
||||
message: "知识库已被 AI Agent「SalesBot」引用,请先解除绑定",
|
||||
want: "This knowledge base is referenced by AI Agent \"SalesBot\". Remove the binding first.",
|
||||
},
|
||||
{
|
||||
name: "multiple knowledge base references",
|
||||
message: "知识库已被 3 个 AI Agent 引用,请先解除绑定",
|
||||
want: "This knowledge base is referenced by 3 AI Agents. Remove the bindings first.",
|
||||
},
|
||||
{
|
||||
name: "schedule conflict range",
|
||||
message: "该客服组在 2026-06-02 09:00:00 至 2026-06-02 10:00:00 已存在排班",
|
||||
want: "This agent team already has a schedule from 2026-06-02 09:00:00 to 2026-06-02 10:00:00.",
|
||||
},
|
||||
{
|
||||
name: "faq import duplicated question",
|
||||
message: "同一文件中标准问题重复,首次出现于第12行",
|
||||
want: "The standard question is duplicated in the same file. It first appeared on row 12.",
|
||||
},
|
||||
{
|
||||
name: "faq import existing question",
|
||||
message: "标准问题已存在,已跳过",
|
||||
want: "The standard question already exists and was skipped.",
|
||||
},
|
||||
{
|
||||
name: "required param",
|
||||
message: "参数:name不能为空",
|
||||
want: "Parameter \"name\" is required.",
|
||||
},
|
||||
{
|
||||
name: "mcp call failed",
|
||||
message: "调用 MCP 工具失败: timeout",
|
||||
want: "Failed to call MCP tool: timeout",
|
||||
},
|
||||
{
|
||||
name: "wxwork unsupported outbound",
|
||||
message: "当前暂不支持企业微信下行消息类型: voice",
|
||||
want: "The current WeCom outbound message type is not supported yet: voice",
|
||||
},
|
||||
{
|
||||
name: "wxwork callback missing",
|
||||
message: "企业微信登录回调地址未配置",
|
||||
want: "WeCom sign-in callback URL is not configured.",
|
||||
name: "zh keeps chinese value",
|
||||
locale: LocaleZhCN,
|
||||
key: "error.e0273",
|
||||
want: "目录不存在",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -175,19 +127,18 @@ func TestTranslateKnownMessageSupportsFormattedMessages(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := TranslateKnownMessage(LocaleEnUS, tt.message); got != tt.want {
|
||||
t.Fatalf("TranslateKnownMessage() = %q, want %q", got, tt.want)
|
||||
if got := Getf(tt.locale, tt.key, tt.args...); got != tt.want {
|
||||
t.Fatalf("Getf() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateKnownMessageKeepsChineseLocale(t *testing.T) {
|
||||
func TestGetfFallsBackToKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
message := "目录不存在"
|
||||
if got := TranslateKnownMessage(LocaleZhCN, message); got != message {
|
||||
t.Fatalf("TranslateKnownMessage() = %q, want %q", got, message)
|
||||
if got := Getf(LocaleEnUS, "missing.key"); got != "missing.key" {
|
||||
t.Fatalf("missing translation = %q, want %q", got, "missing.key")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,440 +0,0 @@
|
||||
package i18nx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
maxScheduleBatchMessagePattern = regexp.MustCompile(`^单次最多生成 ([0-9]+) 条排班$`)
|
||||
knowledgeBaseReferencedByAgentPattern = regexp.MustCompile(`^知识库已被 AI Agent「(.+)」引用,请先解除绑定$`)
|
||||
knowledgeBaseReferencedByAgentsPattern = regexp.MustCompile(`^知识库已被 ([0-9]+) 个 AI Agent 引用,请先解除绑定$`)
|
||||
agentTeamScheduleConflictPattern = regexp.MustCompile(`^该客服组在 (.+) 至 (.+) 已存在排班$`)
|
||||
knowledgeFAQImportDuplicatePattern = regexp.MustCompile(`^同一文件中标准问题重复,首次出现于第([0-9]+)行$`)
|
||||
requiredParamMessagePattern = regexp.MustCompile(`^参数:(.+)不能为空$`)
|
||||
mcpListToolsMessagePattern = regexp.MustCompile(`^列出 MCP 工具失败: (.+)$`)
|
||||
mcpCallToolMessagePattern = regexp.MustCompile(`^调用 MCP 工具失败: (.+)$`)
|
||||
mcpConnectServerMessagePattern = regexp.MustCompile(`^连接 MCP Server 失败: (.+)$`)
|
||||
wxworkUnsupportedOutboundPattern = regexp.MustCompile(`^不支持的企业微信下行消息类型: (.+)$`)
|
||||
wxworkUnsupportedCurrentOutboundPattern = regexp.MustCompile(`^当前暂不支持企业微信下行消息类型: (.+)$`)
|
||||
)
|
||||
|
||||
var knownMessages = map[string]map[string]string{
|
||||
LocaleEnUS: {
|
||||
"未登录或登录已过期": "Your session has expired. Please sign in again.",
|
||||
"无权限执行该操作": "You do not have permission to perform this action.",
|
||||
"无权限操作": "You do not have permission to perform this action.",
|
||||
"参数不合法": "Invalid request parameters.",
|
||||
"路径参数错误": "Invalid path parameter.",
|
||||
"状态值不合法": "Invalid status value.",
|
||||
"用户名和密码不能为空": "Enter both username and password.",
|
||||
"用户名或密码错误": "The username or password is incorrect.",
|
||||
"登录失败次数过多,请稍后再试": "Too many failed sign-in attempts. Please try again later.",
|
||||
"用户不存在": "User not found.",
|
||||
"用户不存在或已被禁用": "The user does not exist or has been disabled.",
|
||||
"用户名不能为空": "Enter a username.",
|
||||
"用户名已存在": "This username is already in use.",
|
||||
"手机号已存在": "This phone number is already in use.",
|
||||
"邮箱已存在": "This email address is already in use.",
|
||||
"新密码不能为空": "Enter a new password.",
|
||||
"禁用角色不允许分配": "Disabled roles cannot be assigned.",
|
||||
"角色不存在": "Role not found.",
|
||||
"角色名称和编码不能为空": "Enter both role name and role code.",
|
||||
"角色编码已存在": "This role code is already in use.",
|
||||
"系统内置角色不允许删除": "Built-in system roles cannot be deleted.",
|
||||
"角色已被用户使用,无法删除": "This role is assigned to users and cannot be deleted.",
|
||||
"权限不存在": "Permission not found.",
|
||||
"会话不存在": "Conversation not found.",
|
||||
"会话筛选项不合法": "Invalid conversation filter.",
|
||||
"只有待接入会话允许分配": "Only queued conversations can be assigned.",
|
||||
"只有待接入会话允许自动分配": "Only queued conversations can be auto-assigned.",
|
||||
"当前会话已分配客服": "This conversation has already been assigned.",
|
||||
"当前暂不在人工客服服务时间内": "Human support is currently outside service hours.",
|
||||
"目标客服不能为空": "Select a target agent.",
|
||||
"目标客服不存在": "Target agent not found.",
|
||||
"无权转接该会话": "You do not have permission to transfer this conversation.",
|
||||
"只有处理中会话允许转接": "Only active conversations can be transferred.",
|
||||
"当前会话未分配客服": "This conversation is not assigned to an agent.",
|
||||
"目标客服不能与当前指派人相同": "The target agent must be different from the current assignee.",
|
||||
"无权访问该会话": "You do not have permission to access this conversation.",
|
||||
"当前状态不允许关闭会话": "This conversation cannot be closed in its current status.",
|
||||
"关闭原因不能为空": "Enter a close reason.",
|
||||
"无权关闭该会话": "You do not have permission to close this conversation.",
|
||||
"已关闭的会话无法关联客户": "Closed conversations cannot be linked to customers.",
|
||||
"无权限关联该会话": "You do not have permission to link this conversation.",
|
||||
"外部用户标识不能为空": "External user ID is required.",
|
||||
"接入渠道未初始化": "The channel is not initialized.",
|
||||
"接入渠道不存在或已停用": "The channel does not exist or has been disabled.",
|
||||
"接入渠道异常": "Channel error.",
|
||||
"channel not found": "Channel not found.",
|
||||
"该渠道不支持开放客服配置": "This channel does not support public support configuration.",
|
||||
"外部身份未初始化": "External identity is not initialized.",
|
||||
"conversationId不能为空": "Conversation ID is required.",
|
||||
"请选择上传图片": "Choose an image to upload.",
|
||||
"仅支持上传图片文件": "Only image files are supported.",
|
||||
"请选择上传附件": "Choose an attachment to upload.",
|
||||
"请选择上传文件": "Choose a file to upload.",
|
||||
"文件不存在": "File not found.",
|
||||
"文件不可访问": "This file cannot be accessed.",
|
||||
"上传文件超过大小限制": "The uploaded file exceeds the size limit.",
|
||||
"图片资源不存在": "Image asset not found.",
|
||||
"当前暂不支持该存储类型的文件读取": "Reading files from this storage provider is not supported yet.",
|
||||
"客户不存在": "Customer not found.",
|
||||
"客户名称不能为空": "Enter a customer name.",
|
||||
"所属公司不存在": "Company not found.",
|
||||
"公司不存在": "Company not found.",
|
||||
"公司名称不能为空": "Enter a company name.",
|
||||
"公司名称已存在": "This company name is already in use.",
|
||||
"工单不存在": "Ticket not found.",
|
||||
"工单标题不能为空": "Enter a ticket title.",
|
||||
"工单描述不能为空": "Enter a ticket description.",
|
||||
"工单来源不合法": "Invalid ticket source.",
|
||||
"工单状态不合法": "Invalid ticket status.",
|
||||
"处理进展不能为空": "Enter a progress update.",
|
||||
"负责人不存在": "Assignee not found.",
|
||||
"会话与客户不匹配": "The conversation does not belong to this customer.",
|
||||
"视图名称不能为空": "Enter a view name.",
|
||||
"视图筛选条件格式不正确": "The view filter format is invalid.",
|
||||
"视图不存在": "View not found.",
|
||||
"标签不存在": "Tag not found.",
|
||||
"标签名称不能为空": "Enter a tag name.",
|
||||
"父标签不存在": "Parent tag not found.",
|
||||
"同级下已存在相同名称的标签": "A tag with this name already exists at the same level.",
|
||||
"不能将标签设为自己的子标签": "A tag cannot be moved under itself.",
|
||||
"该标签下存在子标签,无法删除": "This tag has child tags and cannot be deleted.",
|
||||
"该标签已关联会话,无法删除": "This tag is linked to conversations and cannot be deleted.",
|
||||
"该标签已关联工单,无法删除": "This tag is linked to tickets and cannot be deleted.",
|
||||
"快捷回复不存在": "Quick reply not found.",
|
||||
"客服档案不存在": "Agent profile not found.",
|
||||
"请选择关联用户": "Select a user to link.",
|
||||
"关联用户不存在": "Linked user not found.",
|
||||
"请选择所属客服组": "Select an agent team.",
|
||||
"所属客服组不存在": "Agent team not found.",
|
||||
"客服工号和展示名不能为空": "Enter both agent ID and display name.",
|
||||
"该用户已存在客服档案": "This user already has an agent profile.",
|
||||
"客服工号已存在": "This agent ID is already in use.",
|
||||
"客服状态不合法": "Invalid agent status.",
|
||||
"最大并发接待数不能小于 0": "Maximum concurrent conversations cannot be less than 0.",
|
||||
"客服组不存在": "Agent team not found.",
|
||||
"客服组排班不存在": "Agent team schedule not found.",
|
||||
"知识库不存在": "Knowledge base not found.",
|
||||
"FAQ不存在": "FAQ not found.",
|
||||
"FAQ知识库不支持文档": "FAQ knowledge bases do not support documents.",
|
||||
"文档不存在": "Document not found.",
|
||||
"内容类型不支持": "Unsupported content type.",
|
||||
"问题不能为空": "Enter a question.",
|
||||
"答案不能为空": "Enter an answer.",
|
||||
"相似问格式不合法": "Invalid similar-question format.",
|
||||
"当前知识库不是FAQ知识库": "This knowledge base is not an FAQ knowledge base.",
|
||||
"documentId或faqId不能为空": "Document ID or FAQ ID is required.",
|
||||
"检索日志不存在": "Retrieval log not found.",
|
||||
"indexStatus参数不合法": "Invalid index status.",
|
||||
"AI配置不存在": "AI configuration not found.",
|
||||
"启用中的AI配置不允许删除": "Enabled AI configurations cannot be deleted.",
|
||||
"配置名称不能为空": "Enter a configuration name.",
|
||||
"供应商不能为空": "Select a provider.",
|
||||
"基础地址不能为空": "Enter a base URL.",
|
||||
"模型类型不能为空": "Select a model type.",
|
||||
"模型名称不能为空": "Enter a model name.",
|
||||
"AI Agent 不存在": "AI Agent not found.",
|
||||
"AI Agent不存在或未启用": "AI Agent not found or not enabled.",
|
||||
"AI Agent 不存在或已停用": "AI Agent not found or disabled.",
|
||||
"AI Agent关联的AI配置不存在": "The AI configuration linked to this AI Agent does not exist.",
|
||||
"CheckPoint 不存在": "Checkpoint not found.",
|
||||
"CheckPoint 与 AI Agent 不匹配": "The checkpoint does not belong to this AI Agent.",
|
||||
"会话与 AI Agent 不匹配": "The conversation does not belong to this AI Agent.",
|
||||
"Skill 不存在或未启用": "Skill not found or not enabled.",
|
||||
"Skill 不存在": "Skill not found.",
|
||||
"Skill ID 不合法": "Invalid Skill ID.",
|
||||
"Skill 编码已存在": "This Skill code is already in use.",
|
||||
"Skill 编码不能为空": "Enter a Skill code.",
|
||||
"Skill 名称不能为空": "Enter a Skill name.",
|
||||
"技能说明不能为空": "Enter a Skill description.",
|
||||
"JSON 数组格式不合法": "Invalid JSON array format.",
|
||||
"已删除的 Skill 不能直接修改状态,请先恢复": "Restore this deleted Skill before changing its status.",
|
||||
"请使用删除接口处理删除状态": "Use the delete endpoint to mark a Skill as deleted.",
|
||||
"仅已删除的 Skill 支持恢复": "Only deleted Skills can be restored.",
|
||||
"MCP未启用": "MCP is not enabled.",
|
||||
"serverCode不能为空": "Server code is required.",
|
||||
"MCP endpoint不能为空": "MCP endpoint is required.",
|
||||
"MCP服务配置不存在": "MCP server configuration not found.",
|
||||
"MCP服务未启用": "MCP server is not enabled.",
|
||||
"toolName不能为空": "Tool name is required.",
|
||||
"toolCode不能为空": "Tool code is required.",
|
||||
"toolCode格式不合法": "Invalid tool code format.",
|
||||
"toolCode 绑定的 MCP 服务不存在或未启用": "The MCP server bound to this tool code does not exist or is not enabled.",
|
||||
"文本内容不能为空": "Text content is required.",
|
||||
"文本列表不能为空": "Text list is required.",
|
||||
"密码长度不合法": "Invalid password length.",
|
||||
"AI Agent 不存在或未启用": "AI Agent not found or not enabled.",
|
||||
"AI Agent 名称不能为空": "Enter an AI Agent name.",
|
||||
"AI Agent 名称已存在": "This AI Agent name is already in use.",
|
||||
"AI 配置不存在": "AI configuration not found.",
|
||||
"AI 配置不能为空": "Select an AI configuration.",
|
||||
"AI 配置未启用": "The AI configuration is not enabled.",
|
||||
"Agent 运行日志不存在": "Agent run log not found.",
|
||||
"Direct Tool 的 toolCode 与 serverCode 不一致": "Direct Tool toolCode does not match serverCode.",
|
||||
"Direct Tool 的 toolCode 与 toolName 不一致": "Direct Tool toolCode does not match toolName.",
|
||||
"Direct Tool 的 toolCode 格式不合法": "Invalid Direct Tool toolCode format.",
|
||||
"Direct Tool 的 toolCode、serverCode 和 toolName 不能为空": "Direct Tool toolCode, serverCode, and toolName are required.",
|
||||
"Direct Tools 仅允许配置 MCP 工具": "Direct Tools can only contain MCP tools.",
|
||||
"Direct Tools 配置格式不合法": "Invalid Direct Tools configuration format.",
|
||||
"Graph Tools 仅允许配置 Graph Tool": "Graph Tools can only contain Graph tools.",
|
||||
"Graph Tools 配置格式不合法": "Invalid Graph Tools configuration format.",
|
||||
"HTML消息中的图片必须使用已上传文件": "Images in HTML messages must use uploaded files.",
|
||||
"OIDC id_token 不存在": "OIDC id_token is missing.",
|
||||
"OIDC clientId 未配置": "OIDC clientId is not configured.",
|
||||
"OIDC clientSecret 未配置": "OIDC clientSecret is not configured.",
|
||||
"OIDC issuer 未配置": "OIDC issuer is not configured.",
|
||||
"OIDC redirectUrl 未配置": "OIDC redirectUrl is not configured.",
|
||||
"OIDC 授权 code 不能为空": "OIDC authorization code is required.",
|
||||
"OIDC 用户信息不存在": "OIDC user information not found.",
|
||||
"OIDC 用户标识不存在": "OIDC user identifier not found.",
|
||||
"OIDC 登录密钥未配置": "OIDC sign-in secret is not configured.",
|
||||
"OIDC 登录未启用": "OIDC sign-in is not enabled.",
|
||||
"OIDC 登录状态无效或已过期": "OIDC sign-in state is invalid or has expired.",
|
||||
"OIDC 账号绑定的系统用户不存在": "The system user linked to this OIDC account does not exist.",
|
||||
"OSS accessKeyId 未配置": "OSS accessKeyId is not configured.",
|
||||
"OSS accessKeySecret 未配置": "OSS accessKeySecret is not configured.",
|
||||
"OSS bucket 未配置": "OSS bucket is not configured.",
|
||||
"OSS endpoint 未配置": "OSS endpoint is not configured.",
|
||||
"Skill 未启用": "Skill is not enabled.",
|
||||
"Web渠道配置 position 只能为 left 或 right": "Web channel position must be left or right.",
|
||||
"Web渠道配置不合法": "Invalid web channel configuration.",
|
||||
"aiAgentId不能为空": "AI Agent ID is required.",
|
||||
"checkPointId不能为空": "Checkpoint ID is required.",
|
||||
"customerId 必填": "Customer ID is required.",
|
||||
"openKfID不能为空": "openKfID is required.",
|
||||
"openKfId 已被其他渠道使用": "This openKfId is already used by another channel.",
|
||||
"skillCode不能为空": "Skill code is required.",
|
||||
"ticket 不能为空": "Ticket is required.",
|
||||
"userMessage不能为空": "User message is required.",
|
||||
"登录结果不能为空": "Sign-in result is required.",
|
||||
"不支持的发送人类型": "Unsupported sender type.",
|
||||
"不支持的已读操作类型": "Unsupported read operation type.",
|
||||
"不支持的文件存储类型": "Unsupported file storage type.",
|
||||
"不能添加或修改历史日期的排班": "Schedules cannot be added or changed for past dates.",
|
||||
"事务上下文不能为空": "Transaction context is required.",
|
||||
"仅允许撤回自己发送的消息": "You can only recall messages you sent.",
|
||||
"仅支持撤回客服消息": "Only agent messages can be recalled.",
|
||||
"仅能指定一条主联系方式": "Only one primary contact method can be set.",
|
||||
"企业微信 openKfID 不能为空": "WeCom openKfID is required.",
|
||||
"企业微信媒体ID不能为空": "WeCom media ID is required.",
|
||||
"企业微信客户ID不能为空": "WeCom customer ID is required.",
|
||||
"企业微信手机号已被系统用户占用": "This WeCom phone number is already used by a system user.",
|
||||
"企业微信接入渠道未绑定AI Agent": "The WeCom channel is not linked to an AI Agent.",
|
||||
"企业微信接入渠道绑定的AI Agent不存在或已禁用": "The AI Agent linked to this WeCom channel does not exist or has been disabled.",
|
||||
"企业微信未启用或配置不完整": "WeCom is not enabled or its configuration is incomplete.",
|
||||
"企业微信消息ID不能为空": "WeCom message ID is required.",
|
||||
"企业微信渠道配置不合法": "Invalid WeCom channel configuration.",
|
||||
"企业微信渠道配置缺少 openKfId": "WeCom channel configuration is missing openKfId.",
|
||||
"企业微信登录状态无效": "WeCom sign-in state is invalid.",
|
||||
"企业微信登录回调地址未配置": "WeCom sign-in callback URL is not configured.",
|
||||
"企业微信 AgentID 未配置": "WeCom AgentID is not configured.",
|
||||
"微信授权 code 不能为空": "WeChat authorization code is required.",
|
||||
"当前登录身份不是企业内部成员": "The current sign-in identity is not an internal enterprise member.",
|
||||
"企业微信返回的消息ID为空": "WeCom returned an empty message ID.",
|
||||
"企业微信返回的图片 media_id 为空": "WeCom returned an empty image media_id.",
|
||||
"图片消息缺少 assetId": "Image message is missing assetId.",
|
||||
"平台消息不存在": "Platform message not found.",
|
||||
"文本消息内容为空": "Text message content is empty.",
|
||||
"HTML 消息内容为空": "HTML message content is empty.",
|
||||
"企业微信用户ID已被系统用户名占用": "This WeCom user ID is already used as a system username.",
|
||||
"企业微信用户ID获取失败": "Failed to get the WeCom user ID.",
|
||||
"企业微信用户信息不存在": "WeCom user information not found.",
|
||||
"企业微信登录密钥未配置": "WeCom sign-in secret is not configured.",
|
||||
"企业微信登录未启用": "WeCom sign-in is not enabled.",
|
||||
"企业微信登录状态无效或已过期": "WeCom sign-in state is invalid or has expired.",
|
||||
"企业微信账号绑定的系统用户不存在": "The system user linked to this WeCom account does not exist.",
|
||||
"企业微信邮箱已被系统用户占用": "This WeCom email address is already used by a system user.",
|
||||
"会话已关闭": "This conversation is closed.",
|
||||
"会话未分配客服,暂不允许发送消息": "This conversation has not been assigned to an agent, so messages cannot be sent yet.",
|
||||
"兜底策略不合法": "Invalid fallback policy.",
|
||||
"分块策略不支持": "Unsupported chunking strategy.",
|
||||
"创建会话失败": "Failed to create the conversation.",
|
||||
"单条排班记录不能跨天": "A single schedule entry cannot span multiple days.",
|
||||
"只有待接入未分配会话允许自动分配": "Only queued unassigned conversations can be auto-assigned.",
|
||||
"回复超时秒数不能小于 0": "Reply timeout seconds cannot be less than 0.",
|
||||
"存在冲突排班,请先处理冲突": "There are conflicting schedules. Resolve the conflicts first.",
|
||||
"存在无效工单标签": "Some ticket tags are invalid.",
|
||||
"存在未启用的工单标签": "Some ticket tags are disabled.",
|
||||
"客服会话不能为空": "Support session is required.",
|
||||
"客服会话参数不完整": "Support session parameters are incomplete.",
|
||||
"客服会话密钥未配置": "Support session secret is not configured.",
|
||||
"客服会话已过期": "The support session has expired.",
|
||||
"客服会话校验失败": "Support session verification failed.",
|
||||
"客服组下仍有关联 AI Agent,无法删除": "This agent team is linked to AI Agents and cannot be deleted.",
|
||||
"客服组下仍有关联客服档案,无法删除": "This agent team has linked agent profiles and cannot be deleted.",
|
||||
"客服组下仍有关联组排班,无法删除": "This agent team has schedules and cannot be deleted.",
|
||||
"客服组名称不能为空": "Enter an agent team name.",
|
||||
"客服组名称已存在": "This agent team name is already in use.",
|
||||
"客服组未启用": "Agent team is not enabled.",
|
||||
"客服组状态不合法": "Invalid agent team status.",
|
||||
"已有接入渠道绑定该 AI Agent,无法删除": "This AI Agent is linked to channels and cannot be deleted.",
|
||||
"当前 OIDC 绑定已停用": "The current OIDC binding has been disabled.",
|
||||
"当前企业微信绑定已停用": "The current WeCom binding has been disabled.",
|
||||
"当前会话不处于 AI 接待状态": "This conversation is not currently handled by AI.",
|
||||
"当前会话已分配给其他客服": "This conversation has been assigned to another agent.",
|
||||
"当前会话已由人工客服接管": "This conversation has already been taken over by a human agent.",
|
||||
"当前渠道不支持用户 JWT Secret": "This channel does not support a user JWT secret.",
|
||||
"当前系统账号已被禁用": "The current system account has been disabled.",
|
||||
"微信公众号渠道配置不合法": "Invalid WeChat Official Account channel configuration.",
|
||||
"接入渠道不存在": "Channel not found.",
|
||||
"接收人不能为空": "Recipient is required.",
|
||||
"文档知识库不能使用FAQ分块策略": "Document knowledge bases cannot use the FAQ chunking strategy.",
|
||||
"目标工具未被当前会话授权": "The target tool is not authorized for the current conversation.",
|
||||
"tool_search 只支持调用 MCP toolCode": "tool_search only supports calling MCP toolCode.",
|
||||
"时间格式错误": "Invalid time format.",
|
||||
"星期必须在 1 到 7 之间": "Weekday must be between 1 and 7.",
|
||||
"服务模式不合法": "Invalid service mode.",
|
||||
"未找到匹配的企业微信接入渠道": "No matching WeCom channel was found.",
|
||||
"未生成任何排班": "No schedules were generated.",
|
||||
"未配置可用的 AI 配置": "No available AI configuration is configured.",
|
||||
"未配置可用的 Embedding 模型": "No available embedding model is configured.",
|
||||
"标题和内容不能为空": "Enter both title and content.",
|
||||
"消息不存在": "Message not found.",
|
||||
"消息内容不能为空": "Message content is required.",
|
||||
"消息已撤回": "This message has been recalled.",
|
||||
"渠道名称不能为空": "Enter a channel name.",
|
||||
"渠道标识已存在": "This channel code is already in use.",
|
||||
"渠道状态不合法": "Invalid channel status.",
|
||||
"渠道类型不合法": "Invalid channel type.",
|
||||
"用户名称不能为空": "User name is required.",
|
||||
"用户标识不能为空": "User ID is required.",
|
||||
"用户身份不能为空": "User identity is required.",
|
||||
"用户身份已过期": "User identity has expired.",
|
||||
"用户身份校验失败": "User identity verification failed.",
|
||||
"用户身份校验未配置": "User identity verification is not configured.",
|
||||
"登录票据无效或已过期": "The sign-in ticket is invalid or has expired.",
|
||||
"知识库下存在FAQ,无法删除": "This knowledge base contains FAQs and cannot be deleted.",
|
||||
"知识库下存在文档,无法删除": "This knowledge base contains documents and cannot be deleted.",
|
||||
"知识库不能为空": "Knowledge base is required.",
|
||||
"知识库未启用": "Knowledge base is not enabled.",
|
||||
"知识库类型不支持": "Unsupported knowledge base type.",
|
||||
"目录名称不能为空": "Directory name is required.",
|
||||
"目录不存在": "Directory not found.",
|
||||
"目录不能移动到其他知识库": "Directories cannot be moved to another knowledge base.",
|
||||
"存在子目录的目录不能移动到二级目录": "A directory with subdirectories cannot be moved to the second level.",
|
||||
"同级下已存在相同名称的目录": "A directory with this name already exists at the same level.",
|
||||
"该目录下存在子目录,无法删除": "This directory contains subdirectories and cannot be deleted.",
|
||||
"该目录下存在文档,无法删除": "This directory contains documents and cannot be deleted.",
|
||||
"该目录下存在FAQ,无法删除": "This directory contains FAQs and cannot be deleted.",
|
||||
"只能调整同知识库同级目录排序": "Only directories in the same knowledge base and same level can be reordered.",
|
||||
"知识库目录不存在": "Knowledge base directory not found.",
|
||||
"知识库目录不属于当前知识库": "This directory does not belong to the current knowledge base.",
|
||||
"知识库目录不可用": "This knowledge base directory is unavailable.",
|
||||
"不能将目录设为自己的子目录": "A directory cannot be moved under itself.",
|
||||
"父目录不存在": "Parent directory not found.",
|
||||
"父目录不属于当前知识库": "The parent directory does not belong to the current knowledge base.",
|
||||
"知识库目录最多支持二级": "Knowledge base directories support at most two levels.",
|
||||
"请选择要移动的文档": "Select documents to move.",
|
||||
"只能移动当前知识库下的文档": "Only documents in the current knowledge base can be moved.",
|
||||
"请选择要删除的文档": "Select documents to delete.",
|
||||
"请选择要移动的FAQ": "Select FAQs to move.",
|
||||
"只能移动当前知识库下的FAQ": "Only FAQs in the current knowledge base can be moved.",
|
||||
"请选择要删除的FAQ": "Select FAQs to delete.",
|
||||
"导入模式不合法": "Invalid import mode.",
|
||||
"请选择导入文件": "Choose a file to import.",
|
||||
"仅支持.xlsx文件": "Only .xlsx files are supported.",
|
||||
"导入文件读取失败": "Failed to read the import file.",
|
||||
"Excel文件解析失败": "Failed to parse the Excel file.",
|
||||
"Excel文件为空": "The Excel file is empty.",
|
||||
"Excel文件读取失败": "Failed to read the Excel file.",
|
||||
"缺少标准问题列": "The standard question column is missing.",
|
||||
"缺少答案列": "The answer column is missing.",
|
||||
"标准问题已存在,已跳过": "The standard question already exists and was skipped.",
|
||||
"问题不能超过500字": "The question cannot exceed 500 characters.",
|
||||
"组长用户不存在": "Team lead user not found.",
|
||||
"结束日期必须晚于或等于开始日期": "End date must be later than or equal to start date.",
|
||||
"结束时间必须晚于开始时间": "End time must be later than start time.",
|
||||
"联系方式不存在": "Contact method not found.",
|
||||
"联系方式不能为空": "Contact method is required.",
|
||||
"联系方式类型不合法": "Invalid contact method type.",
|
||||
"该客服组在所选时间段已存在排班": "This agent team already has a schedule in the selected time period.",
|
||||
"该联系方式已存在": "This contact method already exists.",
|
||||
"请至少选择一个知识库": "Select at least one knowledge base.",
|
||||
"请选择 AI Agent": "Select an AI Agent.",
|
||||
"请选择客服组": "Select an agent team.",
|
||||
"请选择星期": "Select a weekday.",
|
||||
"转人工模式不合法": "Invalid human handoff mode.",
|
||||
"通知不存在": "Notification not found.",
|
||||
"附件不存在": "Attachment not found.",
|
||||
"附件尚未上传完成": "The attachment has not finished uploading.",
|
||||
"附件消息 payload 格式错误": "Invalid attachment message payload format.",
|
||||
"附件消息缺少 assetId": "Attachment message is missing assetId.",
|
||||
"附件消息缺少 payload": "Attachment message is missing payload.",
|
||||
"默认客服组待接入池模式必须至少选择一个客服组": "Default team queue mode requires at least one agent team.",
|
||||
},
|
||||
}
|
||||
|
||||
func TranslateKnownMessage(locale string, message string) string {
|
||||
normalized := NormalizeLocale(locale)
|
||||
if normalized == LocaleZhCN {
|
||||
return message
|
||||
}
|
||||
if translations, ok := knownMessages[normalized]; ok {
|
||||
if translated, exists := translations[message]; exists {
|
||||
return translated
|
||||
}
|
||||
}
|
||||
if translated, ok := translateKnownPattern(normalized, message); ok {
|
||||
return translated
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func translateKnownPattern(locale string, message string) (string, bool) {
|
||||
if locale != LocaleEnUS {
|
||||
return "", false
|
||||
}
|
||||
if matches := maxScheduleBatchMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("You can generate at most %s schedule entries at a time.", matches[1]), true
|
||||
}
|
||||
if matches := knowledgeBaseReferencedByAgentPattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("This knowledge base is referenced by AI Agent %q. Remove the binding first.", matches[1]), true
|
||||
}
|
||||
if matches := knowledgeBaseReferencedByAgentsPattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("This knowledge base is referenced by %s AI Agents. Remove the bindings first.", matches[1]), true
|
||||
}
|
||||
if matches := agentTeamScheduleConflictPattern.FindStringSubmatch(message); len(matches) == 3 {
|
||||
return fmt.Sprintf("This agent team already has a schedule from %s to %s.", matches[1], matches[2]), true
|
||||
}
|
||||
if matches := knowledgeFAQImportDuplicatePattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("The standard question is duplicated in the same file. It first appeared on row %s.", matches[1]), true
|
||||
}
|
||||
if matches := requiredParamMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("Parameter %q is required.", matches[1]), true
|
||||
}
|
||||
if matches := mcpListToolsMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("Failed to list MCP tools: %s", matches[1]), true
|
||||
}
|
||||
if matches := mcpCallToolMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("Failed to call MCP tool: %s", matches[1]), true
|
||||
}
|
||||
if matches := mcpConnectServerMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("Failed to connect to MCP Server: %s", matches[1]), true
|
||||
}
|
||||
if matches := wxworkUnsupportedOutboundPattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("Unsupported WeCom outbound message type: %s", matches[1]), true
|
||||
}
|
||||
if matches := wxworkUnsupportedCurrentOutboundPattern.FindStringSubmatch(message); len(matches) == 2 {
|
||||
return fmt.Sprintf("The current WeCom outbound message type is not supported yet: %s", matches[1]), true
|
||||
}
|
||||
if strings.HasPrefix(message, "开始时间格式错误,请使用 ") {
|
||||
return translateTimeFormatMessage(message, "开始时间格式错误,请使用 ", "start time")
|
||||
}
|
||||
if strings.HasPrefix(message, "结束时间格式错误,请使用 ") {
|
||||
return translateTimeFormatMessage(message, "结束时间格式错误,请使用 ", "end time")
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func translateTimeFormatMessage(message string, prefix string, field string) (string, bool) {
|
||||
format := strings.TrimPrefix(message, prefix)
|
||||
if format == message || format == "" {
|
||||
return "", false
|
||||
}
|
||||
format = strings.ReplaceAll(format, " 或 ", " or ")
|
||||
return fmt.Sprintf("Invalid %s format. Use %s.", field, format), true
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package i18nx
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed locales/*.yml
|
||||
var localeFiles embed.FS
|
||||
|
||||
var (
|
||||
messagesOnce sync.Once
|
||||
messages map[string]map[string]string
|
||||
)
|
||||
|
||||
func T(ctx *gin.Context, key string, args ...any) string {
|
||||
if ctx == nil {
|
||||
return Getf(DefaultLocale, key, args...)
|
||||
}
|
||||
return Getf(Locale(ctx), key, args...)
|
||||
}
|
||||
|
||||
func TLocale(locale string, key string, args ...any) string {
|
||||
return Getf(locale, key, args...)
|
||||
}
|
||||
|
||||
func Get(key string) string {
|
||||
return Getf(DefaultLocale, key)
|
||||
}
|
||||
|
||||
func Getf(locale string, key string, args ...any) string {
|
||||
format := lookup(NormalizeLocale(locale), key)
|
||||
if len(args) == 0 {
|
||||
return format
|
||||
}
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func lookup(locale string, key string) string {
|
||||
loadMessages()
|
||||
if value := lookupLocale(locale, key); value != "" {
|
||||
return value
|
||||
}
|
||||
if locale != DefaultLocale {
|
||||
if value := lookupLocale(DefaultLocale, key); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
slog.Warn("translation key not found", "key", key, "locale", locale)
|
||||
return key
|
||||
}
|
||||
|
||||
func lookupLocale(locale string, key string) string {
|
||||
if values, ok := messages[locale]; ok {
|
||||
return values[key]
|
||||
}
|
||||
slog.Warn("locale not found", "locale", locale)
|
||||
return ""
|
||||
}
|
||||
|
||||
func loadMessages() {
|
||||
messagesOnce.Do(func() {
|
||||
loaded := make(map[string]map[string]string)
|
||||
entries, err := localeFiles.ReadDir("locales")
|
||||
if err != nil {
|
||||
slog.Error("read locale files failed", "err", err)
|
||||
messages = loaded
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yml") {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join("locales", entry.Name())
|
||||
data, err := localeFiles.ReadFile(path)
|
||||
if err != nil {
|
||||
slog.Error("read locale file failed", "file", path, "err", err)
|
||||
continue
|
||||
}
|
||||
values := make(map[string]string)
|
||||
if err := yaml.Unmarshal(data, &values); err != nil {
|
||||
slog.Error("parse locale file failed", "file", path, "err", err)
|
||||
continue
|
||||
}
|
||||
locale := strings.TrimSuffix(entry.Name(), ".yml")
|
||||
loaded[NormalizeLocale(locale)] = values
|
||||
}
|
||||
messages = loaded
|
||||
})
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
["error.auth.expired"]
|
||||
other = "Your session has expired. Please sign in again."
|
||||
|
||||
["error.notFound"]
|
||||
other = "Not found"
|
||||
|
||||
["error.path.invalid"]
|
||||
other = "Invalid path parameter."
|
||||
@@ -1,8 +0,0 @@
|
||||
["error.auth.expired"]
|
||||
other = "未登录或登录已过期"
|
||||
|
||||
["error.notFound"]
|
||||
other = "Not found"
|
||||
|
||||
["error.path.invalid"]
|
||||
other = "路径参数错误"
|
||||
@@ -0,0 +1,358 @@
|
||||
error.e0001: "AI Agent not found"
|
||||
error.e0002: "AI Agent not found."
|
||||
error.e0003: "AI Agent not found or disabled."
|
||||
error.e0004: "AI Agent not found or not enabled."
|
||||
error.e0005: "Enter an AI Agent name."
|
||||
error.e0006: "This AI Agent name is already in use."
|
||||
error.e0007: "AI Agent not found or not enabled."
|
||||
error.e0008: "The AI configuration linked to this AI Agent does not exist."
|
||||
error.e0009: "AI configuration not found."
|
||||
error.e0010: "Select an AI configuration."
|
||||
error.e0011: "The AI configuration is not enabled."
|
||||
error.e0012: "AI configuration not found."
|
||||
error.e0013: "Agent run log not found."
|
||||
error.e0014: "Checkpoint not found."
|
||||
error.e0015: "The checkpoint does not belong to this AI Agent."
|
||||
error.e0016: "Direct Tool toolCode does not match serverCode."
|
||||
error.e0017: "Direct Tool toolCode does not match toolName."
|
||||
error.e0018: "Invalid Direct Tool toolCode format."
|
||||
error.e0019: "Direct Tool toolCode, serverCode, and toolName are required."
|
||||
error.e0020: "Direct Tools can only contain MCP tools."
|
||||
error.e0021: "Invalid Direct Tools configuration format."
|
||||
error.e0022: "The Excel file is empty."
|
||||
error.e0023: "Failed to parse the Excel file."
|
||||
error.e0024: "Failed to read the Excel file."
|
||||
error.e0025: "FAQ not found."
|
||||
error.e0026: "FAQ knowledge bases do not support documents."
|
||||
error.e0027: "Graph Tools can only contain Graph tools."
|
||||
error.e0028: "Invalid Graph Tools configuration format."
|
||||
error.e0029: "HTML message content is empty."
|
||||
error.e0030: "Images in HTML messages must use uploaded files."
|
||||
error.e0031: "Invalid JSON array format."
|
||||
error.e0032: "MCP endpoint is required."
|
||||
error.e0033: "MCP server is not enabled."
|
||||
error.e0034: "MCP server configuration not found."
|
||||
error.e0035: "MCP is not enabled."
|
||||
error.e0036: "OIDC clientId is not configured."
|
||||
error.e0037: "OIDC clientSecret is not configured."
|
||||
error.e0038: "OIDC id_token is missing."
|
||||
error.e0039: "OIDC issuer is not configured."
|
||||
error.e0040: "OIDC redirectUrl is not configured."
|
||||
error.e0041: "OIDC authorization code is required."
|
||||
error.e0042: "OIDC user information not found."
|
||||
error.e0043: "OIDC user identifier not found."
|
||||
error.e0044: "OIDC sign-in secret is not configured."
|
||||
error.e0045: "OIDC sign-in is not enabled."
|
||||
error.e0046: "OIDC sign-in state is invalid or has expired."
|
||||
error.e0047: "The system user linked to this OIDC account does not exist."
|
||||
error.e0048: "OSS accessKeyId is not configured."
|
||||
error.e0049: "OSS accessKeySecret is not configured."
|
||||
error.e0050: "OSS bucket is not configured."
|
||||
error.e0051: "OSS endpoint is not configured."
|
||||
error.e0052: "Invalid Skill ID."
|
||||
error.e0053: "Skill not found."
|
||||
error.e0054: "Skill not found or not enabled."
|
||||
error.e0055: "Enter a Skill name."
|
||||
error.e0056: "Skill is not enabled."
|
||||
error.e0057: "Enter a Skill code."
|
||||
error.e0058: "This Skill code is already in use."
|
||||
error.e0059: "Web channel position must be left or right."
|
||||
error.e0060: "Invalid web channel configuration."
|
||||
error.e0061: "AI Agent ID is required."
|
||||
error.e0062: "Channel not found."
|
||||
error.e0063: "Checkpoint ID is required."
|
||||
error.e0064: "Conversation ID is required."
|
||||
error.e0065: "Customer ID is required."
|
||||
error.e0066: "Document ID or FAQ ID is required."
|
||||
error.e0067: "Invalid index status."
|
||||
error.e0068: "openKfID is required."
|
||||
error.e0069: "This openKfId is already used by another channel."
|
||||
error.e0070: "Server code is required."
|
||||
error.e0071: "Skill code is required."
|
||||
error.e0072: "Ticket is required."
|
||||
error.e0073: "The MCP server bound to this tool code does not exist or is not enabled."
|
||||
error.e0074: "Tool code is required."
|
||||
error.e0075: "Invalid tool code format."
|
||||
error.e0076: "Tool name is required."
|
||||
error.e0077: "tool_search only supports calling MCP toolCode."
|
||||
error.e0078: "User message is required."
|
||||
error.e0079: "The uploaded file exceeds the size limit."
|
||||
error.e0080: "Unsupported sender type."
|
||||
error.e0081: "Unsupported read operation type."
|
||||
error.e0082: "Unsupported file storage type."
|
||||
error.e0083: "A tag cannot be moved under itself."
|
||||
error.e0084: "A directory cannot be moved under itself."
|
||||
error.e0085: "Schedules cannot be added or changed for past dates."
|
||||
error.e0086: "Transaction context is required."
|
||||
error.e0087: "You can only recall messages you sent."
|
||||
error.e0088: "Only deleted Skills can be restored."
|
||||
error.e0089: "Only .xlsx files are supported."
|
||||
error.e0090: "Only image files are supported."
|
||||
error.e0091: "Only agent messages can be recalled."
|
||||
error.e0092: "Only one primary contact method can be set."
|
||||
error.e0093: "WeCom AgentID is not configured."
|
||||
error.e0094: "WeCom openKfID is required."
|
||||
error.e0095: "WeCom media ID is required."
|
||||
error.e0096: "WeCom customer ID is required."
|
||||
error.e0097: "This WeCom phone number is already used by a system user."
|
||||
error.e0098: "The WeCom channel is not linked to an AI Agent."
|
||||
error.e0099: "The AI Agent linked to this WeCom channel does not exist or has been disabled."
|
||||
error.e0100: "WeCom is not enabled or its configuration is incomplete."
|
||||
error.e0101: "WeCom message ID is required."
|
||||
error.e0102: "Invalid WeCom channel configuration."
|
||||
error.e0103: "WeCom channel configuration is missing openKfId."
|
||||
error.e0104: "This WeCom user ID is already used as a system username."
|
||||
error.e0105: "Failed to get the WeCom user ID."
|
||||
error.e0106: "WeCom user information not found."
|
||||
error.e0107: "WeCom sign-in callback URL is not configured."
|
||||
error.e0108: "WeCom sign-in secret is not configured."
|
||||
error.e0109: "WeCom sign-in is not enabled."
|
||||
error.e0110: "WeCom sign-in state is invalid."
|
||||
error.e0111: "WeCom sign-in state is invalid or has expired."
|
||||
error.e0112: "The system user linked to this WeCom account does not exist."
|
||||
error.e0113: "WeCom returned an empty image media_id."
|
||||
error.e0114: "WeCom returned an empty message ID."
|
||||
error.e0115: "This WeCom email address is already used by a system user."
|
||||
error.e0116: "Conversation not found."
|
||||
error.e0117: "The conversation does not belong to this AI Agent."
|
||||
error.e0118: "The conversation does not belong to this customer."
|
||||
error.e0119: "This conversation is closed."
|
||||
error.e0120: "This conversation has not been assigned to an agent, so messages cannot be sent yet."
|
||||
error.e0121: "Invalid conversation filter."
|
||||
error.e0122: "Select a provider."
|
||||
error.e0123: "Invalid fallback policy."
|
||||
error.e0124: "Company not found."
|
||||
error.e0125: "Enter a company name."
|
||||
error.e0126: "This company name is already in use."
|
||||
error.e0127: "Linked user not found."
|
||||
error.e0128: "Enter a close reason."
|
||||
error.e0129: "Unsupported content type."
|
||||
error.e0130: "Unsupported chunking strategy."
|
||||
error.e0131: "Failed to create the conversation."
|
||||
error.e0132: "A single schedule entry cannot span multiple days."
|
||||
error.e0133: "Invalid request parameters."
|
||||
error.e0134: "Only active conversations can be transferred."
|
||||
error.e0135: "Only queued conversations can be assigned."
|
||||
error.e0136: "Only queued conversations can be auto-assigned."
|
||||
error.e0137: "Only queued unassigned conversations can be auto-assigned."
|
||||
error.e0138: "Only FAQs in the current knowledge base can be moved."
|
||||
error.e0139: "Only documents in the current knowledge base can be moved."
|
||||
error.e0140: "Only directories in the same knowledge base and same level can be reordered."
|
||||
error.e0141: "A tag with this name already exists at the same level."
|
||||
error.e0142: "A directory with this name already exists at the same level."
|
||||
error.e0143: "Enabled AI configurations cannot be deleted."
|
||||
error.e0144: "Reply timeout seconds cannot be less than 0."
|
||||
error.e0145: "Image message is missing assetId."
|
||||
error.e0146: "Image asset not found."
|
||||
error.e0147: "Enter a base URL."
|
||||
error.e0148: "Enter a progress update."
|
||||
error.e0149: "External user ID is required."
|
||||
error.e0150: "External identity is not initialized."
|
||||
error.e0151: "There are conflicting schedules. Resolve the conflicts first."
|
||||
error.e0152: "A directory with subdirectories cannot be moved to the second level."
|
||||
error.e0153: "Some ticket tags are invalid."
|
||||
error.e0154: "Some ticket tags are disabled."
|
||||
error.e0155: "Customer not found."
|
||||
error.e0156: "Enter a customer name."
|
||||
error.e0157: "Support session is required."
|
||||
error.e0158: "Support session parameters are incomplete."
|
||||
error.e0159: "Support session secret is not configured."
|
||||
error.e0160: "The support session has expired."
|
||||
error.e0161: "Support session verification failed."
|
||||
error.e0162: "Enter both agent ID and display name."
|
||||
error.e0163: "This agent ID is already in use."
|
||||
error.e0164: "Agent profile not found."
|
||||
error.e0165: "Invalid agent status."
|
||||
error.e0166: "This agent team is linked to AI Agents and cannot be deleted."
|
||||
error.e0167: "This agent team has linked agent profiles and cannot be deleted."
|
||||
error.e0168: "This agent team has schedules and cannot be deleted."
|
||||
error.e0169: "Agent team not found."
|
||||
error.e0170: "Enter an agent team name."
|
||||
error.e0171: "This agent team name is already in use."
|
||||
error.e0172: "Agent team schedule not found."
|
||||
error.e0173: "Agent team is not enabled."
|
||||
error.e0174: "Invalid agent team status."
|
||||
error.e0175: "Invalid password length."
|
||||
error.e0176: "Failed to read the import file."
|
||||
error.e0177: "Invalid import mode."
|
||||
error.e0178: "Ticket not found."
|
||||
error.e0179: "Enter a ticket description."
|
||||
error.e0180: "Invalid ticket source."
|
||||
error.e0181: "Enter a ticket title."
|
||||
error.e0182: "Invalid ticket status."
|
||||
error.e0183: "Closed conversations cannot be linked to customers."
|
||||
error.e0184: "Restore this deleted Skill before changing its status."
|
||||
error.e0185: "This AI Agent is linked to channels and cannot be deleted."
|
||||
error.e0186: "Platform message not found."
|
||||
error.e0187: "The current OIDC binding has been disabled."
|
||||
error.e0188: "The current WeCom binding has been disabled."
|
||||
error.e0189: "This conversation is not currently handled by AI."
|
||||
error.e0190: "This conversation has already been assigned."
|
||||
error.e0191: "This conversation has been assigned to another agent."
|
||||
error.e0192: "This conversation has already been taken over by a human agent."
|
||||
error.e0193: "This conversation is not assigned to an agent."
|
||||
error.e0194: "Human support is currently outside service hours."
|
||||
error.e0195: "Reading files from this storage provider is not supported yet."
|
||||
error.e0196: "This channel does not support a user JWT secret."
|
||||
error.e0197: "This conversation cannot be closed in its current status."
|
||||
error.e0198: "The current sign-in identity is not an internal enterprise member."
|
||||
error.e0199: "This knowledge base is not an FAQ knowledge base."
|
||||
error.e0200: "The current system account has been disabled."
|
||||
error.e0201: "Invalid WeChat Official Account channel configuration."
|
||||
error.e0202: "WeChat authorization code is required."
|
||||
error.e0203: "Quick reply not found."
|
||||
error.e0204: "Company not found."
|
||||
error.e0205: "Agent team not found."
|
||||
error.e0206: "This phone number is already in use."
|
||||
error.e0207: "Enter a Skill description."
|
||||
error.e0208: "Channel not found."
|
||||
error.e0209: "The channel does not exist or has been disabled."
|
||||
error.e0210: "Channel error."
|
||||
error.e0211: "The channel is not initialized."
|
||||
error.e0212: "Recipient is required."
|
||||
error.e0213: "This file cannot be accessed."
|
||||
error.e0214: "File not found."
|
||||
error.e0215: "Text content is required."
|
||||
error.e0216: "Text list is required."
|
||||
error.e0217: "Text message content is empty."
|
||||
error.e0218: "Document not found."
|
||||
error.e0219: "Document knowledge bases cannot use the FAQ chunking strategy."
|
||||
error.e0220: "Enter a new password."
|
||||
error.e0221: "You do not have permission to close this conversation."
|
||||
error.e0222: "You do not have permission to access this conversation."
|
||||
error.e0223: "You do not have permission to transfer this conversation."
|
||||
error.e0224: "You do not have permission to link this conversation."
|
||||
error.e0225: "You do not have permission to perform this action."
|
||||
error.e0226: "You do not have permission to perform this action."
|
||||
error.e0227: "Invalid time format."
|
||||
error.e0228: "Weekday must be between 1 and 7."
|
||||
error.e0229: "Maximum concurrent conversations cannot be less than 0."
|
||||
error.e0230: "Invalid service mode."
|
||||
error.e0231: "No matching WeCom channel was found."
|
||||
error.e0232: "No schedules were generated."
|
||||
error.auth.expired: "Your session has expired. Please sign in again."
|
||||
error.e0234: "No available AI configuration is configured."
|
||||
error.e0235: "No available embedding model is configured."
|
||||
error.e0236: "Permission not found."
|
||||
error.e0237: "The standard question already exists and was skipped."
|
||||
error.e0238: "Tag not found."
|
||||
error.e0239: "Enter a tag name."
|
||||
error.e0240: "Enter both title and content."
|
||||
error.e0241: "Retrieval log not found."
|
||||
error.e0242: "Enter a model name."
|
||||
error.e0243: "Select a model type."
|
||||
error.e0244: "Message not found."
|
||||
error.e0245: "Message content is required."
|
||||
error.e0246: "This message has been recalled."
|
||||
error.e0247: "Enter a channel name."
|
||||
error.e0248: "This channel code is already in use."
|
||||
error.e0249: "Invalid channel status."
|
||||
error.e0250: "Invalid channel type."
|
||||
error.e0251: "Parent tag not found."
|
||||
error.e0252: "Parent directory not found."
|
||||
error.e0253: "The parent directory does not belong to the current knowledge base."
|
||||
error.e0254: "Invalid status value."
|
||||
error.e0255: "User not found."
|
||||
error.e0256: "The user does not exist or has been disabled."
|
||||
error.e0257: "Enter a username."
|
||||
error.e0258: "Enter both username and password."
|
||||
error.e0259: "This username is already in use."
|
||||
error.e0260: "The username or password is incorrect."
|
||||
error.e0261: "User name is required."
|
||||
error.e0262: "User ID is required."
|
||||
error.e0263: "User identity is required."
|
||||
error.e0264: "User identity has expired."
|
||||
error.e0265: "User identity verification failed."
|
||||
error.e0266: "User identity verification is not configured."
|
||||
error.e0267: "Login credential has been invalidated."
|
||||
error.e0268: "Login credential has expired."
|
||||
error.e0269: "Invalid login credential."
|
||||
error.e0270: "Too many failed sign-in attempts. Please try again later."
|
||||
error.e0271: "The sign-in ticket is invalid or has expired."
|
||||
error.e0272: "Sign-in result is required."
|
||||
error.e0273: "Directory not found."
|
||||
error.e0274: "Directories cannot be moved to another knowledge base."
|
||||
error.e0275: "Directory name is required."
|
||||
error.e0276: "Target agent not found."
|
||||
error.e0277: "The target agent must be different from the current assignee."
|
||||
error.e0278: "Select a target agent."
|
||||
error.e0279: "The target tool is not authorized for the current conversation."
|
||||
error.e0280: "Invalid similar-question format."
|
||||
error.e0281: "This knowledge base contains FAQs and cannot be deleted."
|
||||
error.e0282: "This knowledge base contains documents and cannot be deleted."
|
||||
error.e0283: "Knowledge base not found."
|
||||
error.e0284: "Knowledge base is required."
|
||||
error.e0285: "Knowledge base is not enabled."
|
||||
error.e0286: "This knowledge base directory is unavailable."
|
||||
error.e0287: "Knowledge base directory not found."
|
||||
error.e0288: "This directory does not belong to the current knowledge base."
|
||||
error.e0289: "Knowledge base directories support at most two levels."
|
||||
error.e0290: "Unsupported knowledge base type."
|
||||
error.e0291: "Disabled roles cannot be assigned."
|
||||
error.e0292: "Enter an answer."
|
||||
error.e0293: "Built-in system roles cannot be deleted."
|
||||
error.e0294: "Team lead user not found."
|
||||
error.e0295: "End date must be later than or equal to start date."
|
||||
error.e0296: "End time must be later than start time."
|
||||
error.e0297: "The standard question column is missing."
|
||||
error.e0298: "The answer column is missing."
|
||||
error.e0299: "Contact method not found."
|
||||
error.e0300: "Contact method is required."
|
||||
error.e0301: "Invalid contact method type."
|
||||
error.e0302: "View not found."
|
||||
error.e0303: "Enter a view name."
|
||||
error.e0304: "The view filter format is invalid."
|
||||
error.e0305: "Role not found."
|
||||
error.e0306: "Enter both role name and role code."
|
||||
error.e0307: "This role is assigned to users and cannot be deleted."
|
||||
error.e0308: "This role code is already in use."
|
||||
error.e0309: "This agent team already has a schedule in the selected time period."
|
||||
error.e0310: "This tag has child tags and cannot be deleted."
|
||||
error.e0311: "This tag is linked to conversations and cannot be deleted."
|
||||
error.e0312: "This tag is linked to tickets and cannot be deleted."
|
||||
error.e0313: "This channel does not support public support configuration."
|
||||
error.e0314: "This user already has an agent profile."
|
||||
error.e0315: "This directory contains FAQs and cannot be deleted."
|
||||
error.e0316: "This directory contains subdirectories and cannot be deleted."
|
||||
error.e0317: "This directory contains documents and cannot be deleted."
|
||||
error.e0318: "This contact method already exists."
|
||||
error.e0319: "Use the delete endpoint to mark a Skill as deleted."
|
||||
error.e0320: "Select at least one knowledge base."
|
||||
error.e0321: "Select an AI Agent."
|
||||
error.e0322: "Choose an image to upload."
|
||||
error.e0323: "Choose a file to upload."
|
||||
error.e0324: "Choose an attachment to upload."
|
||||
error.e0325: "Select a user to link."
|
||||
error.e0326: "Select an agent team."
|
||||
error.e0327: "Choose a file to import."
|
||||
error.e0328: "Select an agent team."
|
||||
error.e0329: "Select a weekday."
|
||||
error.e0330: "Select FAQs to delete."
|
||||
error.e0331: "Select documents to delete."
|
||||
error.e0332: "Select FAQs to move."
|
||||
error.e0333: "Select documents to move."
|
||||
error.e0334: "Assignee not found."
|
||||
error.path.invalid: "Invalid path parameter."
|
||||
error.e0336: "Invalid human handoff mode."
|
||||
error.e0337: "Notification not found."
|
||||
error.e0338: "This email address is already in use."
|
||||
error.e0339: "Enter a configuration name."
|
||||
error.e0340: "Enter a question."
|
||||
error.e0341: "The question cannot exceed 500 characters."
|
||||
error.e0342: "Attachment not found."
|
||||
error.e0343: "The attachment has not finished uploading."
|
||||
error.e0344: "Invalid attachment message payload format."
|
||||
error.e0345: "Attachment message is missing assetId."
|
||||
error.e0346: "Attachment message is missing payload."
|
||||
error.e0347: "Default team queue mode requires at least one agent team."
|
||||
error.notFound: "Not found"
|
||||
error.knowledgeBase.referencedByAgent: "This knowledge base is referenced by AI Agent \"%s\". Remove the binding first."
|
||||
error.knowledgeBase.referencedByAgents: "This knowledge base is referenced by %d AI Agents. Remove the bindings first."
|
||||
error.agentTeamSchedule.batchLimit: "You can generate at most %d schedule entries at a time."
|
||||
error.agentTeamSchedule.conflictRange: "This agent team already has a schedule from %s to %s."
|
||||
error.knowledgeFAQImport.duplicateQuestionInFile: "The standard question is duplicated in the same file. It first appeared on row %d."
|
||||
error.mcp.listToolsFailed: "Failed to list MCP tools: %v"
|
||||
error.mcp.callToolFailed: "Failed to call MCP tool: %v"
|
||||
error.mcp.connectServerFailed: "Failed to connect to MCP Server: %v"
|
||||
error.wxwork.unsupportedOutboundMessageType: "Unsupported WeCom outbound message type: %s"
|
||||
error.wxwork.currentUnsupportedOutboundMessageType: "The current WeCom outbound message type is not supported yet: %s"
|
||||
@@ -0,0 +1,358 @@
|
||||
error.e0001: "AI Agent not found"
|
||||
error.e0002: "AI Agent 不存在"
|
||||
error.e0003: "AI Agent 不存在或已停用"
|
||||
error.e0004: "AI Agent 不存在或未启用"
|
||||
error.e0005: "AI Agent 名称不能为空"
|
||||
error.e0006: "AI Agent 名称已存在"
|
||||
error.e0007: "AI Agent不存在或未启用"
|
||||
error.e0008: "AI Agent关联的AI配置不存在"
|
||||
error.e0009: "AI 配置不存在"
|
||||
error.e0010: "AI 配置不能为空"
|
||||
error.e0011: "AI 配置未启用"
|
||||
error.e0012: "AI配置不存在"
|
||||
error.e0013: "Agent 运行日志不存在"
|
||||
error.e0014: "CheckPoint 不存在"
|
||||
error.e0015: "CheckPoint 与 AI Agent 不匹配"
|
||||
error.e0016: "Direct Tool 的 toolCode 与 serverCode 不一致"
|
||||
error.e0017: "Direct Tool 的 toolCode 与 toolName 不一致"
|
||||
error.e0018: "Direct Tool 的 toolCode 格式不合法"
|
||||
error.e0019: "Direct Tool 的 toolCode、serverCode 和 toolName 不能为空"
|
||||
error.e0020: "Direct Tools 仅允许配置 MCP 工具"
|
||||
error.e0021: "Direct Tools 配置格式不合法"
|
||||
error.e0022: "Excel文件为空"
|
||||
error.e0023: "Excel文件解析失败"
|
||||
error.e0024: "Excel文件读取失败"
|
||||
error.e0025: "FAQ不存在"
|
||||
error.e0026: "FAQ知识库不支持文档"
|
||||
error.e0027: "Graph Tools 仅允许配置 Graph Tool"
|
||||
error.e0028: "Graph Tools 配置格式不合法"
|
||||
error.e0029: "HTML 消息内容为空"
|
||||
error.e0030: "HTML消息中的图片必须使用已上传文件"
|
||||
error.e0031: "JSON 数组格式不合法"
|
||||
error.e0032: "MCP endpoint不能为空"
|
||||
error.e0033: "MCP服务未启用"
|
||||
error.e0034: "MCP服务配置不存在"
|
||||
error.e0035: "MCP未启用"
|
||||
error.e0036: "OIDC clientId 未配置"
|
||||
error.e0037: "OIDC clientSecret 未配置"
|
||||
error.e0038: "OIDC id_token 不存在"
|
||||
error.e0039: "OIDC issuer 未配置"
|
||||
error.e0040: "OIDC redirectUrl 未配置"
|
||||
error.e0041: "OIDC 授权 code 不能为空"
|
||||
error.e0042: "OIDC 用户信息不存在"
|
||||
error.e0043: "OIDC 用户标识不存在"
|
||||
error.e0044: "OIDC 登录密钥未配置"
|
||||
error.e0045: "OIDC 登录未启用"
|
||||
error.e0046: "OIDC 登录状态无效或已过期"
|
||||
error.e0047: "OIDC 账号绑定的系统用户不存在"
|
||||
error.e0048: "OSS accessKeyId 未配置"
|
||||
error.e0049: "OSS accessKeySecret 未配置"
|
||||
error.e0050: "OSS bucket 未配置"
|
||||
error.e0051: "OSS endpoint 未配置"
|
||||
error.e0052: "Skill ID 不合法"
|
||||
error.e0053: "Skill 不存在"
|
||||
error.e0054: "Skill 不存在或未启用"
|
||||
error.e0055: "Skill 名称不能为空"
|
||||
error.e0056: "Skill 未启用"
|
||||
error.e0057: "Skill 编码不能为空"
|
||||
error.e0058: "Skill 编码已存在"
|
||||
error.e0059: "Web渠道配置 position 只能为 left 或 right"
|
||||
error.e0060: "Web渠道配置不合法"
|
||||
error.e0061: "aiAgentId不能为空"
|
||||
error.e0062: "channel not found"
|
||||
error.e0063: "checkPointId不能为空"
|
||||
error.e0064: "conversationId不能为空"
|
||||
error.e0065: "customerId 必填"
|
||||
error.e0066: "documentId或faqId不能为空"
|
||||
error.e0067: "indexStatus参数不合法"
|
||||
error.e0068: "openKfID不能为空"
|
||||
error.e0069: "openKfId 已被其他渠道使用"
|
||||
error.e0070: "serverCode不能为空"
|
||||
error.e0071: "skillCode不能为空"
|
||||
error.e0072: "ticket 不能为空"
|
||||
error.e0073: "toolCode 绑定的 MCP 服务不存在或未启用"
|
||||
error.e0074: "toolCode不能为空"
|
||||
error.e0075: "toolCode格式不合法"
|
||||
error.e0076: "toolName不能为空"
|
||||
error.e0077: "tool_search 只支持调用 MCP toolCode"
|
||||
error.e0078: "userMessage不能为空"
|
||||
error.e0079: "上传文件超过大小限制"
|
||||
error.e0080: "不支持的发送人类型"
|
||||
error.e0081: "不支持的已读操作类型"
|
||||
error.e0082: "不支持的文件存储类型"
|
||||
error.e0083: "不能将标签设为自己的子标签"
|
||||
error.e0084: "不能将目录设为自己的子目录"
|
||||
error.e0085: "不能添加或修改历史日期的排班"
|
||||
error.e0086: "事务上下文不能为空"
|
||||
error.e0087: "仅允许撤回自己发送的消息"
|
||||
error.e0088: "仅已删除的 Skill 支持恢复"
|
||||
error.e0089: "仅支持.xlsx文件"
|
||||
error.e0090: "仅支持上传图片文件"
|
||||
error.e0091: "仅支持撤回客服消息"
|
||||
error.e0092: "仅能指定一条主联系方式"
|
||||
error.e0093: "企业微信 AgentID 未配置"
|
||||
error.e0094: "企业微信 openKfID 不能为空"
|
||||
error.e0095: "企业微信媒体ID不能为空"
|
||||
error.e0096: "企业微信客户ID不能为空"
|
||||
error.e0097: "企业微信手机号已被系统用户占用"
|
||||
error.e0098: "企业微信接入渠道未绑定AI Agent"
|
||||
error.e0099: "企业微信接入渠道绑定的AI Agent不存在或已禁用"
|
||||
error.e0100: "企业微信未启用或配置不完整"
|
||||
error.e0101: "企业微信消息ID不能为空"
|
||||
error.e0102: "企业微信渠道配置不合法"
|
||||
error.e0103: "企业微信渠道配置缺少 openKfId"
|
||||
error.e0104: "企业微信用户ID已被系统用户名占用"
|
||||
error.e0105: "企业微信用户ID获取失败"
|
||||
error.e0106: "企业微信用户信息不存在"
|
||||
error.e0107: "企业微信登录回调地址未配置"
|
||||
error.e0108: "企业微信登录密钥未配置"
|
||||
error.e0109: "企业微信登录未启用"
|
||||
error.e0110: "企业微信登录状态无效"
|
||||
error.e0111: "企业微信登录状态无效或已过期"
|
||||
error.e0112: "企业微信账号绑定的系统用户不存在"
|
||||
error.e0113: "企业微信返回的图片 media_id 为空"
|
||||
error.e0114: "企业微信返回的消息ID为空"
|
||||
error.e0115: "企业微信邮箱已被系统用户占用"
|
||||
error.e0116: "会话不存在"
|
||||
error.e0117: "会话与 AI Agent 不匹配"
|
||||
error.e0118: "会话与客户不匹配"
|
||||
error.e0119: "会话已关闭"
|
||||
error.e0120: "会话未分配客服,暂不允许发送消息"
|
||||
error.e0121: "会话筛选项不合法"
|
||||
error.e0122: "供应商不能为空"
|
||||
error.e0123: "兜底策略不合法"
|
||||
error.e0124: "公司不存在"
|
||||
error.e0125: "公司名称不能为空"
|
||||
error.e0126: "公司名称已存在"
|
||||
error.e0127: "关联用户不存在"
|
||||
error.e0128: "关闭原因不能为空"
|
||||
error.e0129: "内容类型不支持"
|
||||
error.e0130: "分块策略不支持"
|
||||
error.e0131: "创建会话失败"
|
||||
error.e0132: "单条排班记录不能跨天"
|
||||
error.e0133: "参数不合法"
|
||||
error.e0134: "只有处理中会话允许转接"
|
||||
error.e0135: "只有待接入会话允许分配"
|
||||
error.e0136: "只有待接入会话允许自动分配"
|
||||
error.e0137: "只有待接入未分配会话允许自动分配"
|
||||
error.e0138: "只能移动当前知识库下的FAQ"
|
||||
error.e0139: "只能移动当前知识库下的文档"
|
||||
error.e0140: "只能调整同知识库同级目录排序"
|
||||
error.e0141: "同级下已存在相同名称的标签"
|
||||
error.e0142: "同级下已存在相同名称的目录"
|
||||
error.e0143: "启用中的AI配置不允许删除"
|
||||
error.e0144: "回复超时秒数不能小于 0"
|
||||
error.e0145: "图片消息缺少 assetId"
|
||||
error.e0146: "图片资源不存在"
|
||||
error.e0147: "基础地址不能为空"
|
||||
error.e0148: "处理进展不能为空"
|
||||
error.e0149: "外部用户标识不能为空"
|
||||
error.e0150: "外部身份未初始化"
|
||||
error.e0151: "存在冲突排班,请先处理冲突"
|
||||
error.e0152: "存在子目录的目录不能移动到二级目录"
|
||||
error.e0153: "存在无效工单标签"
|
||||
error.e0154: "存在未启用的工单标签"
|
||||
error.e0155: "客户不存在"
|
||||
error.e0156: "客户名称不能为空"
|
||||
error.e0157: "客服会话不能为空"
|
||||
error.e0158: "客服会话参数不完整"
|
||||
error.e0159: "客服会话密钥未配置"
|
||||
error.e0160: "客服会话已过期"
|
||||
error.e0161: "客服会话校验失败"
|
||||
error.e0162: "客服工号和展示名不能为空"
|
||||
error.e0163: "客服工号已存在"
|
||||
error.e0164: "客服档案不存在"
|
||||
error.e0165: "客服状态不合法"
|
||||
error.e0166: "客服组下仍有关联 AI Agent,无法删除"
|
||||
error.e0167: "客服组下仍有关联客服档案,无法删除"
|
||||
error.e0168: "客服组下仍有关联组排班,无法删除"
|
||||
error.e0169: "客服组不存在"
|
||||
error.e0170: "客服组名称不能为空"
|
||||
error.e0171: "客服组名称已存在"
|
||||
error.e0172: "客服组排班不存在"
|
||||
error.e0173: "客服组未启用"
|
||||
error.e0174: "客服组状态不合法"
|
||||
error.e0175: "密码长度不合法"
|
||||
error.e0176: "导入文件读取失败"
|
||||
error.e0177: "导入模式不合法"
|
||||
error.e0178: "工单不存在"
|
||||
error.e0179: "工单描述不能为空"
|
||||
error.e0180: "工单来源不合法"
|
||||
error.e0181: "工单标题不能为空"
|
||||
error.e0182: "工单状态不合法"
|
||||
error.e0183: "已关闭的会话无法关联客户"
|
||||
error.e0184: "已删除的 Skill 不能直接修改状态,请先恢复"
|
||||
error.e0185: "已有接入渠道绑定该 AI Agent,无法删除"
|
||||
error.e0186: "平台消息不存在"
|
||||
error.e0187: "当前 OIDC 绑定已停用"
|
||||
error.e0188: "当前企业微信绑定已停用"
|
||||
error.e0189: "当前会话不处于 AI 接待状态"
|
||||
error.e0190: "当前会话已分配客服"
|
||||
error.e0191: "当前会话已分配给其他客服"
|
||||
error.e0192: "当前会话已由人工客服接管"
|
||||
error.e0193: "当前会话未分配客服"
|
||||
error.e0194: "当前暂不在人工客服服务时间内"
|
||||
error.e0195: "当前暂不支持该存储类型的文件读取"
|
||||
error.e0196: "当前渠道不支持用户 JWT Secret"
|
||||
error.e0197: "当前状态不允许关闭会话"
|
||||
error.e0198: "当前登录身份不是企业内部成员"
|
||||
error.e0199: "当前知识库不是FAQ知识库"
|
||||
error.e0200: "当前系统账号已被禁用"
|
||||
error.e0201: "微信公众号渠道配置不合法"
|
||||
error.e0202: "微信授权 code 不能为空"
|
||||
error.e0203: "快捷回复不存在"
|
||||
error.e0204: "所属公司不存在"
|
||||
error.e0205: "所属客服组不存在"
|
||||
error.e0206: "手机号已存在"
|
||||
error.e0207: "技能说明不能为空"
|
||||
error.e0208: "接入渠道不存在"
|
||||
error.e0209: "接入渠道不存在或已停用"
|
||||
error.e0210: "接入渠道异常"
|
||||
error.e0211: "接入渠道未初始化"
|
||||
error.e0212: "接收人不能为空"
|
||||
error.e0213: "文件不可访问"
|
||||
error.e0214: "文件不存在"
|
||||
error.e0215: "文本内容不能为空"
|
||||
error.e0216: "文本列表不能为空"
|
||||
error.e0217: "文本消息内容为空"
|
||||
error.e0218: "文档不存在"
|
||||
error.e0219: "文档知识库不能使用FAQ分块策略"
|
||||
error.e0220: "新密码不能为空"
|
||||
error.e0221: "无权关闭该会话"
|
||||
error.e0222: "无权访问该会话"
|
||||
error.e0223: "无权转接该会话"
|
||||
error.e0224: "无权限关联该会话"
|
||||
error.e0225: "无权限执行该操作"
|
||||
error.e0226: "无权限操作"
|
||||
error.e0227: "时间格式错误"
|
||||
error.e0228: "星期必须在 1 到 7 之间"
|
||||
error.e0229: "最大并发接待数不能小于 0"
|
||||
error.e0230: "服务模式不合法"
|
||||
error.e0231: "未找到匹配的企业微信接入渠道"
|
||||
error.e0232: "未生成任何排班"
|
||||
error.auth.expired: "未登录或登录已过期"
|
||||
error.e0234: "未配置可用的 AI 配置"
|
||||
error.e0235: "未配置可用的 Embedding 模型"
|
||||
error.e0236: "权限不存在"
|
||||
error.e0237: "标准问题已存在,已跳过"
|
||||
error.e0238: "标签不存在"
|
||||
error.e0239: "标签名称不能为空"
|
||||
error.e0240: "标题和内容不能为空"
|
||||
error.e0241: "检索日志不存在"
|
||||
error.e0242: "模型名称不能为空"
|
||||
error.e0243: "模型类型不能为空"
|
||||
error.e0244: "消息不存在"
|
||||
error.e0245: "消息内容不能为空"
|
||||
error.e0246: "消息已撤回"
|
||||
error.e0247: "渠道名称不能为空"
|
||||
error.e0248: "渠道标识已存在"
|
||||
error.e0249: "渠道状态不合法"
|
||||
error.e0250: "渠道类型不合法"
|
||||
error.e0251: "父标签不存在"
|
||||
error.e0252: "父目录不存在"
|
||||
error.e0253: "父目录不属于当前知识库"
|
||||
error.e0254: "状态值不合法"
|
||||
error.e0255: "用户不存在"
|
||||
error.e0256: "用户不存在或已被禁用"
|
||||
error.e0257: "用户名不能为空"
|
||||
error.e0258: "用户名和密码不能为空"
|
||||
error.e0259: "用户名已存在"
|
||||
error.e0260: "用户名或密码错误"
|
||||
error.e0261: "用户名称不能为空"
|
||||
error.e0262: "用户标识不能为空"
|
||||
error.e0263: "用户身份不能为空"
|
||||
error.e0264: "用户身份已过期"
|
||||
error.e0265: "用户身份校验失败"
|
||||
error.e0266: "用户身份校验未配置"
|
||||
error.e0267: "登录凭证已失效"
|
||||
error.e0268: "登录凭证已过期"
|
||||
error.e0269: "登录凭证无效"
|
||||
error.e0270: "登录失败次数过多,请稍后再试"
|
||||
error.e0271: "登录票据无效或已过期"
|
||||
error.e0272: "登录结果不能为空"
|
||||
error.e0273: "目录不存在"
|
||||
error.e0274: "目录不能移动到其他知识库"
|
||||
error.e0275: "目录名称不能为空"
|
||||
error.e0276: "目标客服不存在"
|
||||
error.e0277: "目标客服不能与当前指派人相同"
|
||||
error.e0278: "目标客服不能为空"
|
||||
error.e0279: "目标工具未被当前会话授权"
|
||||
error.e0280: "相似问格式不合法"
|
||||
error.e0281: "知识库下存在FAQ,无法删除"
|
||||
error.e0282: "知识库下存在文档,无法删除"
|
||||
error.e0283: "知识库不存在"
|
||||
error.e0284: "知识库不能为空"
|
||||
error.e0285: "知识库未启用"
|
||||
error.e0286: "知识库目录不可用"
|
||||
error.e0287: "知识库目录不存在"
|
||||
error.e0288: "知识库目录不属于当前知识库"
|
||||
error.e0289: "知识库目录最多支持二级"
|
||||
error.e0290: "知识库类型不支持"
|
||||
error.e0291: "禁用角色不允许分配"
|
||||
error.e0292: "答案不能为空"
|
||||
error.e0293: "系统内置角色不允许删除"
|
||||
error.e0294: "组长用户不存在"
|
||||
error.e0295: "结束日期必须晚于或等于开始日期"
|
||||
error.e0296: "结束时间必须晚于开始时间"
|
||||
error.e0297: "缺少标准问题列"
|
||||
error.e0298: "缺少答案列"
|
||||
error.e0299: "联系方式不存在"
|
||||
error.e0300: "联系方式不能为空"
|
||||
error.e0301: "联系方式类型不合法"
|
||||
error.e0302: "视图不存在"
|
||||
error.e0303: "视图名称不能为空"
|
||||
error.e0304: "视图筛选条件格式不正确"
|
||||
error.e0305: "角色不存在"
|
||||
error.e0306: "角色名称和编码不能为空"
|
||||
error.e0307: "角色已被用户使用,无法删除"
|
||||
error.e0308: "角色编码已存在"
|
||||
error.e0309: "该客服组在所选时间段已存在排班"
|
||||
error.e0310: "该标签下存在子标签,无法删除"
|
||||
error.e0311: "该标签已关联会话,无法删除"
|
||||
error.e0312: "该标签已关联工单,无法删除"
|
||||
error.e0313: "该渠道不支持开放客服配置"
|
||||
error.e0314: "该用户已存在客服档案"
|
||||
error.e0315: "该目录下存在FAQ,无法删除"
|
||||
error.e0316: "该目录下存在子目录,无法删除"
|
||||
error.e0317: "该目录下存在文档,无法删除"
|
||||
error.e0318: "该联系方式已存在"
|
||||
error.e0319: "请使用删除接口处理删除状态"
|
||||
error.e0320: "请至少选择一个知识库"
|
||||
error.e0321: "请选择 AI Agent"
|
||||
error.e0322: "请选择上传图片"
|
||||
error.e0323: "请选择上传文件"
|
||||
error.e0324: "请选择上传附件"
|
||||
error.e0325: "请选择关联用户"
|
||||
error.e0326: "请选择客服组"
|
||||
error.e0327: "请选择导入文件"
|
||||
error.e0328: "请选择所属客服组"
|
||||
error.e0329: "请选择星期"
|
||||
error.e0330: "请选择要删除的FAQ"
|
||||
error.e0331: "请选择要删除的文档"
|
||||
error.e0332: "请选择要移动的FAQ"
|
||||
error.e0333: "请选择要移动的文档"
|
||||
error.e0334: "负责人不存在"
|
||||
error.path.invalid: "路径参数错误"
|
||||
error.e0336: "转人工模式不合法"
|
||||
error.e0337: "通知不存在"
|
||||
error.e0338: "邮箱已存在"
|
||||
error.e0339: "配置名称不能为空"
|
||||
error.e0340: "问题不能为空"
|
||||
error.e0341: "问题不能超过500字"
|
||||
error.e0342: "附件不存在"
|
||||
error.e0343: "附件尚未上传完成"
|
||||
error.e0344: "附件消息 payload 格式错误"
|
||||
error.e0345: "附件消息缺少 assetId"
|
||||
error.e0346: "附件消息缺少 payload"
|
||||
error.e0347: "默认客服组待接入池模式必须至少选择一个客服组"
|
||||
error.notFound: "数据不存在"
|
||||
error.knowledgeBase.referencedByAgent: "知识库已被 AI Agent「%s」引用,请先解除绑定"
|
||||
error.knowledgeBase.referencedByAgents: "知识库已被 %d 个 AI Agent 引用,请先解除绑定"
|
||||
error.agentTeamSchedule.batchLimit: "单次最多生成 %d 条排班"
|
||||
error.agentTeamSchedule.conflictRange: "该客服组在 %s 至 %s 已存在排班"
|
||||
error.knowledgeFAQImport.duplicateQuestionInFile: "同一文件中标准问题重复,首次出现于第%d行"
|
||||
error.mcp.listToolsFailed: "列出 MCP 工具失败: %v"
|
||||
error.mcp.callToolFailed: "调用 MCP 工具失败: %v"
|
||||
error.mcp.connectServerFailed: "连接 MCP Server 失败: %v"
|
||||
error.wxwork.unsupportedOutboundMessageType: "不支持的企业微信下行消息类型: %s"
|
||||
error.wxwork.currentUnsupportedOutboundMessageType: "当前暂不支持企业微信下行消息类型: %s"
|
||||
@@ -1,44 +0,0 @@
|
||||
package i18nx
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
)
|
||||
|
||||
func T(ctx *gin.Context, messageID string, data map[string]any) string {
|
||||
if ctx != nil {
|
||||
if value, exists := ctx.Get(contextLocaleKey); exists {
|
||||
if locale, ok := value.(string); ok {
|
||||
return TLocale(locale, messageID, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return TLocale(DefaultLocale, messageID, data)
|
||||
}
|
||||
|
||||
func TLocale(locale string, messageID string, data map[string]any) string {
|
||||
normalized := NormalizeLocale(locale)
|
||||
message := localize(normalized, messageID, data)
|
||||
if message != "" {
|
||||
return message
|
||||
}
|
||||
if normalized != DefaultLocale {
|
||||
message = localize(DefaultLocale, messageID, data)
|
||||
if message != "" {
|
||||
return message
|
||||
}
|
||||
}
|
||||
return messageID
|
||||
}
|
||||
|
||||
func localize(locale string, messageID string, data map[string]any) string {
|
||||
localizer := i18n.NewLocalizer(Bundle(), locale, DefaultLocale)
|
||||
message, err := localizer.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: messageID,
|
||||
TemplateData: data,
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return message
|
||||
}
|
||||
@@ -44,10 +44,10 @@ func GetExternalUser(ctx *gin.Context, secret string) (*ExternalUser, error) {
|
||||
|
||||
func verifyUserToken(userToken, secret string) (*UserTokenClaims, error) {
|
||||
if strs.IsBlank(userToken) {
|
||||
return nil, errorsx.Unauthorized("用户身份不能为空")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0263")
|
||||
}
|
||||
if strs.IsBlank(secret) {
|
||||
return nil, errorsx.Unauthorized("用户身份校验未配置")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0266")
|
||||
}
|
||||
|
||||
claims := &UserTokenClaims{}
|
||||
@@ -63,22 +63,22 @@ func verifyUserToken(userToken, secret string) (*UserTokenClaims, error) {
|
||||
}))
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, errorsx.Unauthorized("用户身份已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0264")
|
||||
}
|
||||
return nil, errorsx.Unauthorized("用户身份校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0265")
|
||||
}
|
||||
if token == nil || !token.Valid {
|
||||
return nil, errorsx.Unauthorized("用户身份校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0265")
|
||||
}
|
||||
|
||||
if strs.IsBlank(claims.UserID) {
|
||||
return nil, errorsx.Unauthorized("用户标识不能为空")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0262")
|
||||
}
|
||||
if strs.IsBlank(claims.Name) {
|
||||
return nil, errorsx.Unauthorized("用户名称不能为空")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0261")
|
||||
}
|
||||
if claims.ExpiresAt == nil {
|
||||
return nil, errorsx.Unauthorized("用户身份已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0264")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
@@ -98,7 +98,7 @@ func getUserToken(ctx *gin.Context) string {
|
||||
func getGuestUser(ctx *gin.Context) (*ExternalUser, error) {
|
||||
externalID := getExternalID(ctx)
|
||||
if strs.IsBlank(externalID) {
|
||||
return nil, errorsx.Unauthorized("用户标识不能为空")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0262")
|
||||
}
|
||||
return &ExternalUser{
|
||||
ExternalSource: enums.ExternalSourceGuest,
|
||||
|
||||
@@ -38,10 +38,10 @@ func NormalizeMCPToolRequest(item request.AIAgentMCPToolRequest) (request.AIAgen
|
||||
parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode)
|
||||
if parsedServerCode != "" && parsedToolName != "" {
|
||||
if serverCode != "" && !strings.EqualFold(serverCode, parsedServerCode) {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 serverCode 不一致")
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0016")
|
||||
}
|
||||
if toolName != "" && !strings.EqualFold(toolName, parsedToolName) {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 toolName 不一致")
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0017")
|
||||
}
|
||||
serverCode = parsedServerCode
|
||||
toolName = parsedToolName
|
||||
@@ -53,14 +53,14 @@ func NormalizeMCPToolRequest(item request.AIAgentMCPToolRequest) (request.AIAgen
|
||||
toolCode = BuildMCPToolCode(serverCode, toolName)
|
||||
}
|
||||
if toolCode == "" {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode、serverCode 和 toolName 不能为空")
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0019")
|
||||
}
|
||||
if parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode); parsedServerCode != "" && parsedToolName != "" {
|
||||
serverCode = parsedServerCode
|
||||
toolName = parsedToolName
|
||||
}
|
||||
if serverCode == "" && toolName == "" && strings.Contains(toolCode, "/") && !strings.HasPrefix(toolCode, "builtin/") {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 格式不合法")
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0018")
|
||||
}
|
||||
ret := request.AIAgentMCPToolRequest{
|
||||
ToolCode: toolCode,
|
||||
|
||||
@@ -54,7 +54,7 @@ func FormatTime(t time.Time) string {
|
||||
|
||||
func GenerateRandomPassword(length int) (string, error) {
|
||||
if length <= 0 {
|
||||
return "", errorsx.InvalidParam("密码长度不合法")
|
||||
return "", errorsx.InvalidParamI18n("error.e0175")
|
||||
}
|
||||
|
||||
const charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"
|
||||
|
||||
@@ -88,7 +88,7 @@ func (s *agentProfileService) GetDispatchAgents(teamIds []int64) []models.AgentP
|
||||
|
||||
func (s *agentProfileService) CreateAgentProfile(req request.CreateAgentProfileRequest, operator *dto.AuthPrincipal) (*models.AgentProfile, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item, err := s.buildProfileModel(0, req)
|
||||
if err != nil {
|
||||
@@ -104,11 +104,11 @@ func (s *agentProfileService) CreateAgentProfile(req request.CreateAgentProfileR
|
||||
|
||||
func (s *agentProfileService) UpdateAgentProfile(req request.UpdateAgentProfileRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("客服档案不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0164")
|
||||
}
|
||||
item, err := s.buildProfileModel(req.ID, req.CreateAgentProfileRequest)
|
||||
if err != nil {
|
||||
@@ -139,7 +139,7 @@ func (s *agentProfileService) UpdateAgentProfile(req request.UpdateAgentProfileR
|
||||
func (s *agentProfileService) DeleteAgentProfile(id int64) error {
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("客服档案不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0164")
|
||||
}
|
||||
repositories.AgentProfileRepository.Delete(sqls.DB(), id)
|
||||
return nil
|
||||
@@ -147,33 +147,33 @@ func (s *agentProfileService) DeleteAgentProfile(id int64) error {
|
||||
|
||||
func (s *agentProfileService) buildProfileModel(id int64, req request.CreateAgentProfileRequest) (*models.AgentProfile, error) {
|
||||
if req.UserID <= 0 {
|
||||
return nil, errorsx.InvalidParam("请选择关联用户")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0325")
|
||||
}
|
||||
if UserService.Get(req.UserID) == nil {
|
||||
return nil, errorsx.InvalidParam("关联用户不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0127")
|
||||
}
|
||||
if req.TeamID <= 0 {
|
||||
return nil, errorsx.InvalidParam("请选择所属客服组")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0328")
|
||||
}
|
||||
if AgentTeamService.Get(req.TeamID) == nil {
|
||||
return nil, errorsx.InvalidParam("所属客服组不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0205")
|
||||
}
|
||||
req.AgentCode = strings.TrimSpace(req.AgentCode)
|
||||
req.DisplayName = strings.TrimSpace(req.DisplayName)
|
||||
if req.AgentCode == "" || req.DisplayName == "" {
|
||||
return nil, errorsx.InvalidParam("客服工号和展示名不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0162")
|
||||
}
|
||||
if exists := s.Take("user_id = ? AND id <> ?", req.UserID, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("该用户已存在客服档案")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0314")
|
||||
}
|
||||
if exists := s.Take("agent_code = ? AND id <> ?", req.AgentCode, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("客服工号已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0163")
|
||||
}
|
||||
if !enums.IsValidServiceStatus(req.ServiceStatus) {
|
||||
return nil, errorsx.InvalidParam("客服状态不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0165")
|
||||
}
|
||||
if req.MaxConcurrentCount < 0 {
|
||||
return nil, errorsx.InvalidParam("最大并发接待数不能小于 0")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0229")
|
||||
}
|
||||
return &models.AgentProfile{
|
||||
UserID: req.UserID,
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/pkg/utils"
|
||||
"agent-desk/internal/repositories"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -101,7 +101,7 @@ func (s *agentTeamScheduleService) FindCalendarSchedules(req request.AgentTeamSc
|
||||
return nil, err
|
||||
}
|
||||
if !endAtValue.After(startAtValue) {
|
||||
return nil, errorsx.InvalidParam("结束时间必须晚于开始时间")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0296")
|
||||
}
|
||||
return repositories.AgentTeamScheduleRepository.FindByTimeRange(sqls.DB(), startAtValue, endAtValue, req.TeamID), nil
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (s *agentTeamScheduleService) Delete(id int64) {
|
||||
|
||||
func (s *agentTeamScheduleService) CreateAgentTeamSchedule(req request.CreateAgentTeamScheduleRequest, operator *dto.AuthPrincipal) (*models.AgentTeamSchedule, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
s.writeMu.Lock()
|
||||
item, err := s.buildScheduleModel(0, req.TeamID, req.StartAt, req.EndAt, req.Remark)
|
||||
@@ -148,12 +148,12 @@ func (s *agentTeamScheduleService) CreateAgentTeamSchedule(req request.CreateAge
|
||||
|
||||
func (s *agentTeamScheduleService) UpdateAgentTeamSchedule(req request.UpdateAgentTeamScheduleRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
s.writeMu.Lock()
|
||||
if s.Get(req.ID) == nil {
|
||||
s.writeMu.Unlock()
|
||||
return errorsx.InvalidParam("客服组排班不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0172")
|
||||
}
|
||||
item, err := s.buildScheduleModel(req.ID, req.TeamID, req.StartAt, req.EndAt, req.Remark)
|
||||
if err != nil {
|
||||
@@ -179,7 +179,7 @@ func (s *agentTeamScheduleService) UpdateAgentTeamSchedule(req request.UpdateAge
|
||||
|
||||
func (s *agentTeamScheduleService) DeleteAgentTeamSchedule(id int64) error {
|
||||
if s.Get(id) == nil {
|
||||
return errorsx.InvalidParam("客服组排班不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0172")
|
||||
}
|
||||
repositories.AgentTeamScheduleRepository.Delete(sqls.DB(), id)
|
||||
return nil
|
||||
@@ -187,19 +187,19 @@ func (s *agentTeamScheduleService) DeleteAgentTeamSchedule(id int64) error {
|
||||
|
||||
func (s *agentTeamScheduleService) BatchPreview(req request.AgentTeamScheduleBatchRequest, operator *dto.AuthPrincipal) (*AgentTeamScheduleBatchPreviewResult, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
candidates, err := s.buildBatchScheduleCandidates(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conflicts := s.findBatchConflict(candidates)
|
||||
conflicts := s.findBatchConflict(candidates, req.Locale)
|
||||
return buildBatchPreviewResult(candidates, conflicts), nil
|
||||
}
|
||||
|
||||
func (s *agentTeamScheduleService) BatchGenerate(req request.AgentTeamScheduleBatchRequest, operator *dto.AuthPrincipal) (*AgentTeamScheduleBatchGenerateResult, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
s.writeMu.Lock()
|
||||
candidates, err := s.buildBatchScheduleCandidates(req)
|
||||
@@ -207,11 +207,11 @@ func (s *agentTeamScheduleService) BatchGenerate(req request.AgentTeamScheduleBa
|
||||
s.writeMu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
conflicts := s.findBatchConflict(candidates)
|
||||
conflicts := s.findBatchConflict(candidates, req.Locale)
|
||||
for _, conflict := range conflicts {
|
||||
if conflict != "" {
|
||||
s.writeMu.Unlock()
|
||||
return nil, errorsx.InvalidParam("存在冲突排班,请先处理冲突")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0151")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,10 +227,10 @@ func (s *agentTeamScheduleService) BatchGenerate(req request.AgentTeamScheduleBa
|
||||
})
|
||||
}
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
conflicts := s.findBatchConflictByDB(ctx.Tx, candidates)
|
||||
conflicts := s.findBatchConflictByDB(ctx.Tx, candidates, req.Locale)
|
||||
for _, conflict := range conflicts {
|
||||
if conflict != "" {
|
||||
return errorsx.InvalidParam("存在冲突排班,请先处理冲突")
|
||||
return errorsx.InvalidParamI18n("error.e0151")
|
||||
}
|
||||
}
|
||||
return repositories.AgentTeamScheduleRepository.CreateBatch(ctx.Tx, schedules)
|
||||
@@ -247,14 +247,14 @@ func (s *agentTeamScheduleService) BatchGenerate(req request.AgentTeamScheduleBa
|
||||
|
||||
func (s *agentTeamScheduleService) buildScheduleModel(id, teamID int64, startAt, endAt, remark string) (*models.AgentTeamSchedule, error) {
|
||||
if teamID <= 0 {
|
||||
return nil, errorsx.InvalidParam("请选择客服组")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0326")
|
||||
}
|
||||
team := AgentTeamService.Get(teamID)
|
||||
if team == nil {
|
||||
return nil, errorsx.InvalidParam("客服组不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0169")
|
||||
}
|
||||
if !slices.Contains(enums.StatusValues, team.Status) {
|
||||
return nil, errorsx.InvalidParam("客服组状态不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0174")
|
||||
}
|
||||
startAtValue, err := parseRequiredDateTime(startAt, "开始时间格式错误")
|
||||
if err != nil {
|
||||
@@ -265,18 +265,18 @@ func (s *agentTeamScheduleService) buildScheduleModel(id, teamID int64, startAt,
|
||||
return nil, err
|
||||
}
|
||||
if !endAtValue.After(startAtValue) {
|
||||
return nil, errorsx.InvalidParam("结束时间必须晚于开始时间")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0296")
|
||||
}
|
||||
if !sameLocalDay(startAtValue, endAtValue) {
|
||||
return nil, errorsx.InvalidParam("单条排班记录不能跨天")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0132")
|
||||
}
|
||||
if startAtValue.Before(startOfLocalDay(time.Now())) {
|
||||
return nil, errorsx.InvalidParam("不能添加或修改历史日期的排班")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0085")
|
||||
}
|
||||
overlapping := repositories.AgentTeamScheduleRepository.FindOverlappingByTeamIDsAndTimeRange(sqls.DB(), []int64{teamID}, startAtValue, endAtValue)
|
||||
for _, item := range overlapping {
|
||||
if item.ID != id {
|
||||
return nil, errorsx.InvalidParam("该客服组在所选时间段已存在排班")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0309")
|
||||
}
|
||||
}
|
||||
return &models.AgentTeamSchedule{
|
||||
@@ -290,7 +290,7 @@ func (s *agentTeamScheduleService) buildScheduleModel(id, teamID int64, startAt,
|
||||
func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.AgentTeamScheduleBatchRequest) ([]batchScheduleCandidate, error) {
|
||||
teamIDs := uniquePositiveInt64s(req.TeamIDs)
|
||||
if len(teamIDs) == 0 {
|
||||
return nil, errorsx.InvalidParam("请选择客服组")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0326")
|
||||
}
|
||||
weekdays, err := normalizeBatchWeekdays(req.Weekdays)
|
||||
if err != nil {
|
||||
@@ -305,10 +305,10 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
|
||||
return nil, err
|
||||
}
|
||||
if endDate.Before(startDate) {
|
||||
return nil, errorsx.InvalidParam("结束日期必须晚于或等于开始日期")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0295")
|
||||
}
|
||||
if startDate.Before(startOfLocalDay(time.Now())) {
|
||||
return nil, errorsx.InvalidParam("不能添加或修改历史日期的排班")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0085")
|
||||
}
|
||||
startClock, err := parseRequiredClock(req.StartTime, "开始时间格式错误")
|
||||
if err != nil {
|
||||
@@ -321,7 +321,7 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
|
||||
firstStartAt := combineDateAndClock(startDate, startClock)
|
||||
firstEndAt := combineDateAndClock(startDate, endClock)
|
||||
if !firstEndAt.After(firstStartAt) {
|
||||
return nil, errorsx.InvalidParam("结束时间必须晚于开始时间")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0296")
|
||||
}
|
||||
|
||||
teams := AgentTeamService.FindByIds(teamIDs)
|
||||
@@ -332,10 +332,10 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
|
||||
for _, teamID := range teamIDs {
|
||||
team, ok := teamsByID[teamID]
|
||||
if !ok || team.Status == enums.StatusDeleted {
|
||||
return nil, errorsx.InvalidParam("客服组不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0169")
|
||||
}
|
||||
if !slices.Contains(enums.StatusValues, team.Status) {
|
||||
return nil, errorsx.InvalidParam("客服组状态不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0174")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
|
||||
continue
|
||||
}
|
||||
if len(candidates) >= maxAgentTeamScheduleBatchItems {
|
||||
return nil, errorsx.InvalidParam(fmt.Sprintf("单次最多生成 %d 条排班", maxAgentTeamScheduleBatchItems))
|
||||
return nil, errorsx.InvalidParamI18n("error.agentTeamSchedule.batchLimit", maxAgentTeamScheduleBatchItems)
|
||||
}
|
||||
candidates = append(candidates, batchScheduleCandidate{
|
||||
TeamID: teamID,
|
||||
@@ -365,7 +365,7 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
|
||||
}
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return nil, errorsx.InvalidParam("未生成任何排班")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0232")
|
||||
}
|
||||
return candidates, nil
|
||||
}
|
||||
@@ -430,11 +430,11 @@ func buildBatchPreviewResult(candidates []batchScheduleCandidate, conflicts map[
|
||||
}
|
||||
}
|
||||
|
||||
func (s *agentTeamScheduleService) findBatchConflict(candidates []batchScheduleCandidate) map[int]string {
|
||||
return s.findBatchConflictByDB(sqls.DB(), candidates)
|
||||
func (s *agentTeamScheduleService) findBatchConflict(candidates []batchScheduleCandidate, locale string) map[int]string {
|
||||
return s.findBatchConflictByDB(sqls.DB(), candidates, locale)
|
||||
}
|
||||
|
||||
func (s *agentTeamScheduleService) findBatchConflictByDB(db *gorm.DB, candidates []batchScheduleCandidate) map[int]string {
|
||||
func (s *agentTeamScheduleService) findBatchConflictByDB(db *gorm.DB, candidates []batchScheduleCandidate, locale string) map[int]string {
|
||||
conflicts := make(map[int]string)
|
||||
if len(candidates) == 0 {
|
||||
return conflicts
|
||||
@@ -458,7 +458,7 @@ func (s *agentTeamScheduleService) findBatchConflictByDB(db *gorm.DB, candidates
|
||||
continue
|
||||
}
|
||||
if item.StartAt.Before(candidate.EndAt) && item.EndAt.After(candidate.StartAt) {
|
||||
conflicts[i] = fmt.Sprintf("该客服组在 %s 至 %s 已存在排班", item.StartAt.Format(time.DateTime), item.EndAt.Format(time.DateTime))
|
||||
conflicts[i] = i18nx.Getf(locale, "error.agentTeamSchedule.conflictRange", item.StartAt.Format(time.DateTime), item.EndAt.Format(time.DateTime))
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -471,7 +471,7 @@ func normalizeBatchWeekdays(values []int) ([]int, error) {
|
||||
ret := make([]int, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value < 1 || value > 7 {
|
||||
return nil, errorsx.InvalidParam("星期必须在 1 到 7 之间")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0228")
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
continue
|
||||
@@ -480,7 +480,7 @@ func normalizeBatchWeekdays(values []int) ([]int, error) {
|
||||
ret = append(ret, value)
|
||||
}
|
||||
if len(ret) == 0 {
|
||||
return nil, errorsx.InvalidParam("请选择星期")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0329")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@@ -532,7 +532,7 @@ func parseDateTimeValue(value string) (time.Time, error) {
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, errorsx.InvalidParam("时间格式错误")
|
||||
return time.Time{}, errorsx.InvalidParamI18n("error.e0227")
|
||||
}
|
||||
|
||||
func startOfLocalDay(value time.Time) time.Time {
|
||||
|
||||
@@ -79,7 +79,7 @@ func (s *agentTeamService) Delete(id int64) {
|
||||
|
||||
func (s *agentTeamService) CreateAgentTeam(req request.CreateAgentTeamRequest, operator *dto.AuthPrincipal) (*models.AgentTeam, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item, err := s.buildTeamModel(0, req.Name, req.LeaderUserID, req.Status, req.Description, req.Remark)
|
||||
if err != nil {
|
||||
@@ -94,11 +94,11 @@ func (s *agentTeamService) CreateAgentTeam(req request.CreateAgentTeamRequest, o
|
||||
|
||||
func (s *agentTeamService) UpdateAgentTeam(req request.UpdateAgentTeamRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil || current.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("客服组不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0169")
|
||||
}
|
||||
item, err := s.buildTeamModel(req.ID, req.Name, req.LeaderUserID, req.Status, req.Description, req.Remark)
|
||||
if err != nil {
|
||||
@@ -119,17 +119,17 @@ func (s *agentTeamService) UpdateAgentTeam(req request.UpdateAgentTeamRequest, o
|
||||
|
||||
func (s *agentTeamService) DeleteAgentTeam(id int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(id)
|
||||
if current == nil || current.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("客服组不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0169")
|
||||
}
|
||||
if AgentProfileService.Take("team_id = ?", id) != nil {
|
||||
return errorsx.Forbidden("客服组下仍有关联客服档案,无法删除")
|
||||
return errorsx.ForbiddenI18n("error.e0167")
|
||||
}
|
||||
if AgentTeamScheduleService.Take("team_id = ?", id) != nil {
|
||||
return errorsx.Forbidden("客服组下仍有关联组排班,无法删除")
|
||||
return errorsx.ForbiddenI18n("error.e0168")
|
||||
}
|
||||
if AIAgentService.Take(
|
||||
"(team_ids = ? OR team_ids LIKE ? OR team_ids LIKE ? OR team_ids LIKE ?) AND status <> ?",
|
||||
@@ -139,7 +139,7 @@ func (s *agentTeamService) DeleteAgentTeam(id int64, operator *dto.AuthPrincipal
|
||||
"%,"+utils.JoinInt64s([]int64{id})+",%",
|
||||
enums.StatusDeleted,
|
||||
) != nil {
|
||||
return errorsx.Forbidden("客服组下仍有关联 AI Agent,无法删除")
|
||||
return errorsx.ForbiddenI18n("error.e0166")
|
||||
}
|
||||
return repositories.AgentTeamRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
"status": enums.StatusDeleted,
|
||||
@@ -152,16 +152,16 @@ func (s *agentTeamService) DeleteAgentTeam(id int64, operator *dto.AuthPrincipal
|
||||
func (s *agentTeamService) buildTeamModel(id int64, name string, leaderUserID int64, status int, description, remark string) (*models.AgentTeam, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("客服组名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0170")
|
||||
}
|
||||
if exists := s.Take("name = ? AND status <> ? AND id <> ?", name, enums.StatusDeleted, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("客服组名称已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0171")
|
||||
}
|
||||
if leaderUserID > 0 && UserService.Get(leaderUserID) == nil {
|
||||
return nil, errorsx.InvalidParam("组长用户不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0294")
|
||||
}
|
||||
if status != 0 && status != 1 {
|
||||
return nil, errorsx.InvalidParam("客服组状态不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0174")
|
||||
}
|
||||
return &models.AgentTeam{
|
||||
Name: name,
|
||||
|
||||
@@ -66,7 +66,7 @@ func (s *aIAgentService) FindByIds(ids []int64) []models.AIAgent {
|
||||
|
||||
func (s *aIAgentService) CreateAIAgent(req request.CreateAIAgentRequest, operator *dto.AuthPrincipal) (*models.AIAgent, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item, err := s.buildAIAgentModel(0, req)
|
||||
if err != nil {
|
||||
@@ -83,10 +83,10 @@ func (s *aIAgentService) CreateAIAgent(req request.CreateAIAgentRequest, operato
|
||||
|
||||
func (s *aIAgentService) UpdateAIAgent(req request.UpdateAIAgentRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if s.Get(req.ID) == nil {
|
||||
return errorsx.InvalidParam("AI Agent 不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0002")
|
||||
}
|
||||
item, err := s.buildAIAgentModel(req.ID, req.CreateAIAgentRequest)
|
||||
if err != nil {
|
||||
@@ -117,10 +117,10 @@ func (s *aIAgentService) UpdateAIAgent(req request.UpdateAIAgentRequest, operato
|
||||
func (s *aIAgentService) DeleteAIAgent(id int64, operator *dto.AuthPrincipal) error {
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("AI Agent 不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0002")
|
||||
}
|
||||
if ChannelService.Take("ai_agent_id = ?", id) != nil {
|
||||
return errorsx.Forbidden("已有接入渠道绑定该 AI Agent,无法删除")
|
||||
return errorsx.ForbiddenI18n("error.e0185")
|
||||
}
|
||||
return repositories.AIAgentRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
"status": enums.StatusDeleted,
|
||||
@@ -133,23 +133,23 @@ func (s *aIAgentService) DeleteAIAgent(id int64, operator *dto.AuthPrincipal) er
|
||||
func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRequest) (*models.AIAgent, error) {
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("AI Agent 名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0005")
|
||||
}
|
||||
if exists := s.Take("name = ? AND id <> ?", name, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("AI Agent 名称已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0006")
|
||||
}
|
||||
if req.AIConfigID <= 0 {
|
||||
return nil, errorsx.InvalidParam("AI 配置不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0010")
|
||||
}
|
||||
aiConfig := AIConfigService.Get(req.AIConfigID)
|
||||
if aiConfig == nil {
|
||||
return nil, errorsx.InvalidParam("AI 配置不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0009")
|
||||
}
|
||||
if aiConfig.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("AI 配置未启用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0011")
|
||||
}
|
||||
if !slices.Contains(enums.IMConversationServiceModeValues, req.ServiceMode) {
|
||||
return nil, errorsx.InvalidParam("服务模式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0230")
|
||||
}
|
||||
teamIDs, err := s.normalizeTeamIDs(req.TeamIDs)
|
||||
if err != nil {
|
||||
@@ -157,19 +157,19 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
}
|
||||
|
||||
if !slices.Contains(enums.AIAgentHandoffModeValues, enums.AIAgentHandoffMode(req.HandoffMode)) {
|
||||
return nil, errorsx.InvalidParam("转人工模式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0336")
|
||||
}
|
||||
if req.FallbackMode == 0 {
|
||||
req.FallbackMode = enums.AIAgentFallbackModeNoAnswer
|
||||
}
|
||||
if !slices.Contains(enums.AIAgentFallbackModeValues, enums.AIAgentFallbackMode(req.FallbackMode)) {
|
||||
return nil, errorsx.InvalidParam("兜底策略不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0123")
|
||||
}
|
||||
if enums.AIAgentHandoffMode(req.HandoffMode) == enums.AIAgentHandoffModeDefaultTeamPool && len(teamIDs) == 0 {
|
||||
return nil, errorsx.InvalidParam("默认客服组待接入池模式必须至少选择一个客服组")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0347")
|
||||
}
|
||||
if req.ReplyTimeoutSeconds < 0 {
|
||||
return nil, errorsx.InvalidParam("回复超时秒数不能小于 0")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0144")
|
||||
}
|
||||
|
||||
knowledgeIDs, err := s.normalizeKnowledgeIDs(req.KnowledgeIDs)
|
||||
@@ -177,7 +177,7 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
return nil, err
|
||||
}
|
||||
if len(knowledgeIDs) == 0 {
|
||||
return nil, errorsx.InvalidParam("请至少选择一个知识库")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0320")
|
||||
}
|
||||
skillIDs, err := s.normalizeSkillIDs(req.SkillIDs)
|
||||
if err != nil {
|
||||
@@ -195,7 +195,7 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
if len(directTools) > 0 {
|
||||
buf, marshalErr := json.Marshal(directTools)
|
||||
if marshalErr != nil {
|
||||
return nil, errorsx.InvalidParam("Direct Tools 配置格式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0021")
|
||||
}
|
||||
directToolsJSON = string(buf)
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
if len(graphTools) > 0 {
|
||||
buf, marshalErr := json.Marshal(graphTools)
|
||||
if marshalErr != nil {
|
||||
return nil, errorsx.InvalidParam("Graph Tools 配置格式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0028")
|
||||
}
|
||||
graphToolsJSON = string(buf)
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func (s *aIAgentService) normalizeTeamIDs(input []int64) ([]int64, error) {
|
||||
continue
|
||||
}
|
||||
// if team.Status != enums.StatusOk {
|
||||
// return nil, errorsx.InvalidParam("客服组未启用")
|
||||
// return nil, errorsx.InvalidParamI18n("error.e0173")
|
||||
// }
|
||||
seen[id] = struct{}{}
|
||||
ret = append(ret, id)
|
||||
@@ -265,7 +265,7 @@ func (s *aIAgentService) normalizeKnowledgeIDs(input []int64) ([]int64, error) {
|
||||
continue
|
||||
}
|
||||
// if kb.Status != enums.StatusOk {
|
||||
// return nil, errorsx.InvalidParam("知识库未启用")
|
||||
// return nil, errorsx.InvalidParamI18n("error.e0285")
|
||||
// }
|
||||
seen[id] = struct{}{}
|
||||
ret = append(ret, id)
|
||||
@@ -288,7 +288,7 @@ func (s *aIAgentService) normalizeSkillIDs(input []int64) ([]int64, error) {
|
||||
continue
|
||||
}
|
||||
// if skill.Status != enums.StatusOk {
|
||||
// return nil, errorsx.InvalidParam("Skill 未启用")
|
||||
// return nil, errorsx.InvalidParamI18n("error.e0056")
|
||||
// }
|
||||
seen[id] = struct{}{}
|
||||
ret = append(ret, id)
|
||||
@@ -311,7 +311,7 @@ func (s *aIAgentService) normalizeDirectTools(input []request.AIAgentMCPToolRequ
|
||||
continue
|
||||
}
|
||||
if toolx.ResolveToolSourceType(normalized.ToolCode) != enums.ToolSourceTypeMCP {
|
||||
return nil, errorsx.InvalidParam("Direct Tools 仅允许配置 MCP 工具")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0020")
|
||||
}
|
||||
if err := ToolCatalogService.ValidateToolCode(normalized.ToolCode); err != nil {
|
||||
return nil, err
|
||||
@@ -338,7 +338,7 @@ func (s *aIAgentService) normalizeGraphTools(input []string) ([]string, error) {
|
||||
continue
|
||||
}
|
||||
if !toolx.IsAgentDirectGraphToolCode(toolCode) {
|
||||
return nil, errorsx.InvalidParam("Graph Tools 仅允许配置 Graph Tool")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0027")
|
||||
}
|
||||
if _, exists := seen[toolCode]; exists {
|
||||
continue
|
||||
@@ -362,14 +362,14 @@ func (s *aIAgentService) UpdateSort(ids []int64) error {
|
||||
|
||||
func (s *aIAgentService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("AI Agent 不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0002")
|
||||
}
|
||||
if status != int(enums.StatusOk) && status != int(enums.StatusDisabled) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
|
||||
return repositories.AIAgentRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
|
||||
@@ -77,7 +77,7 @@ func (s *aIConfigService) Delete(id int64) {
|
||||
|
||||
func (s *aIConfigService) CreateAIConfig(req request.CreateAIConfigRequest, operator *dto.AuthPrincipal) (*models.AIConfig, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item, err := s.buildAIConfigModel(req)
|
||||
if err != nil {
|
||||
@@ -100,11 +100,11 @@ func (s *aIConfigService) CreateAIConfig(req request.CreateAIConfigRequest, oper
|
||||
|
||||
func (s *aIConfigService) UpdateAIConfig(req request.UpdateAIConfigRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("AI配置不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0012")
|
||||
}
|
||||
item, err := s.buildAIConfigModel(req.CreateAIConfigRequest)
|
||||
if err != nil {
|
||||
@@ -141,7 +141,7 @@ func (s *aIConfigService) DeleteAIConfig(id int64, operator *dto.AuthPrincipal)
|
||||
return nil
|
||||
}
|
||||
if current.Status == enums.StatusOk {
|
||||
return errorsx.Forbidden("启用中的AI配置不允许删除")
|
||||
return errorsx.ForbiddenI18n("error.e0143")
|
||||
}
|
||||
return repositories.AIConfigRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
"status": enums.StatusDeleted,
|
||||
@@ -153,14 +153,14 @@ func (s *aIConfigService) DeleteAIConfig(id int64, operator *dto.AuthPrincipal)
|
||||
|
||||
func (s *aIConfigService) UpdateStatus(id int64, status enums.Status, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("AI配置不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0012")
|
||||
}
|
||||
if status != enums.StatusOk && status != enums.StatusDisabled {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
@@ -206,19 +206,19 @@ func (s *aIConfigService) buildAIConfigModel(req request.CreateAIConfigRequest)
|
||||
modelName := strings.TrimSpace(req.ModelName)
|
||||
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("配置名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0339")
|
||||
}
|
||||
if strs.IsBlank(string(req.Provider)) {
|
||||
return nil, errorsx.InvalidParam("供应商不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0122")
|
||||
}
|
||||
if baseURL == "" {
|
||||
return nil, errorsx.InvalidParam("基础地址不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0147")
|
||||
}
|
||||
if strs.IsBlank(string(req.ModelType)) {
|
||||
return nil, errorsx.InvalidParam("模型类型不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0243")
|
||||
}
|
||||
if modelName == "" {
|
||||
return nil, errorsx.InvalidParam("模型名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0242")
|
||||
}
|
||||
if req.Dimension < 0 {
|
||||
req.Dimension = 0
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *assetService) FindPageByCnd(cnd *sqls.Cnd) (list []models.Asset, paging
|
||||
func (s *assetService) OpenReader(asset *models.Asset) (io.ReadCloser, error) {
|
||||
cfg := config.Current()
|
||||
if asset == nil {
|
||||
return nil, errorsx.InvalidParam("图片资源不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0146")
|
||||
}
|
||||
switch asset.Provider {
|
||||
case "", enums.AssetProviderLocal:
|
||||
@@ -56,7 +56,7 @@ func (s *assetService) OpenReader(asset *models.Asset) (io.ReadCloser, error) {
|
||||
case enums.AssetProviderOSS:
|
||||
return storage.NewOSSStorage(cfg.Storage.OSS).Read(asset.StorageKey)
|
||||
default:
|
||||
return nil, errorsx.InvalidParam("当前暂不支持该存储类型的文件读取")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0195")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ func (s *assetService) UploadBytes(data []byte, prefix, filename string, princip
|
||||
|
||||
func (s *assetService) UploadFile(file *multipart.FileHeader, prefix string, principal *dto.AuthPrincipal) (*models.Asset, error) {
|
||||
if file == nil {
|
||||
return nil, errorsx.InvalidParam("请选择上传文件")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0323")
|
||||
}
|
||||
|
||||
cfg := config.Current()
|
||||
if file.Size > cfg.Storage.MaxUploadSizeBytes() {
|
||||
return nil, errorsx.InvalidParam("上传文件超过大小限制")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0079")
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
@@ -137,10 +137,10 @@ func (s *assetService) Upload(reader io.Reader, info storage.UploadInfo) (*model
|
||||
func (s *assetService) GetSignedURL(id int64) (string, error) {
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return "", errorsx.InvalidParam("文件不存在")
|
||||
return "", errorsx.InvalidParamI18n("error.e0214")
|
||||
}
|
||||
if item.Status != enums.AssetStatusSuccess {
|
||||
return "", errorsx.InvalidParam("文件不可访问")
|
||||
return "", errorsx.InvalidParamI18n("error.e0213")
|
||||
}
|
||||
|
||||
provider, err := storage.NewProvider(item.Provider)
|
||||
@@ -153,11 +153,11 @@ func (s *assetService) GetSignedURL(id int64) (string, error) {
|
||||
|
||||
func (s *assetService) DeleteAsset(id int64, principal *dto.AuthPrincipal) error {
|
||||
if principal == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("文件不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0214")
|
||||
}
|
||||
return repositories.AssetRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
"status": enums.AssetStatusDeleted,
|
||||
|
||||
@@ -70,11 +70,11 @@ func (s *authService) RequirePermission(ctx *gin.Context, permission constants.P
|
||||
}
|
||||
|
||||
if principal == nil {
|
||||
return nil, errorsx.Forbidden("无权限执行该操作")
|
||||
return nil, errorsx.ForbiddenI18n("error.e0225")
|
||||
}
|
||||
|
||||
if !s.HasPermission(ctx, permission.Code) {
|
||||
return principal, errorsx.Forbidden("无权限执行该操作")
|
||||
return principal, errorsx.ForbiddenI18n("error.e0225")
|
||||
}
|
||||
return principal, nil
|
||||
}
|
||||
@@ -84,22 +84,22 @@ func (s *authService) Login(req request.LoginRequest, authCfg config.AuthConfig,
|
||||
principal := normalizeLoginPrincipal(username)
|
||||
password := req.Password
|
||||
if username == "" || strings.TrimSpace(password) == "" {
|
||||
return nil, errorsx.InvalidParam("用户名和密码不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0258")
|
||||
}
|
||||
|
||||
if s.isCredentialLocked(principal, authCfg) {
|
||||
_ = s.createLoginCredentialLog(principal, 0, false, clientIP, userAgent, "credential locked")
|
||||
return nil, errorsx.CredentialLocked("登录失败次数过多,请稍后再试")
|
||||
return nil, errorsx.CredentialLockedI18n("error.e0270")
|
||||
}
|
||||
|
||||
user := UserService.GetByUsername(username)
|
||||
if user == nil || user.Status != enums.StatusOk {
|
||||
_ = s.createLoginCredentialLog(principal, 0, false, clientIP, userAgent, "user not found")
|
||||
return nil, errorsx.InvalidAccount("用户名或密码错误")
|
||||
return nil, errorsx.InvalidAccountI18n("error.e0260")
|
||||
}
|
||||
if strs.IsBlank(user.Password) || bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) != nil {
|
||||
_ = s.createLoginCredentialLog(principal, user.ID, false, clientIP, userAgent, "password mismatch")
|
||||
return nil, errorsx.InvalidAccount("用户名或密码错误")
|
||||
return nil, errorsx.InvalidAccountI18n("error.e0260")
|
||||
}
|
||||
|
||||
var ret *response.LoginResponse
|
||||
@@ -153,7 +153,7 @@ func (s *authService) Authenticate(ctx *gin.Context) (*dto.AuthPrincipal, error)
|
||||
token = strings.TrimSpace(ctx.Query("accessToken"))
|
||||
}
|
||||
if token == "" {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
|
||||
session, err := s.validateSessionToken(token)
|
||||
@@ -163,7 +163,7 @@ func (s *authService) Authenticate(ctx *gin.Context) (*dto.AuthPrincipal, error)
|
||||
|
||||
user := UserService.Get(session.UserID)
|
||||
if user == nil || user.Status != enums.StatusOk {
|
||||
return nil, errorsx.Unauthorized("用户不存在或已被禁用")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0256")
|
||||
}
|
||||
|
||||
roles, permissions, err := s.loadUserAuthScope(sqls.DB(), user.ID)
|
||||
@@ -276,17 +276,17 @@ func (s *authService) resolveTokenTTL(authCfg config.AuthConfig) time.Duration {
|
||||
|
||||
func (s *authService) validateSessionToken(token string) (*models.LoginSession, error) {
|
||||
if strings.TrimSpace(token) == "" {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
session := LoginSessionService.FindOne(sqls.NewCnd().Eq("token", token))
|
||||
if session == nil {
|
||||
return nil, errorsx.InvalidToken("登录凭证无效")
|
||||
return nil, errorsx.InvalidTokenI18n("error.e0269")
|
||||
}
|
||||
if session.RevokedAt != nil {
|
||||
return nil, errorsx.InvalidToken("登录凭证已失效")
|
||||
return nil, errorsx.InvalidTokenI18n("error.e0267")
|
||||
}
|
||||
if time.Now().After(session.ExpiredAt) {
|
||||
return nil, errorsx.InvalidToken("登录凭证已过期")
|
||||
return nil, errorsx.InvalidTokenI18n("error.e0268")
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (s *channelService) UpdateColumn(id int64, name string, value interface{})
|
||||
|
||||
func (s *channelService) CreateChannel(req request.CreateChannelRequest, operator *dto.AuthPrincipal) (*models.Channel, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item, err := s.buildChannelModel(0, req)
|
||||
if err != nil {
|
||||
@@ -95,11 +95,11 @@ func (s *channelService) CreateChannel(req request.CreateChannelRequest, operato
|
||||
|
||||
func (s *channelService) UpdateChannel(req request.UpdateChannelRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil || current.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("接入渠道不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0208")
|
||||
}
|
||||
item, err := s.buildChannelModel(req.ID, req.CreateChannelRequest)
|
||||
if err != nil {
|
||||
@@ -121,14 +121,14 @@ func (s *channelService) UpdateChannel(req request.UpdateChannelRequest, operato
|
||||
|
||||
func (s *channelService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(id)
|
||||
if item == nil || item.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("接入渠道不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0208")
|
||||
}
|
||||
if status != int(enums.StatusOk) && status != int(enums.StatusDisabled) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
return s.Updates(id, map[string]any{
|
||||
"status": status,
|
||||
@@ -140,11 +140,11 @@ func (s *channelService) UpdateStatus(id int64, status int, operator *dto.AuthPr
|
||||
|
||||
func (s *channelService) DeleteChannel(id int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(id)
|
||||
if item == nil || item.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("接入渠道不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0208")
|
||||
}
|
||||
return s.Updates(id, map[string]any{
|
||||
"status": enums.StatusDeleted,
|
||||
@@ -233,7 +233,7 @@ func (s *channelService) ParseWebChannelConfig(raw string) (*dto.WebChannelConfi
|
||||
cfg.Position = "right"
|
||||
}
|
||||
if cfg.Position != "left" && cfg.Position != "right" {
|
||||
return nil, errorsx.InvalidParam("Web渠道配置 position 只能为 left 或 right")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0059")
|
||||
}
|
||||
cfg.Width = strings.TrimSpace(cfg.Width)
|
||||
if cfg.Width == "" {
|
||||
@@ -292,14 +292,14 @@ func (s *channelService) GetUserTokenSecret(channel *models.Channel) string {
|
||||
|
||||
func (s *channelService) ResetUserTokenSecret(channelID int64, operator *dto.AuthPrincipal) (string, error) {
|
||||
if operator == nil {
|
||||
return "", errorsx.Unauthorized("未登录或登录已过期")
|
||||
return "", errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
channel := s.Get(channelID)
|
||||
if channel == nil || channel.Status == enums.StatusDeleted {
|
||||
return "", errorsx.InvalidParam("接入渠道不存在")
|
||||
return "", errorsx.InvalidParamI18n("error.e0208")
|
||||
}
|
||||
if channel.ChannelType != enums.ChannelTypeWeb && channel.ChannelType != enums.ChannelTypeWechatMP {
|
||||
return "", errorsx.InvalidParam("当前渠道不支持用户 JWT Secret")
|
||||
return "", errorsx.InvalidParamI18n("error.e0196")
|
||||
}
|
||||
secret, err := generateUserTokenSecret()
|
||||
if err != nil {
|
||||
@@ -385,32 +385,32 @@ func (s *channelService) GetEnabledChannel(ctx *gin.Context) *models.Channel {
|
||||
func (s *channelService) buildChannelModel(id int64, req request.CreateChannelRequest) (*models.Channel, error) {
|
||||
channelType := strings.TrimSpace(req.ChannelType)
|
||||
if channelType != enums.ChannelTypeWeb && channelType != enums.ChannelTypeWechatMP && channelType != enums.ChannelTypeWxWorkKF {
|
||||
return nil, errorsx.InvalidParam("渠道类型不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0250")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("渠道名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0247")
|
||||
}
|
||||
if req.AIAgentID <= 0 {
|
||||
return nil, errorsx.InvalidParam("请选择 AI Agent")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0321")
|
||||
}
|
||||
aiAgent := AIAgentService.Get(req.AIAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("AI Agent 不存在或未启用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0004")
|
||||
}
|
||||
status := enums.Status(req.Status)
|
||||
if req.Status == 0 {
|
||||
status = enums.StatusOk
|
||||
}
|
||||
if status != enums.StatusOk && status != enums.StatusDisabled {
|
||||
return nil, errorsx.InvalidParam("渠道状态不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0249")
|
||||
}
|
||||
|
||||
channelID := ""
|
||||
if id > 0 {
|
||||
current := s.Get(id)
|
||||
if current == nil || current.Status == enums.StatusDeleted {
|
||||
return nil, errorsx.InvalidParam("接入渠道不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0208")
|
||||
}
|
||||
channelID = strings.TrimSpace(current.ChannelID)
|
||||
}
|
||||
@@ -421,11 +421,11 @@ func (s *channelService) buildChannelModel(id int64, req request.CreateChannelRe
|
||||
channelID = strs.UUID()
|
||||
}
|
||||
if exists := s.Take("channel_id = ? AND status <> ? AND id <> ?", channelID, enums.StatusDeleted, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("渠道标识已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0248")
|
||||
}
|
||||
cfg, err := s.ParseWebChannelConfig(configJSON)
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("Web渠道配置不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0060")
|
||||
}
|
||||
if strings.TrimSpace(cfg.UserTokenSecret) == "" {
|
||||
secret, err := generateUserTokenSecret()
|
||||
@@ -444,11 +444,11 @@ func (s *channelService) buildChannelModel(id int64, req request.CreateChannelRe
|
||||
channelID = strs.UUID()
|
||||
}
|
||||
if exists := s.Take("channel_id = ? AND status <> ? AND id <> ?", channelID, enums.StatusDeleted, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("渠道标识已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0248")
|
||||
}
|
||||
cfg, err := s.ParseWechatMPChannelConfig(configJSON)
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("微信公众号渠道配置不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0201")
|
||||
}
|
||||
if strings.TrimSpace(cfg.UserTokenSecret) == "" {
|
||||
secret, err := generateUserTokenSecret()
|
||||
@@ -467,17 +467,17 @@ func (s *channelService) buildChannelModel(id int64, req request.CreateChannelRe
|
||||
channelID = strs.UUID()
|
||||
}
|
||||
if exists := s.Take("channel_id = ? AND status <> ? AND id <> ?", channelID, enums.StatusDeleted, id); exists != nil {
|
||||
return nil, errorsx.InvalidParam("渠道标识已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0248")
|
||||
}
|
||||
cfg, err := s.ParseWxWorkKFChannelConfig(configJSON)
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("企业微信渠道配置不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0102")
|
||||
}
|
||||
if cfg == nil || cfg.OpenKfID == "" {
|
||||
return nil, errorsx.InvalidParam("企业微信渠道配置缺少 openKfId")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0103")
|
||||
}
|
||||
if channel := s.GetEnabledWxWorkKFChannelByOpenKfID(cfg.OpenKfID); channel != nil && channel.ID != id {
|
||||
return nil, errorsx.InvalidParam("openKfId 已被其他渠道使用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0069")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,16 +58,16 @@ func (s *companyService) Count(cnd *sqls.Cnd) int64 {
|
||||
|
||||
func (s *companyService) CreateCompany(req request.CreateCompanyRequest, operator *dto.AuthPrincipal) (*models.Company, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("公司名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0125")
|
||||
}
|
||||
|
||||
existing := repositories.CompanyRepository.GetByName(sqls.DB(), name)
|
||||
if existing != nil && existing.Status != enums.StatusDeleted {
|
||||
return nil, errorsx.InvalidParam("公司名称已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0126")
|
||||
}
|
||||
|
||||
item := &models.Company{
|
||||
@@ -85,20 +85,20 @@ func (s *companyService) CreateCompany(req request.CreateCompanyRequest, operato
|
||||
|
||||
func (s *companyService) UpdateCompany(req request.UpdateCompanyRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(req.ID)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("公司不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0124")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return errorsx.InvalidParam("公司名称不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0125")
|
||||
}
|
||||
|
||||
existing := repositories.CompanyRepository.GetByName(sqls.DB(), name)
|
||||
if existing != nil && existing.ID != req.ID {
|
||||
return errorsx.InvalidParam("公司名称已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0126")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@@ -118,7 +118,7 @@ func (s *companyService) UpdateCompany(req request.UpdateCompanyRequest, operato
|
||||
func (s *companyService) DeleteCompany(id int64, operator dto.AuthPrincipal) error {
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("公司不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0124")
|
||||
}
|
||||
|
||||
return repositories.CompanyRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
@@ -131,14 +131,14 @@ func (s *companyService) DeleteCompany(id int64, operator dto.AuthPrincipal) err
|
||||
|
||||
func (s *companyService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("公司不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0124")
|
||||
}
|
||||
if status != int(enums.StatusOk) && status != int(enums.StatusDisabled) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
now := time.Now()
|
||||
return repositories.CompanyRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *conversationHumanDispatchService) TryOffHoursHandoffByAI(conversationID
|
||||
func (s *conversationHumanDispatchService) TryOffHoursHandoffByAIWithRequestID(conversationID int64, aiAgent models.AIAgent, reason string, requestID string) (bool, error) {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return false, errorsx.InvalidParam("会话不存在")
|
||||
return false, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
teamIDs := orderedPositiveIDs(aiAgent.TeamIDs)
|
||||
activeTeamIDs := ConversationDispatchService.findActiveScheduleTeamIDs(teamIDs, time.Now())
|
||||
@@ -75,7 +75,7 @@ func (s *conversationHumanDispatchService) HandoffByAI(conversationID int64, aiA
|
||||
func (s *conversationHumanDispatchService) HandoffByAIWithRequestID(conversationID int64, aiAgent models.AIAgent, reason string, requestID string) (*HandoffDecisionResult, error) {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
teamIDs := orderedPositiveIDs(aiAgent.TeamIDs)
|
||||
activeTeamIDs := ConversationDispatchService.findActiveScheduleTeamIDs(teamIDs, time.Now())
|
||||
@@ -110,10 +110,10 @@ func (s *conversationHumanDispatchService) ApplyHumanOnlyCreate(conversationID i
|
||||
func (s *conversationHumanDispatchService) DispatchPendingConversation(conversationID int64, aiAgent models.AIAgent) (*HandoffDecisionResult, error) {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusPending || conversation.CurrentAssigneeID > 0 {
|
||||
return nil, errorsx.InvalidParam("只有待接入未分配会话允许自动分配")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0137")
|
||||
}
|
||||
activeTeamIDs := ConversationDispatchService.findActiveScheduleTeamIDs(orderedPositiveIDs(aiAgent.TeamIDs), time.Now())
|
||||
if len(activeTeamIDs) == 0 {
|
||||
@@ -227,7 +227,7 @@ func (s *conversationHumanDispatchService) moveToTeamPoolWithRequestID(conversat
|
||||
err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
current := repositories.ConversationRepository.Get(ctx.Tx, conversationID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if err := ConversationAssignmentService.FinishActiveAssignments(ctx, conversationID, now); err != nil {
|
||||
return err
|
||||
@@ -273,7 +273,7 @@ func (s *conversationHumanDispatchService) moveToGlobalPool(conversationID int64
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
conversation := repositories.ConversationRepository.Get(ctx.Tx, conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if err := repositories.ConversationRepository.Updates(ctx.Tx, conversationID, map[string]any{
|
||||
"status": enums.IMConversationStatusPending,
|
||||
|
||||
@@ -37,7 +37,7 @@ type readerCursor struct {
|
||||
|
||||
func agentReaderCursor(operator *dto.AuthPrincipal) (readerCursor, error) {
|
||||
if operator == nil {
|
||||
return readerCursor{}, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return readerCursor{}, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
return readerCursor{
|
||||
readerType: enums.IMSenderTypeAgent,
|
||||
@@ -50,7 +50,7 @@ func agentReaderCursor(operator *dto.AuthPrincipal) (readerCursor, error) {
|
||||
|
||||
func customerReaderCursor(external *openidentity.ExternalUser) (readerCursor, error) {
|
||||
if external == nil || strings.TrimSpace(external.ExternalID) == "" {
|
||||
return readerCursor{}, errorsx.Unauthorized("外部用户标识不能为空")
|
||||
return readerCursor{}, errorsx.UnauthorizedI18n("error.e0149")
|
||||
}
|
||||
extID := strings.TrimSpace(external.ExternalID)
|
||||
name := strings.TrimSpace(external.ExternalName)
|
||||
@@ -195,7 +195,7 @@ func (s *conversationReadStateService) markReadTxWithCursor(ctx *sqls.TxContext,
|
||||
return nil, nil
|
||||
}
|
||||
if c.readerType != enums.IMSenderTypeAgent && c.readerType != enums.IMSenderTypeCustomer {
|
||||
return nil, errorsx.InvalidParam("不支持的已读操作类型")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0081")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *conversationService) ListConversations(userID int64, filter request.Age
|
||||
case request.AgentConversationFilterClosed:
|
||||
cnd.Eq("current_assignee_id", userID).Eq("status", enums.IMConversationStatusClosed).Desc("last_active_at").Desc("id")
|
||||
default:
|
||||
return nil, nil, errorsx.InvalidParam("会话筛选项不合法")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0121")
|
||||
}
|
||||
|
||||
list, paging := repositories.ConversationRepository.FindPageByCnd(sqls.DB(), cnd)
|
||||
@@ -103,7 +103,7 @@ func (s *conversationService) getLatestNotFinishedByCustomerID(db *gorm.DB, cust
|
||||
func (s *conversationService) Create(externalUser openidentity.ExternalUser, channelID, aiAgentID int64) (*models.Conversation, error) {
|
||||
aiAgent := AIAgentService.Get(aiAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("AI Agent 不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0002")
|
||||
}
|
||||
|
||||
var conversation *models.Conversation
|
||||
@@ -184,20 +184,20 @@ func (s *conversationService) Create(externalUser openidentity.ExternalUser, cha
|
||||
|
||||
func (s *conversationService) AssignConversation(req request.AssignConversationRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
targetProfile := AgentProfileService.GetByUserID(req.AssigneeID)
|
||||
if targetProfile == nil || targetProfile.Status != enums.StatusOk {
|
||||
return errorsx.InvalidParam("目标客服不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0276")
|
||||
}
|
||||
var assignedEvent events.ConversationAssignedEvent
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
conversation := repositories.ConversationRepository.Get(ctx.Tx, req.ConversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusPending {
|
||||
return errorsx.InvalidParam("只有待接入会话允许分配")
|
||||
return errorsx.InvalidParamI18n("error.e0135")
|
||||
}
|
||||
now := time.Now()
|
||||
if err := ConversationAssignmentService.FinishActiveAssignments(ctx, req.ConversationID, now); err != nil {
|
||||
@@ -245,62 +245,62 @@ func (s *conversationService) AssignConversation(req request.AssignConversationR
|
||||
|
||||
func (s *conversationService) AutoAssignConversation(conversationID int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
|
||||
conversation := s.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusPending {
|
||||
return errorsx.InvalidParam("只有待接入会话允许自动分配")
|
||||
return errorsx.InvalidParamI18n("error.e0136")
|
||||
}
|
||||
if conversation.CurrentAssigneeID > 0 {
|
||||
return errorsx.InvalidParam("当前会话已分配客服")
|
||||
return errorsx.InvalidParamI18n("error.e0190")
|
||||
}
|
||||
|
||||
aiAgent := AIAgentService.Get(conversation.AIAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return errorsx.InvalidParam("AI Agent 不存在或已停用")
|
||||
return errorsx.InvalidParamI18n("error.e0003")
|
||||
}
|
||||
result, err := ConversationHumanDispatchService.DispatchPendingConversation(conversationID, *aiAgent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == nil || result.Decision == HandoffDecisionOffHours {
|
||||
return errorsx.InvalidParam("当前暂不在人工客服服务时间内")
|
||||
return errorsx.InvalidParamI18n("error.e0194")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *conversationService) TransferConversation(conversationID, toUserID int64, reason string, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if toUserID <= 0 {
|
||||
return errorsx.InvalidParam("目标客服不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0278")
|
||||
}
|
||||
targetProfile := AgentProfileService.GetByUserID(toUserID)
|
||||
if targetProfile == nil || targetProfile.Status != enums.StatusOk {
|
||||
return errorsx.InvalidParam("目标客服不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0276")
|
||||
}
|
||||
var assignedEvent events.ConversationAssignedEvent
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
conversation := repositories.ConversationRepository.Get(ctx.Tx, conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if !s.canTransferConversation(conversation, operator) {
|
||||
return errorsx.Forbidden("无权转接该会话")
|
||||
return errorsx.ForbiddenI18n("error.e0223")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusActive {
|
||||
return errorsx.InvalidParam("只有处理中会话允许转接")
|
||||
return errorsx.InvalidParamI18n("error.e0134")
|
||||
}
|
||||
if conversation.CurrentAssigneeID <= 0 {
|
||||
return errorsx.InvalidParam("当前会话未分配客服")
|
||||
return errorsx.InvalidParamI18n("error.e0193")
|
||||
}
|
||||
if conversation.CurrentAssigneeID == toUserID {
|
||||
return errorsx.InvalidParam("目标客服不能与当前指派人相同")
|
||||
return errorsx.InvalidParamI18n("error.e0277")
|
||||
}
|
||||
now := time.Now()
|
||||
if err := ConversationAssignmentService.FinishActiveAssignments(ctx, conversationID, now); err != nil {
|
||||
@@ -352,7 +352,7 @@ func (s *conversationService) HandoffByAI(conversationID int64, aiAgent models.A
|
||||
|
||||
func (s *conversationService) HandoffByAIWithRequestID(conversationID int64, aiAgent models.AIAgent, reason string, requestID string) error {
|
||||
if conversationID <= 0 {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
_, err := ConversationHumanDispatchService.HandoffByAIWithRequestID(conversationID, aiAgent, reason, requestID)
|
||||
if err != nil {
|
||||
@@ -371,7 +371,7 @@ func (s *conversationService) TryOffHoursHandoffByAI(conversationID int64, aiAge
|
||||
|
||||
func (s *conversationService) TryOffHoursHandoffByAIWithRequestID(conversationID int64, aiAgent models.AIAgent, reason string, requestID string) (bool, error) {
|
||||
if conversationID <= 0 {
|
||||
return false, errorsx.InvalidParam("会话不存在")
|
||||
return false, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
handled, err := ConversationHumanDispatchService.TryOffHoursHandoffByAIWithRequestID(conversationID, aiAgent, reason, requestID)
|
||||
if err != nil {
|
||||
@@ -386,7 +386,7 @@ func (s *conversationService) TryOffHoursHandoffByAIWithRequestID(conversationID
|
||||
|
||||
func (s *conversationService) CloseConversation(conversationID int64, closeReason string, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
return s.closeConversation(conversationID, enums.IMSenderTypeAgent, closeReason, operator)
|
||||
}
|
||||
@@ -394,10 +394,10 @@ func (s *conversationService) CloseConversation(conversationID int64, closeReaso
|
||||
func (s *conversationService) CloseCustomerConversation(conversationID int64, externalUser openidentity.ExternalUser) error {
|
||||
conversation := s.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if !s.IsCustomerConversationOwner(conversation, externalUser) {
|
||||
return errorsx.Forbidden("无权访问该会话")
|
||||
return errorsx.ForbiddenI18n("error.e0222")
|
||||
}
|
||||
return s.closeConversation(conversationID, enums.IMSenderTypeCustomer, "", nil)
|
||||
}
|
||||
@@ -406,7 +406,7 @@ func (s *conversationService) closeConversation(conversationID int64, senderType
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
conversation := repositories.ConversationRepository.Get(ctx.Tx, conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.Status == enums.IMConversationStatusClosed {
|
||||
return nil
|
||||
@@ -414,7 +414,7 @@ func (s *conversationService) closeConversation(conversationID int64, senderType
|
||||
if conversation.Status != enums.IMConversationStatusAIServing &&
|
||||
conversation.Status != enums.IMConversationStatusPending &&
|
||||
conversation.Status != enums.IMConversationStatusActive {
|
||||
return errorsx.InvalidParam("当前状态不允许关闭会话")
|
||||
return errorsx.InvalidParamI18n("error.e0197")
|
||||
}
|
||||
var (
|
||||
now = time.Now()
|
||||
@@ -427,13 +427,13 @@ func (s *conversationService) closeConversation(conversationID int64, senderType
|
||||
eventDesc = "客户关闭会话"
|
||||
} else {
|
||||
if operator == nil {
|
||||
return errorsx.InvalidParam("无权限操作")
|
||||
return errorsx.InvalidParamI18n("error.e0226")
|
||||
}
|
||||
if closeReason == "" {
|
||||
return errorsx.InvalidParam("关闭原因不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0128")
|
||||
}
|
||||
if !s.canCloseConversation(conversation, operator) {
|
||||
return errorsx.Forbidden("无权关闭该会话")
|
||||
return errorsx.ForbiddenI18n("error.e0221")
|
||||
}
|
||||
operatorID = operator.UserID
|
||||
operatorName = operator.Nickname
|
||||
@@ -471,11 +471,11 @@ func (s *conversationService) closeConversation(conversationID int64, senderType
|
||||
// MarkAgentConversationReadToMessage 控制台客服将会话已读推进到指定消息。
|
||||
func (s *conversationService) MarkAgentConversationReadToMessage(conversationID, messageID int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
conversation := s.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
changed, err := s.markConversationReadWithActor(conversation, messageID, agentConversationReadActor{operator: operator})
|
||||
if err != nil {
|
||||
@@ -492,14 +492,14 @@ func (s *conversationService) MarkAgentConversationReadToMessage(conversationID,
|
||||
// MarkCustomerConversationReadToMessage IM 客户将会话已读推进到指定消息(需为会话归属外部身份)。
|
||||
func (s *conversationService) MarkCustomerConversationReadToMessage(conversationID, messageID int64, external *openidentity.ExternalUser) error {
|
||||
if external == nil || strings.TrimSpace(external.ExternalID) == "" {
|
||||
return errorsx.Unauthorized("外部用户标识不能为空")
|
||||
return errorsx.UnauthorizedI18n("error.e0149")
|
||||
}
|
||||
conversation := s.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if !s.IsCustomerConversationOwner(conversation, *external) {
|
||||
return errorsx.Forbidden("无权访问该会话")
|
||||
return errorsx.ForbiddenI18n("error.e0222")
|
||||
}
|
||||
changed, err := s.markConversationReadWithActor(conversation, messageID, customerConversationReadActor{external: external})
|
||||
if err != nil {
|
||||
@@ -574,7 +574,7 @@ func (a customerConversationReadActor) conversationUpdateAudit() (int64, string)
|
||||
|
||||
func (s *conversationService) markConversationReadWithActor(conversation *models.Conversation, messageID int64, actor conversationReadActor) (bool, error) {
|
||||
if conversation == nil {
|
||||
return false, errorsx.InvalidParam("会话不存在")
|
||||
return false, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
targetMessage, err := MessageService.GetConversationReadTarget(conversation.ID, messageID)
|
||||
if err != nil {
|
||||
@@ -615,7 +615,7 @@ func (s *conversationService) markConversationReadWithActor(conversation *models
|
||||
err = sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
currentConversation := repositories.ConversationRepository.Get(ctx.Tx, conversation.ID)
|
||||
if currentConversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if err := actor.markRead(ctx, currentConversation, targetMessage); err != nil {
|
||||
return err
|
||||
@@ -741,30 +741,30 @@ func (s *conversationService) buildEventPayload(payload map[string]any) string {
|
||||
// LinkConversationCustomer 将会话绑定到指定客户。
|
||||
func (s *conversationService) LinkConversationCustomer(conversationID, customerID int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if conversationID <= 0 || customerID <= 0 {
|
||||
return errorsx.InvalidParam("参数不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0133")
|
||||
}
|
||||
cust := CustomerService.Get(customerID)
|
||||
if cust == nil || cust.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
conv := s.Get(conversationID)
|
||||
if conv == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conv.Status == enums.IMConversationStatusClosed {
|
||||
return errorsx.InvalidParam("已关闭的会话无法关联客户")
|
||||
return errorsx.InvalidParamI18n("error.e0183")
|
||||
}
|
||||
if !s.canLinkConversationCustomer(conv, operator) {
|
||||
return errorsx.Forbidden("无权限关联该会话")
|
||||
return errorsx.ForbiddenI18n("error.e0224")
|
||||
}
|
||||
|
||||
err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
current := repositories.ConversationRepository.Get(ctx.Tx, conversationID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
now := time.Now()
|
||||
return repositories.ConversationRepository.Updates(ctx.Tx, conversationID, map[string]any{
|
||||
|
||||
@@ -167,7 +167,7 @@ func (s *customerContactService) ReplaceAllForCustomerInTx(
|
||||
operator *dto.AuthPrincipal,
|
||||
) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
type line struct {
|
||||
id *int64
|
||||
@@ -184,7 +184,7 @@ func (s *customerContactService) ReplaceAllForCustomerInTx(
|
||||
continue
|
||||
}
|
||||
if !enums.IsValidContactType(ct) {
|
||||
return errorsx.InvalidParam("联系方式类型不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0301")
|
||||
}
|
||||
items = append(items, line{
|
||||
id: r.ID,
|
||||
@@ -204,7 +204,7 @@ func (s *customerContactService) ReplaceAllForCustomerInTx(
|
||||
if primaryCount == 0 {
|
||||
items[0].primary = true
|
||||
} else if primaryCount > 1 {
|
||||
return errorsx.InvalidParam("仅能指定一条主联系方式")
|
||||
return errorsx.InvalidParamI18n("error.e0092")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,10 +241,10 @@ func (s *customerContactService) ReplaceAllForCustomerInTx(
|
||||
if it.id != nil && *it.id > 0 {
|
||||
row := repositories.CustomerContactRepository.Get(ctx.Tx, *it.id)
|
||||
if row == nil || row.CustomerID != customerID || row.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("联系方式不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0299")
|
||||
}
|
||||
if s.hasDuplicateContact(ctx.Tx, customerID, it.ct, it.val, *it.id) {
|
||||
return errorsx.InvalidParam("该联系方式已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0318")
|
||||
}
|
||||
if it.primary {
|
||||
if err := s.clearPrimaryExcept(ctx.Tx, customerID, *it.id); err != nil {
|
||||
@@ -265,7 +265,7 @@ func (s *customerContactService) ReplaceAllForCustomerInTx(
|
||||
continue
|
||||
}
|
||||
if s.hasDuplicateContact(ctx.Tx, customerID, it.ct, it.val, 0) {
|
||||
return errorsx.InvalidParam("该联系方式已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0318")
|
||||
}
|
||||
if deleted := s.findSoftDeletedContactByNaturalKey(ctx.Tx, customerID, it.ct, it.val); deleted != nil {
|
||||
if it.primary {
|
||||
@@ -331,10 +331,10 @@ func (s *customerContactService) clearPrimaryExcept(db *gorm.DB, customerID int6
|
||||
|
||||
func (s *customerContactService) validateContactStatus(status int) error {
|
||||
if !enums.IsValidStatus(status) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
if status == int(enums.StatusDeleted) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -342,21 +342,21 @@ func (s *customerContactService) validateContactStatus(status int) error {
|
||||
// CreateCustomerContact 创建联系方式;主联系方式在同一客户下唯一。
|
||||
func (s *customerContactService) CreateCustomerContact(req request.CreateCustomerContactRequest, operator *dto.AuthPrincipal) (*models.CustomerContact, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if req.CustomerID <= 0 {
|
||||
return nil, errorsx.InvalidParam("客户不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
if CustomerService.Get(req.CustomerID) == nil {
|
||||
return nil, errorsx.InvalidParam("客户不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
ct := strings.TrimSpace(req.ContactType)
|
||||
if !enums.IsValidContactType(ct) {
|
||||
return nil, errorsx.InvalidParam("联系方式类型不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0301")
|
||||
}
|
||||
val := strings.TrimSpace(req.ContactValue)
|
||||
if val == "" {
|
||||
return nil, errorsx.InvalidParam("联系方式不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0300")
|
||||
}
|
||||
if err := s.validateContactStatus(req.Status); err != nil {
|
||||
return nil, err
|
||||
@@ -369,7 +369,7 @@ func (s *customerContactService) CreateCustomerContact(req request.CreateCustome
|
||||
var created *models.CustomerContact
|
||||
err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
if s.hasDuplicateContact(ctx.Tx, req.CustomerID, enums.ContactType(ct), val, 0) {
|
||||
return errorsx.InvalidParam("该联系方式已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0318")
|
||||
}
|
||||
now := time.Now()
|
||||
if deleted := s.findSoftDeletedContactByNaturalKey(ctx.Tx, req.CustomerID, enums.ContactType(ct), val); deleted != nil {
|
||||
@@ -439,22 +439,22 @@ func (s *customerContactService) CreateCustomerContact(req request.CreateCustome
|
||||
// UpdateCustomerContact 更新联系方式。
|
||||
func (s *customerContactService) UpdateCustomerContact(req request.UpdateCustomerContactRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
return errorsx.InvalidParam("联系方式不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0299")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("联系方式不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0299")
|
||||
}
|
||||
ct := strings.TrimSpace(req.ContactType)
|
||||
if !enums.IsValidContactType(ct) {
|
||||
return errorsx.InvalidParam("联系方式类型不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0301")
|
||||
}
|
||||
val := strings.TrimSpace(req.ContactValue)
|
||||
if val == "" {
|
||||
return errorsx.InvalidParam("联系方式不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0300")
|
||||
}
|
||||
if err := s.validateContactStatus(req.Status); err != nil {
|
||||
return err
|
||||
@@ -462,7 +462,7 @@ func (s *customerContactService) UpdateCustomerContact(req request.UpdateCustome
|
||||
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
if s.hasDuplicateContact(ctx.Tx, current.CustomerID, enums.ContactType(ct), val, req.ID) {
|
||||
return errorsx.InvalidParam("该联系方式已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0318")
|
||||
}
|
||||
if req.IsPrimary {
|
||||
if err := s.clearPrimaryExcept(ctx.Tx, current.CustomerID, req.ID); err != nil {
|
||||
@@ -500,14 +500,14 @@ func (s *customerContactService) UpdateCustomerContact(req request.UpdateCustome
|
||||
// DeleteCustomerContact 软删除联系方式并同步客户主联系方式冗余字段。
|
||||
func (s *customerContactService) DeleteCustomerContact(id int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if id <= 0 {
|
||||
return errorsx.InvalidParam("联系方式不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0299")
|
||||
}
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("联系方式不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0299")
|
||||
}
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
now := time.Now()
|
||||
|
||||
@@ -116,12 +116,12 @@ func (s *customerService) CountByCompanyIDs(companyIDs []int64) map[int64]int64
|
||||
|
||||
func (s *customerService) EnsureExternalCustomer(ctx *sqls.TxContext, externalUser openidentity.ExternalUser) (int64, error) {
|
||||
if ctx == nil || ctx.Tx == nil {
|
||||
return 0, errorsx.InvalidParam("事务上下文不能为空")
|
||||
return 0, errorsx.InvalidParamI18n("error.e0086")
|
||||
}
|
||||
externalSource := externalUser.ExternalSource
|
||||
externalID := strings.TrimSpace(externalUser.ExternalID)
|
||||
if strings.TrimSpace(string(externalSource)) == "" || externalID == "" {
|
||||
return 0, errorsx.Unauthorized("外部用户标识不能为空")
|
||||
return 0, errorsx.UnauthorizedI18n("error.e0149")
|
||||
}
|
||||
now := time.Now()
|
||||
if identity := repositories.CustomerIdentityRepository.GetBy(ctx.Tx, externalSource, externalID); identity != nil {
|
||||
@@ -189,17 +189,17 @@ func hashUUID(uuid string) string {
|
||||
|
||||
func (s *customerService) CreateCustomer(req request.CreateCustomerRequest, operator *dto.AuthPrincipal) (*models.Customer, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("客户名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0156")
|
||||
}
|
||||
|
||||
if req.CompanyID > 0 {
|
||||
company := CompanyService.Get(req.CompanyID)
|
||||
if company == nil {
|
||||
return nil, errorsx.InvalidParam("所属公司不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0204")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,21 +222,21 @@ func (s *customerService) CreateCustomer(req request.CreateCustomerRequest, oper
|
||||
|
||||
func (s *customerService) UpdateCustomer(req request.UpdateCustomerRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(req.ID)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return errorsx.InvalidParam("客户名称不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0156")
|
||||
}
|
||||
|
||||
if req.CompanyID > 0 {
|
||||
company := CompanyService.Get(req.CompanyID)
|
||||
if company == nil {
|
||||
return errorsx.InvalidParam("所属公司不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0204")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ func (s *customerService) UpdateCustomer(req request.UpdateCustomerRequest, oper
|
||||
func (s *customerService) DeleteCustomer(id int64, operator dto.AuthPrincipal) error {
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
return repositories.CustomerRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
"status": enums.StatusDeleted,
|
||||
@@ -289,14 +289,14 @@ func (s *customerService) syncConversationCustomerName(db *gorm.DB, customerID i
|
||||
|
||||
func (s *customerService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
if status != int(enums.StatusOk) && status != int(enums.StatusDisabled) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
return repositories.CustomerRepository.Updates(sqls.DB(), id, map[string]any{
|
||||
"status": status,
|
||||
@@ -309,15 +309,15 @@ func (s *customerService) UpdateStatus(id int64, status int, operator *dto.AuthP
|
||||
// SaveCustomerProfile 单事务保存客户主信息与联系方式全量(新建或更新)。
|
||||
func (s *customerService) SaveCustomerProfile(req request.SaveCustomerProfileRequest, operator *dto.AuthPrincipal) (*models.Customer, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("客户名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0156")
|
||||
}
|
||||
if req.CompanyID > 0 {
|
||||
if CompanyService.Get(req.CompanyID) == nil {
|
||||
return nil, errorsx.InvalidParam("所属公司不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0204")
|
||||
}
|
||||
}
|
||||
createMode := req.ID == nil || *req.ID <= 0
|
||||
@@ -345,7 +345,7 @@ func (s *customerService) SaveCustomerProfile(req request.SaveCustomerProfileReq
|
||||
customerID = *req.ID
|
||||
cur := repositories.CustomerRepository.Get(ctx.Tx, customerID)
|
||||
if cur == nil {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
now := time.Now()
|
||||
if err := repositories.CustomerRepository.Updates(ctx.Tx, customerID, map[string]any{
|
||||
|
||||
@@ -54,7 +54,7 @@ type CustomerSessionVerifyResult struct {
|
||||
|
||||
func (s *customerSessionService) Exchange(channel *models.Channel, externalUser openidentity.ExternalUser) (*response.CustomerSessionExchangeResponse, error) {
|
||||
if channel == nil || channel.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("接入渠道不存在或已停用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0209")
|
||||
}
|
||||
var customerID int64
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
@@ -69,7 +69,7 @@ func (s *customerSessionService) Exchange(channel *models.Channel, externalUser
|
||||
}
|
||||
customer := CustomerService.Get(customerID)
|
||||
if customer == nil || customer.Status == enums.StatusDeleted {
|
||||
return nil, errorsx.InvalidParam("客户不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
token, expiresAt, err := s.Sign(channel, customer, externalUser)
|
||||
if err != nil {
|
||||
@@ -93,7 +93,7 @@ func (s *customerSessionService) Sign(channel *models.Channel, customer *models.
|
||||
return "", time.Time{}, errorsx.BusinessError(1, "客服会话密钥未配置")
|
||||
}
|
||||
if channel == nil || customer == nil {
|
||||
return "", time.Time{}, errorsx.InvalidParam("客服会话参数不完整")
|
||||
return "", time.Time{}, errorsx.InvalidParamI18n("error.e0158")
|
||||
}
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(time.Duration(cfg.TTL()) * time.Minute)
|
||||
@@ -119,21 +119,21 @@ func (s *customerSessionService) Sign(channel *models.Channel, customer *models.
|
||||
func (s *customerSessionService) VerifyRequest(ctx *gin.Context, channel *models.Channel) (*CustomerSessionVerifyResult, error) {
|
||||
token := s.getCustomerSessionToken(ctx)
|
||||
if token == "" {
|
||||
return nil, errorsx.Unauthorized("客服会话不能为空")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0157")
|
||||
}
|
||||
claims, err := s.verifyToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if channel == nil || channel.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("接入渠道不存在或已停用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0209")
|
||||
}
|
||||
if claims.ChannelID != channel.ID || strings.TrimSpace(claims.ChannelCode) != strings.TrimSpace(channel.ChannelID) {
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
customer := CustomerService.Get(claims.CustomerID)
|
||||
if customer == nil || customer.Status == enums.StatusDeleted {
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
external, err := s.externalUserFromClaims(claims, customer)
|
||||
if err != nil {
|
||||
@@ -183,15 +183,15 @@ func (s *customerSessionService) verifyToken(rawToken string) (*customerSessionC
|
||||
}))
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, errorsx.Unauthorized("客服会话已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0160")
|
||||
}
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
if token == nil || !token.Valid || claims.TokenType != customerSessionTokenType || claims.ExpiresAt == nil {
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
if claims.ChannelID <= 0 || strings.TrimSpace(claims.ChannelCode) == "" || claims.CustomerID <= 0 || strings.TrimSpace(claims.IdentityKey) == "" {
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func (s *customerSessionService) externalUserFromClaims(claims *customerSessionC
|
||||
identityKey := strings.TrimSpace(claims.IdentityKey)
|
||||
parts := strings.SplitN(identityKey, ":", 2)
|
||||
if len(parts) != 2 || strings.TrimSpace(parts[1]) == "" {
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
var source enums.ExternalSource
|
||||
switch parts[0] {
|
||||
@@ -209,11 +209,11 @@ func (s *customerSessionService) externalUserFromClaims(claims *customerSessionC
|
||||
case "guest":
|
||||
source = enums.ExternalSourceGuest
|
||||
default:
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
identity := repositories.CustomerIdentityRepository.GetBy(sqls.DB(), source, parts[1])
|
||||
if identity == nil || identity.CustomerID != claims.CustomerID {
|
||||
return nil, errorsx.Unauthorized("客服会话校验失败")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0161")
|
||||
}
|
||||
name := strings.TrimSpace(claims.CustomerName)
|
||||
if customer != nil && strings.TrimSpace(customer.Name) != "" {
|
||||
|
||||
@@ -22,24 +22,24 @@ type imMessageAssetPayload struct {
|
||||
func parseIMMessageAssetPayload(payload string) (*imMessageAssetPayload, error) {
|
||||
payload = strings.TrimSpace(payload)
|
||||
if payload == "" {
|
||||
return nil, errorsx.InvalidParam("附件消息缺少 payload")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0346")
|
||||
}
|
||||
ret := &imMessageAssetPayload{}
|
||||
if err := json.Unmarshal([]byte(payload), ret); err != nil {
|
||||
return nil, errorsx.InvalidParam("附件消息 payload 格式错误")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0344")
|
||||
}
|
||||
ret.AssetID = strings.TrimSpace(ret.AssetID)
|
||||
ret.Provider = enums.AssetProvider(strings.TrimSpace(string(ret.Provider)))
|
||||
ret.StorageKey = strings.TrimSpace(ret.StorageKey)
|
||||
if ret.AssetID == "" {
|
||||
return nil, errorsx.InvalidParam("附件消息缺少 assetId")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0345")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func buildIMMessageAssetPayload(asset *models.Asset) (string, error) {
|
||||
if asset == nil {
|
||||
return "", errorsx.InvalidParam("附件不存在")
|
||||
return "", errorsx.InvalidParamI18n("error.e0342")
|
||||
}
|
||||
payload, err := json.Marshal(imMessageAssetPayload{
|
||||
AssetID: asset.AssetID,
|
||||
@@ -107,10 +107,10 @@ func hydrateIMMessageAssetPayload(payload *imMessageAssetPayload) *imMessageAsse
|
||||
|
||||
func validateConversationAsset(asset *models.Asset, conversationID int64, messageType enums.IMMessageType) error {
|
||||
if asset == nil {
|
||||
return errorsx.InvalidParam("附件不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0342")
|
||||
}
|
||||
if asset.Status != enums.AssetStatusSuccess {
|
||||
return errorsx.InvalidParam("附件尚未上传完成")
|
||||
return errorsx.InvalidParamI18n("error.e0343")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"agent-desk/internal/ai/rag"
|
||||
@@ -78,7 +77,7 @@ func (s *knowledgeBaseService) Delete(id int64) {
|
||||
|
||||
func (s *knowledgeBaseService) CreateKnowledgeBase(req request.CreateKnowledgeBaseRequest, operator *dto.AuthPrincipal) (*models.KnowledgeBase, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item, err := s.buildKnowledgeBaseModel(req)
|
||||
if err != nil {
|
||||
@@ -94,11 +93,11 @@ func (s *knowledgeBaseService) CreateKnowledgeBase(req request.CreateKnowledgeBa
|
||||
|
||||
func (s *knowledgeBaseService) UpdateKnowledgeBase(req request.UpdateKnowledgeBaseRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("知识库不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
item, err := s.buildKnowledgeBaseModel(req.CreateKnowledgeBaseRequest)
|
||||
if err != nil {
|
||||
@@ -126,15 +125,15 @@ func (s *knowledgeBaseService) UpdateKnowledgeBase(req request.UpdateKnowledgeBa
|
||||
func (s *knowledgeBaseService) DeleteKnowledgeBase(id int64) error {
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("知识库不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
|
||||
referencingAgents := repositories.AIAgentRepository.FindByKnowledgeBaseID(sqls.DB(), id)
|
||||
if len(referencingAgents) > 0 {
|
||||
if len(referencingAgents) == 1 {
|
||||
return errorsx.Forbidden(fmt.Sprintf("知识库已被 AI Agent「%s」引用,请先解除绑定", referencingAgents[0].Name))
|
||||
return errorsx.ForbiddenI18n("error.knowledgeBase.referencedByAgent", referencingAgents[0].Name)
|
||||
}
|
||||
return errorsx.Forbidden(fmt.Sprintf("知识库已被 %d 个 AI Agent 引用,请先解除绑定", len(referencingAgents)))
|
||||
return errorsx.ForbiddenI18n("error.knowledgeBase.referencedByAgents", len(referencingAgents))
|
||||
}
|
||||
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
@@ -185,7 +184,7 @@ func (s *knowledgeBaseService) buildKnowledgeBaseModel(req request.CreateKnowled
|
||||
item.KnowledgeType = string(enums.KnowledgeBaseTypeDocument)
|
||||
}
|
||||
if !isValidKnowledgeType(item.KnowledgeType) {
|
||||
return nil, errorsx.InvalidParam("知识库类型不支持")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0290")
|
||||
}
|
||||
if item.DefaultScoreThreshold == 0 {
|
||||
item.DefaultScoreThreshold = 0.2
|
||||
@@ -202,10 +201,10 @@ func (s *knowledgeBaseService) buildKnowledgeBaseModel(req request.CreateKnowled
|
||||
item.ChunkMaxTokens = 0
|
||||
item.ChunkOverlapTokens = 0
|
||||
} else if item.ChunkProvider == string(enums.KnowledgeChunkProviderFAQ) {
|
||||
return nil, errorsx.InvalidParam("文档知识库不能使用FAQ分块策略")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0219")
|
||||
}
|
||||
if !isValidChunkProvider(item.ChunkProvider) {
|
||||
return nil, errorsx.InvalidParam("分块策略不支持")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0130")
|
||||
}
|
||||
if item.KnowledgeType != string(enums.KnowledgeBaseTypeFAQ) && item.ChunkTargetTokens == 0 {
|
||||
item.ChunkTargetTokens = 300
|
||||
|
||||
@@ -42,20 +42,20 @@ func (s *knowledgeDirectoryService) Count(cnd *sqls.Cnd) int64 {
|
||||
|
||||
func (s *knowledgeDirectoryService) CreateDirectory(req request.CreateKnowledgeDirectoryRequest, operator *dto.AuthPrincipal) (*models.KnowledgeDirectory, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("目录名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0275")
|
||||
}
|
||||
if req.KnowledgeBaseID <= 0 || KnowledgeBaseService.Get(req.KnowledgeBaseID) == nil {
|
||||
return nil, errorsx.InvalidParam("知识库不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
if err := s.validateParent(req.KnowledgeBaseID, req.ParentID, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing := s.findByName(req.KnowledgeBaseID, req.ParentID, name); existing != nil {
|
||||
return nil, errorsx.InvalidParam("同级下已存在相同名称的目录")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0142")
|
||||
}
|
||||
item := &models.KnowledgeDirectory{
|
||||
KnowledgeBaseID: req.KnowledgeBaseID,
|
||||
@@ -74,30 +74,30 @@ func (s *knowledgeDirectoryService) CreateDirectory(req request.CreateKnowledgeD
|
||||
|
||||
func (s *knowledgeDirectoryService) UpdateDirectory(req request.UpdateKnowledgeDirectoryRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(req.ID)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("目录不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0273")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return errorsx.InvalidParam("目录名称不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0275")
|
||||
}
|
||||
if req.KnowledgeBaseID <= 0 {
|
||||
req.KnowledgeBaseID = item.KnowledgeBaseID
|
||||
}
|
||||
if req.KnowledgeBaseID != item.KnowledgeBaseID {
|
||||
return errorsx.InvalidParam("目录不能移动到其他知识库")
|
||||
return errorsx.InvalidParamI18n("error.e0274")
|
||||
}
|
||||
if err := s.validateParent(item.KnowledgeBaseID, req.ParentID, req.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.ParentID > 0 && s.Count(sqls.NewCnd().Eq("parent_id", req.ID)) > 0 {
|
||||
return errorsx.InvalidParam("存在子目录的目录不能移动到二级目录")
|
||||
return errorsx.InvalidParamI18n("error.e0152")
|
||||
}
|
||||
if existing := s.findByName(item.KnowledgeBaseID, req.ParentID, name); existing != nil && existing.ID != req.ID {
|
||||
return errorsx.InvalidParam("同级下已存在相同名称的目录")
|
||||
return errorsx.InvalidParamI18n("error.e0142")
|
||||
}
|
||||
return repositories.KnowledgeDirectoryRepository.Updates(sqls.DB(), req.ID, map[string]any{
|
||||
"parent_id": req.ParentID,
|
||||
@@ -112,16 +112,16 @@ func (s *knowledgeDirectoryService) UpdateDirectory(req request.UpdateKnowledgeD
|
||||
func (s *knowledgeDirectoryService) DeleteDirectory(id int64) error {
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("目录不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0273")
|
||||
}
|
||||
if s.Count(sqls.NewCnd().Eq("parent_id", id)) > 0 {
|
||||
return errorsx.InvalidParam("该目录下存在子目录,无法删除")
|
||||
return errorsx.InvalidParamI18n("error.e0316")
|
||||
}
|
||||
if KnowledgeDocumentService.Count(sqls.NewCnd().Eq("directory_id", id)) > 0 {
|
||||
return errorsx.InvalidParam("该目录下存在文档,无法删除")
|
||||
return errorsx.InvalidParamI18n("error.e0317")
|
||||
}
|
||||
if KnowledgeFAQService.Count(sqls.NewCnd().Eq("directory_id", id)) > 0 {
|
||||
return errorsx.InvalidParam("该目录下存在FAQ,无法删除")
|
||||
return errorsx.InvalidParamI18n("error.e0315")
|
||||
}
|
||||
return repositories.KnowledgeDirectoryRepository.Delete(sqls.DB(), id)
|
||||
}
|
||||
@@ -131,10 +131,10 @@ func (s *knowledgeDirectoryService) UpdateSort(knowledgeBaseID int64, parentID i
|
||||
for i, id := range ids {
|
||||
item := repositories.KnowledgeDirectoryRepository.Get(ctx.Tx, id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("目录不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0273")
|
||||
}
|
||||
if item.KnowledgeBaseID != knowledgeBaseID || item.ParentID != parentID {
|
||||
return errorsx.InvalidParam("只能调整同知识库同级目录排序")
|
||||
return errorsx.InvalidParamI18n("error.e0140")
|
||||
}
|
||||
if err := repositories.KnowledgeDirectoryRepository.UpdateColumn(ctx.Tx, id, "sort_no", i+1); err != nil {
|
||||
return err
|
||||
@@ -150,13 +150,13 @@ func (s *knowledgeDirectoryService) RequireUsableDirectory(knowledgeBaseID int64
|
||||
}
|
||||
item := s.Get(directoryID)
|
||||
if item == nil {
|
||||
return nil, errorsx.InvalidParam("知识库目录不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0287")
|
||||
}
|
||||
if item.KnowledgeBaseID != knowledgeBaseID {
|
||||
return nil, errorsx.InvalidParam("知识库目录不属于当前知识库")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0288")
|
||||
}
|
||||
if item.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("知识库目录不可用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0286")
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
@@ -192,17 +192,17 @@ func (s *knowledgeDirectoryService) validateParent(knowledgeBaseID int64, parent
|
||||
return nil
|
||||
}
|
||||
if parentID == selfID {
|
||||
return errorsx.InvalidParam("不能将目录设为自己的子目录")
|
||||
return errorsx.InvalidParamI18n("error.e0084")
|
||||
}
|
||||
parent := s.Get(parentID)
|
||||
if parent == nil {
|
||||
return errorsx.InvalidParam("父目录不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0252")
|
||||
}
|
||||
if parent.KnowledgeBaseID != knowledgeBaseID {
|
||||
return errorsx.InvalidParam("父目录不属于当前知识库")
|
||||
return errorsx.InvalidParamI18n("error.e0253")
|
||||
}
|
||||
if parent.ParentID > 0 {
|
||||
return errorsx.InvalidParam("知识库目录最多支持二级")
|
||||
return errorsx.InvalidParamI18n("error.e0289")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -85,14 +85,14 @@ func (s *knowledgeDocumentService) Delete(id int64) {
|
||||
|
||||
func (s *knowledgeDocumentService) CreateKnowledgeDocument(req request.CreateKnowledgeDocumentRequest, operator *dto.AuthPrincipal) (*models.KnowledgeDocument, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
kb := KnowledgeBaseService.Get(req.KnowledgeBaseID)
|
||||
if kb == nil {
|
||||
return nil, errorsx.InvalidParam("知识库不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
if kb.KnowledgeType == string(enums.KnowledgeBaseTypeFAQ) {
|
||||
return nil, errorsx.InvalidParam("FAQ知识库不支持文档")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0026")
|
||||
}
|
||||
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
|
||||
return nil, err
|
||||
@@ -123,18 +123,18 @@ func (s *knowledgeDocumentService) CreateKnowledgeDocument(req request.CreateKno
|
||||
|
||||
func (s *knowledgeDocumentService) UpdateKnowledgeDocument(req request.UpdateKnowledgeDocumentRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("文档不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0218")
|
||||
}
|
||||
kb := KnowledgeBaseService.Get(req.KnowledgeBaseID)
|
||||
if kb == nil {
|
||||
return errorsx.InvalidParam("知识库不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
if kb.KnowledgeType == string(enums.KnowledgeBaseTypeFAQ) {
|
||||
return errorsx.InvalidParam("FAQ知识库不支持文档")
|
||||
return errorsx.InvalidParamI18n("error.e0026")
|
||||
}
|
||||
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
|
||||
return err
|
||||
@@ -185,18 +185,18 @@ func (s *knowledgeDocumentService) DeleteKnowledgeDocument(id int64) error {
|
||||
|
||||
func (s *knowledgeDocumentService) BatchMoveKnowledgeDocuments(req request.BatchMoveKnowledgeDocumentRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
ids := uniquePositiveIDs(req.IDs)
|
||||
if len(ids) == 0 {
|
||||
return errorsx.InvalidParam("请选择要移动的文档")
|
||||
return errorsx.InvalidParamI18n("error.e0333")
|
||||
}
|
||||
kb := KnowledgeBaseService.Get(req.KnowledgeBaseID)
|
||||
if kb == nil {
|
||||
return errorsx.InvalidParam("知识库不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
if kb.KnowledgeType == string(enums.KnowledgeBaseTypeFAQ) {
|
||||
return errorsx.InvalidParam("FAQ知识库不支持文档")
|
||||
return errorsx.InvalidParamI18n("error.e0026")
|
||||
}
|
||||
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
|
||||
return err
|
||||
@@ -204,10 +204,10 @@ func (s *knowledgeDocumentService) BatchMoveKnowledgeDocuments(req request.Batch
|
||||
for _, id := range ids {
|
||||
current := s.Get(id)
|
||||
if current == nil || current.Status == enums.StatusDeleted {
|
||||
return errorsx.InvalidParam("文档不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0218")
|
||||
}
|
||||
if current.KnowledgeBaseID != req.KnowledgeBaseID {
|
||||
return errorsx.InvalidParam("只能移动当前知识库下的文档")
|
||||
return errorsx.InvalidParamI18n("error.e0139")
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
@@ -240,7 +240,7 @@ func (s *knowledgeDocumentService) BatchMoveKnowledgeDocuments(req request.Batch
|
||||
func (s *knowledgeDocumentService) BatchDeleteKnowledgeDocuments(req request.BatchDeleteKnowledgeDocumentRequest) error {
|
||||
ids := uniquePositiveIDs(req.IDs)
|
||||
if len(ids) == 0 {
|
||||
return errorsx.InvalidParam("请选择要删除的文档")
|
||||
return errorsx.InvalidParamI18n("error.e0331")
|
||||
}
|
||||
for _, id := range ids {
|
||||
if err := s.DeleteKnowledgeDocument(id); err != nil {
|
||||
@@ -255,7 +255,7 @@ func (s *knowledgeDocumentService) buildKnowledgeDocumentModel(req request.Creat
|
||||
req.ContentType = enums.KnowledgeDocumentContentTypeHTML
|
||||
}
|
||||
if req.ContentType != enums.KnowledgeDocumentContentTypeHTML && req.ContentType != enums.KnowledgeDocumentContentTypeMarkdown {
|
||||
return nil, errorsx.InvalidParam("内容类型不支持")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0129")
|
||||
}
|
||||
|
||||
plainText := rag.ExtractPlainText(req.Content, req.ContentType)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"agent-desk/internal/pkg/dto/response"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/pkg/utils"
|
||||
"agent-desk/internal/repositories"
|
||||
|
||||
@@ -78,22 +79,22 @@ func (s *knowledgeFAQService) ExportKnowledgeFAQs(knowledgeBaseID int64) (*respo
|
||||
|
||||
func (s *knowledgeFAQService) ImportKnowledgeFAQs(req request.ImportKnowledgeFAQRequest, operator *dto.AuthPrincipal) (*response.KnowledgeFAQImportResult, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if req.Mode != request.KnowledgeFAQImportModeAppend && req.Mode != request.KnowledgeFAQImportModeOverwrite {
|
||||
return nil, errorsx.InvalidParam("导入模式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0177")
|
||||
}
|
||||
if req.Reader == nil {
|
||||
return nil, errorsx.InvalidParam("请选择导入文件")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0327")
|
||||
}
|
||||
if strings.ToLower(filepath.Ext(req.Filename)) != ".xlsx" {
|
||||
return nil, errorsx.InvalidParam("仅支持.xlsx文件")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0089")
|
||||
}
|
||||
kb, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, result, err := parseKnowledgeFAQImportRows(req.Reader)
|
||||
rows, result, err := parseKnowledgeFAQImportRows(req.Reader, req.Locale)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,14 +120,14 @@ func (s *knowledgeFAQService) ImportKnowledgeFAQs(req request.ImportKnowledgeFAQ
|
||||
existing, exists := existingMap[row.Question]
|
||||
if exists && req.Mode == request.KnowledgeFAQImportModeAppend {
|
||||
result.Skipped++
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: row.RowNo, Message: "标准问题已存在,已跳过"})
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: row.RowNo, Message: i18nx.Getf(req.Locale, "error.e0237")})
|
||||
continue
|
||||
}
|
||||
|
||||
similarQuestions, marshalErr := json.Marshal(row.SimilarQuestions)
|
||||
if marshalErr != nil {
|
||||
result.Failed++
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: row.RowNo, Message: "相似问格式不合法"})
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: row.RowNo, Message: i18nx.Getf(req.Locale, "error.e0280")})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -227,33 +228,33 @@ func buildKnowledgeFAQExcelFile(filename string, workbook *excelize.File) (*resp
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseKnowledgeFAQImportRows(reader io.Reader) ([]knowledgeFAQImportRow, *response.KnowledgeFAQImportResult, error) {
|
||||
func parseKnowledgeFAQImportRows(reader io.Reader, locale string) ([]knowledgeFAQImportRow, *response.KnowledgeFAQImportResult, error) {
|
||||
workbook, err := excelize.OpenReader(reader)
|
||||
if err != nil {
|
||||
return nil, nil, errorsx.InvalidParam("Excel文件解析失败")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0023")
|
||||
}
|
||||
defer workbook.Close()
|
||||
sheet := knowledgeFAQExcelSheetName
|
||||
if index, _ := workbook.GetSheetIndex(sheet); index < 0 {
|
||||
sheets := workbook.GetSheetList()
|
||||
if len(sheets) == 0 {
|
||||
return nil, nil, errorsx.InvalidParam("Excel文件为空")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0022")
|
||||
}
|
||||
sheet = sheets[0]
|
||||
}
|
||||
table, err := workbook.GetRows(sheet)
|
||||
if err != nil {
|
||||
return nil, nil, errorsx.InvalidParam("Excel文件读取失败")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0024")
|
||||
}
|
||||
if len(table) == 0 {
|
||||
return nil, nil, errorsx.InvalidParam("Excel文件为空")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0022")
|
||||
}
|
||||
headerMap := buildKnowledgeFAQHeaderMap(table[0])
|
||||
if _, ok := headerMap["question"]; !ok {
|
||||
return nil, nil, errorsx.InvalidParam("缺少标准问题列")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0297")
|
||||
}
|
||||
if _, ok := headerMap["answer"]; !ok {
|
||||
return nil, nil, errorsx.InvalidParam("缺少答案列")
|
||||
return nil, nil, errorsx.InvalidParamI18n("error.e0298")
|
||||
}
|
||||
|
||||
result := &response.KnowledgeFAQImportResult{Errors: make([]response.KnowledgeFAQImportError, 0)}
|
||||
@@ -275,22 +276,22 @@ func parseKnowledgeFAQImportRows(reader io.Reader) ([]knowledgeFAQImportRow, *re
|
||||
}
|
||||
if row.Question == "" {
|
||||
result.Failed++
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: "问题不能为空"})
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: i18nx.Getf(locale, "error.e0340")})
|
||||
continue
|
||||
}
|
||||
if len([]rune(row.Question)) > 500 {
|
||||
result.Failed++
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: "问题不能超过500字"})
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: i18nx.Getf(locale, "error.e0341")})
|
||||
continue
|
||||
}
|
||||
if row.Answer == "" {
|
||||
result.Failed++
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: "答案不能为空"})
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: i18nx.Getf(locale, "error.e0292")})
|
||||
continue
|
||||
}
|
||||
if firstRow, exists := seen[row.Question]; exists {
|
||||
result.Failed++
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: fmt.Sprintf("同一文件中标准问题重复,首次出现于第%d行", firstRow)})
|
||||
result.Errors = append(result.Errors, response.KnowledgeFAQImportError{Row: rowNo, Message: i18nx.Getf(locale, "error.knowledgeFAQImport.duplicateQuestionInFile", firstRow)})
|
||||
continue
|
||||
}
|
||||
seen[row.Question] = rowNo
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *knowledgeFAQService) Count(cnd *sqls.Cnd) int64 {
|
||||
|
||||
func (s *knowledgeFAQService) CreateKnowledgeFAQ(req request.CreateKnowledgeFAQRequest, operator *dto.AuthPrincipal) (*models.KnowledgeFAQ, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
kb, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID)
|
||||
if err != nil {
|
||||
@@ -76,11 +76,11 @@ func (s *knowledgeFAQService) CreateKnowledgeFAQ(req request.CreateKnowledgeFAQR
|
||||
|
||||
func (s *knowledgeFAQService) UpdateKnowledgeFAQ(req request.UpdateKnowledgeFAQRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("FAQ不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0025")
|
||||
}
|
||||
if _, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID); err != nil {
|
||||
return err
|
||||
@@ -114,7 +114,7 @@ func (s *knowledgeFAQService) UpdateKnowledgeFAQ(req request.UpdateKnowledgeFAQR
|
||||
func (s *knowledgeFAQService) DeleteKnowledgeFAQ(id int64) error {
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("FAQ不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0025")
|
||||
}
|
||||
if err := repositories.KnowledgeFAQRepository.Delete(sqls.DB(), id); err != nil {
|
||||
return err
|
||||
@@ -124,11 +124,11 @@ func (s *knowledgeFAQService) DeleteKnowledgeFAQ(id int64) error {
|
||||
|
||||
func (s *knowledgeFAQService) BatchMoveKnowledgeFAQs(req request.BatchMoveKnowledgeFAQRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
ids := uniquePositiveIDs(req.IDs)
|
||||
if len(ids) == 0 {
|
||||
return errorsx.InvalidParam("请选择要移动的FAQ")
|
||||
return errorsx.InvalidParamI18n("error.e0332")
|
||||
}
|
||||
if _, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID); err != nil {
|
||||
return err
|
||||
@@ -139,10 +139,10 @@ func (s *knowledgeFAQService) BatchMoveKnowledgeFAQs(req request.BatchMoveKnowle
|
||||
for _, id := range ids {
|
||||
current := s.Get(id)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("FAQ不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0025")
|
||||
}
|
||||
if current.KnowledgeBaseID != req.KnowledgeBaseID {
|
||||
return errorsx.InvalidParam("只能移动当前知识库下的FAQ")
|
||||
return errorsx.InvalidParamI18n("error.e0138")
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
@@ -175,7 +175,7 @@ func (s *knowledgeFAQService) BatchMoveKnowledgeFAQs(req request.BatchMoveKnowle
|
||||
func (s *knowledgeFAQService) BatchDeleteKnowledgeFAQs(req request.BatchDeleteKnowledgeFAQRequest) error {
|
||||
ids := uniquePositiveIDs(req.IDs)
|
||||
if len(ids) == 0 {
|
||||
return errorsx.InvalidParam("请选择要删除的FAQ")
|
||||
return errorsx.InvalidParamI18n("error.e0330")
|
||||
}
|
||||
for _, id := range ids {
|
||||
if err := s.DeleteKnowledgeFAQ(id); err != nil {
|
||||
@@ -187,17 +187,17 @@ func (s *knowledgeFAQService) BatchDeleteKnowledgeFAQs(req request.BatchDeleteKn
|
||||
|
||||
func (s *knowledgeFAQService) buildKnowledgeFAQModel(req request.CreateKnowledgeFAQRequest) (*models.KnowledgeFAQ, error) {
|
||||
if req.KnowledgeBaseID <= 0 {
|
||||
return nil, errorsx.InvalidParam("知识库不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
if req.Question == "" {
|
||||
return nil, errorsx.InvalidParam("问题不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0340")
|
||||
}
|
||||
if req.Answer == "" {
|
||||
return nil, errorsx.InvalidParam("答案不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0292")
|
||||
}
|
||||
similarQuestions, err := json.Marshal(normalizeSimilarQuestions(req.SimilarQuestions))
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("相似问格式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0280")
|
||||
}
|
||||
return &models.KnowledgeFAQ{
|
||||
KnowledgeBaseID: req.KnowledgeBaseID,
|
||||
@@ -212,10 +212,10 @@ func (s *knowledgeFAQService) buildKnowledgeFAQModel(req request.CreateKnowledge
|
||||
func (s *knowledgeFAQService) requireFAQKnowledgeBase(knowledgeBaseID int64) (*models.KnowledgeBase, error) {
|
||||
kb := KnowledgeBaseService.Get(knowledgeBaseID)
|
||||
if kb == nil {
|
||||
return nil, errorsx.InvalidParam("知识库不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0283")
|
||||
}
|
||||
if kb.KnowledgeType != "faq" {
|
||||
return nil, errorsx.InvalidParam("当前知识库不是FAQ知识库")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0199")
|
||||
}
|
||||
return kb, nil
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (s *loginSessionService) Delete(id int64) {
|
||||
func (s *loginSessionService) Revoke(id int64, operatorID int64, operatorName string) error {
|
||||
session := s.Get(id)
|
||||
if session == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
now := time.Now()
|
||||
return s.Updates(id, map[string]any{
|
||||
|
||||
@@ -95,18 +95,18 @@ func (s *mCPDebugService) CallTool(ctx context.Context, serverCode string, toolN
|
||||
func (s *mCPDebugService) resolveServer(serverCode string) (mcps.ServerConfig, error) {
|
||||
cfg := config.Current()
|
||||
if !cfg.MCP.Enabled {
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParam("MCP未启用")
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParamI18n("error.e0035")
|
||||
}
|
||||
serverCode = strings.TrimSpace(serverCode)
|
||||
if serverCode == "" {
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParam("serverCode不能为空")
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParamI18n("error.e0070")
|
||||
}
|
||||
server, ok := cfg.MCP.Servers[serverCode]
|
||||
if !ok {
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParam("MCP服务配置不存在")
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParamI18n("error.e0034")
|
||||
}
|
||||
if !server.Enabled {
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParam("MCP服务未启用")
|
||||
return mcps.ServerConfig{}, errorsx.InvalidParamI18n("error.e0033")
|
||||
}
|
||||
return mcps.ServerConfig{
|
||||
Code: serverCode,
|
||||
|
||||
@@ -122,7 +122,7 @@ func (s *messageService) GetConversationReadTarget(conversationID, messageID int
|
||||
if messageID > 0 {
|
||||
message := s.Get(messageID)
|
||||
if message == nil || message.ConversationID != conversationID {
|
||||
return nil, errorsx.InvalidParam("消息不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0244")
|
||||
}
|
||||
return message, nil
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (s *messageService) SendMessage(conversationID int64, senderType enums.IMSe
|
||||
case enums.IMSenderTypeCustomer:
|
||||
return s.sendMessage(conversationID, enums.IMSenderTypeCustomer, 0, clientMsgID, messageType, content, payload, nil, external, "")
|
||||
default:
|
||||
return nil, errorsx.InvalidParam("不支持的发送人类型")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0080")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,24 +152,24 @@ func (s *messageService) SendAgentMessageWithRequestID(conversationID int64, req
|
||||
|
||||
func (s *messageService) RecallAgentMessage(messageID int64, operator *dto.AuthPrincipal) (*models.Message, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if messageID <= 0 {
|
||||
return nil, errorsx.InvalidParam("消息不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0244")
|
||||
}
|
||||
|
||||
message := s.Get(messageID)
|
||||
if message == nil {
|
||||
return nil, errorsx.InvalidParam("消息不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0244")
|
||||
}
|
||||
if message.SenderType != enums.IMSenderTypeAgent {
|
||||
return nil, errorsx.InvalidParam("仅支持撤回客服消息")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0091")
|
||||
}
|
||||
if message.SenderID != operator.UserID {
|
||||
return nil, errorsx.Forbidden("仅允许撤回自己发送的消息")
|
||||
return nil, errorsx.ForbiddenI18n("error.e0087")
|
||||
}
|
||||
if message.RecalledAt != nil || message.SendStatus == enums.IMMessageStatusRecalled {
|
||||
return nil, errorsx.InvalidParam("消息已撤回")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0246")
|
||||
}
|
||||
|
||||
conversation, err := s.ValidateConversationSender(message.ConversationID, enums.IMSenderTypeAgent, operator, nil)
|
||||
@@ -260,10 +260,10 @@ func (s *messageService) SendAIServiceNotice(conversationID int64, aiAgentID int
|
||||
func (s *messageService) SendAIServiceNoticeWithRequestID(conversationID int64, aiAgentID int64, content string, requestID string) (*models.Message, error) {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.Status == enums.IMConversationStatusClosed {
|
||||
return nil, errorsx.InvalidParam("会话已关闭")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0119")
|
||||
}
|
||||
return s.sendValidatedMessage(conversation, enums.IMSenderTypeAI, aiAgentID, strs.UUID(), enums.IMMessageTypeText, content, "", &dto.AuthPrincipal{
|
||||
UserID: 0,
|
||||
@@ -378,10 +378,10 @@ func (s *messageService) sendMessage(conversationID int64, senderType enums.IMSe
|
||||
|
||||
if senderType == enums.IMSenderTypeCustomer {
|
||||
if external == nil || strings.TrimSpace(external.ExternalID) == "" {
|
||||
return nil, errorsx.Unauthorized("外部用户标识不能为空")
|
||||
return nil, errorsx.UnauthorizedI18n("error.e0149")
|
||||
}
|
||||
} else if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
|
||||
if strs.IsBlank(string(messageType)) {
|
||||
@@ -404,7 +404,7 @@ func (s *messageService) sendValidatedMessage(conversation *models.Conversation,
|
||||
return nil, err
|
||||
}
|
||||
if strs.IsBlank(content) && strs.IsBlank(payload) {
|
||||
return nil, errorsx.InvalidParam("消息内容不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0245")
|
||||
}
|
||||
|
||||
// 防抖,消息存在就不再发送了
|
||||
@@ -610,11 +610,11 @@ func (s *messageService) normalizeMessageContent(conversationID int64, messageTy
|
||||
sanitized := utils.SanitizeMessageHTML(content)
|
||||
normalized, err := utils.NormalizeMessageHTMLAssets(sanitized)
|
||||
if err != nil {
|
||||
return "", "", "", errorsx.InvalidParam("HTML消息中的图片必须使用已上传文件")
|
||||
return "", "", "", errorsx.InvalidParamI18n("error.e0030")
|
||||
}
|
||||
summary := utils.BuildHTMLSummary(normalized)
|
||||
if summary == "" {
|
||||
return "", "", "", errorsx.InvalidParam("消息内容不能为空")
|
||||
return "", "", "", errorsx.InvalidParamI18n("error.e0245")
|
||||
}
|
||||
return normalized, "", summary, nil
|
||||
case enums.IMMessageTypeImage, enums.IMMessageTypeAttachment:
|
||||
@@ -639,7 +639,7 @@ func (s *messageService) normalizeMessageContent(conversationID int64, messageTy
|
||||
default:
|
||||
content = strings.TrimSpace(content)
|
||||
if content == "" && strings.TrimSpace(payload) == "" {
|
||||
return "", "", "", errorsx.InvalidParam("消息内容不能为空")
|
||||
return "", "", "", errorsx.InvalidParamI18n("error.e0245")
|
||||
}
|
||||
return content, strings.TrimSpace(payload), buildMessageSummary(messageType, content), nil
|
||||
}
|
||||
@@ -648,38 +648,38 @@ func (s *messageService) normalizeMessageContent(conversationID int64, messageTy
|
||||
func (s *messageService) ValidateConversationSender(conversationID int64, senderType enums.IMSenderType, operator *dto.AuthPrincipal, external *openidentity.ExternalUser) (*models.Conversation, error) {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.Status == enums.IMConversationStatusClosed {
|
||||
return nil, errorsx.InvalidParam("会话已关闭")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0119")
|
||||
}
|
||||
switch senderType {
|
||||
case enums.IMSenderTypeAgent:
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusActive || conversation.CurrentAssigneeID == 0 {
|
||||
return nil, errorsx.InvalidParam("会话未分配客服,暂不允许发送消息")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0120")
|
||||
}
|
||||
if conversation.CurrentAssigneeID != operator.UserID {
|
||||
return nil, errorsx.Forbidden("当前会话已分配给其他客服")
|
||||
return nil, errorsx.ForbiddenI18n("error.e0191")
|
||||
}
|
||||
case enums.IMSenderTypeAI:
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusAIServing && !s.allowAIMessageOnPendingHandoff(conversation) {
|
||||
return nil, errorsx.Forbidden("当前会话不处于 AI 接待状态")
|
||||
return nil, errorsx.ForbiddenI18n("error.e0189")
|
||||
}
|
||||
if conversation.CurrentAssigneeID != 0 {
|
||||
return nil, errorsx.Forbidden("当前会话已由人工客服接管")
|
||||
return nil, errorsx.ForbiddenI18n("error.e0192")
|
||||
}
|
||||
case enums.IMSenderTypeCustomer:
|
||||
if external == nil || !ConversationService.IsCustomerConversationOwner(conversation, *external) {
|
||||
return nil, errorsx.Forbidden("无权访问该会话")
|
||||
return nil, errorsx.ForbiddenI18n("error.e0222")
|
||||
}
|
||||
default:
|
||||
return nil, errorsx.InvalidParam("不支持的发送人类型")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0080")
|
||||
}
|
||||
return conversation, nil
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type notificationService struct {
|
||||
|
||||
func (s *notificationService) Create(req request.CreateNotificationRequest) (*models.Notification, error) {
|
||||
if req.RecipientUserID <= 0 {
|
||||
return nil, errorsx.InvalidParam("接收人不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0212")
|
||||
}
|
||||
now := time.Now()
|
||||
item := &models.Notification{
|
||||
@@ -82,11 +82,11 @@ func (s *notificationService) CountUnread(userID int64) int64 {
|
||||
|
||||
func (s *notificationService) MarkRead(id int64, userID int64) error {
|
||||
if id <= 0 {
|
||||
return errorsx.InvalidParam("通知不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0337")
|
||||
}
|
||||
item := repositories.NotificationRepository.Get(sqls.DB(), id)
|
||||
if item == nil || item.RecipientUserID != userID {
|
||||
return errorsx.InvalidParam("通知不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0337")
|
||||
}
|
||||
if item.ReadAt != nil {
|
||||
return nil
|
||||
@@ -99,7 +99,7 @@ func (s *notificationService) MarkRead(id int64, userID int64) error {
|
||||
|
||||
func (s *notificationService) MarkAllRead(userID int64) error {
|
||||
if userID <= 0 {
|
||||
return errorsx.InvalidParam("接收人不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0212")
|
||||
}
|
||||
return repositories.NotificationRepository.MarkAllRead(sqls.DB(), userID, time.Now())
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (s *oidcLoginService) loginWithOIDCProfile(profile *oidcLoginProfile, authC
|
||||
}
|
||||
|
||||
if user.Status != enums.StatusOk {
|
||||
return errorsx.Unauthorized("当前系统账号已被禁用")
|
||||
return errorsx.UnauthorizedI18n("error.e0200")
|
||||
}
|
||||
|
||||
if err = repositories.UserRepository.Updates(ctx.Tx, user.ID, map[string]any{
|
||||
|
||||
@@ -74,12 +74,12 @@ func (s *quickReplyService) Delete(id int64) {
|
||||
|
||||
func (s *quickReplyService) CreateQuickReply(req request.CreateQuickReplyRequest, operator *dto.AuthPrincipal) (*models.QuickReply, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
title := strings.TrimSpace(req.Title)
|
||||
content := strings.TrimSpace(req.Content)
|
||||
if title == "" || content == "" {
|
||||
return nil, errorsx.InvalidParam("标题和内容不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0240")
|
||||
}
|
||||
item := &models.QuickReply{
|
||||
GroupName: strings.TrimSpace(req.GroupName),
|
||||
@@ -97,11 +97,11 @@ func (s *quickReplyService) CreateQuickReply(req request.CreateQuickReplyRequest
|
||||
|
||||
func (s *quickReplyService) UpdateQuickReply(req request.UpdateQuickReplyRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := s.Get(req.ID)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("快捷回复不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0203")
|
||||
}
|
||||
return s.Updates(req.ID, map[string]any{
|
||||
"group_name": strings.TrimSpace(req.GroupName),
|
||||
@@ -118,7 +118,7 @@ func (s *quickReplyService) UpdateQuickReply(req request.UpdateQuickReplyRequest
|
||||
func (s *quickReplyService) DeleteQuickReply(id int64) error {
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("快捷回复不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0203")
|
||||
}
|
||||
s.Delete(id)
|
||||
return nil
|
||||
|
||||
@@ -78,10 +78,10 @@ func (s *roleService) CreateRole(req request.CreateRoleRequest, operator *dto.Au
|
||||
name := strings.TrimSpace(req.Name)
|
||||
code := strings.TrimSpace(req.Code)
|
||||
if name == "" || code == "" {
|
||||
return nil, errorsx.InvalidParam("角色名称和编码不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0306")
|
||||
}
|
||||
if s.Take("code = ?", code) != nil {
|
||||
return nil, errorsx.InvalidParam("角色编码已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0308")
|
||||
}
|
||||
|
||||
role := &models.Role{
|
||||
@@ -102,7 +102,7 @@ func (s *roleService) CreateRole(req request.CreateRoleRequest, operator *dto.Au
|
||||
func (s *roleService) UpdateRole(req request.UpdateRoleRequest, operator *dto.AuthPrincipal) error {
|
||||
role := s.Get(req.ID)
|
||||
if role == nil {
|
||||
return errorsx.InvalidParam("角色不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0305")
|
||||
}
|
||||
now := time.Now()
|
||||
return s.Updates(req.ID, map[string]any{
|
||||
@@ -136,13 +136,13 @@ func (s *roleService) UpdateSort(ids []int64) error {
|
||||
func (s *roleService) DeleteRole(id int64) error {
|
||||
role := s.Get(id)
|
||||
if role == nil {
|
||||
return errorsx.InvalidParam("角色不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0305")
|
||||
}
|
||||
if role.IsSystem {
|
||||
return errorsx.Forbidden("系统内置角色不允许删除")
|
||||
return errorsx.ForbiddenI18n("error.e0293")
|
||||
}
|
||||
if UserRoleService.Take("role_id = ?", id) != nil {
|
||||
return errorsx.Forbidden("角色已被用户使用,无法删除")
|
||||
return errorsx.ForbiddenI18n("error.e0307")
|
||||
}
|
||||
s.Delete(id)
|
||||
return nil
|
||||
@@ -151,10 +151,10 @@ func (s *roleService) DeleteRole(id int64) error {
|
||||
func (s *roleService) UpdateStatus(id int64, status enums.Status, operator *dto.AuthPrincipal) error {
|
||||
role := s.Get(id)
|
||||
if role == nil {
|
||||
return errorsx.InvalidParam("角色不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0305")
|
||||
}
|
||||
if !slices.Contains(enums.StatusValues, status) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
if err := s.Updates(id, map[string]any{
|
||||
"status": status,
|
||||
@@ -170,7 +170,7 @@ func (s *roleService) UpdateStatus(id int64, status enums.Status, operator *dto.
|
||||
func (s *roleService) AssignPermissions(roleID int64, permissionIDs []int64, operator *dto.AuthPrincipal) error {
|
||||
role := s.Get(roleID)
|
||||
if role == nil {
|
||||
return errorsx.InvalidParam("角色不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0305")
|
||||
}
|
||||
|
||||
return s.replaceRolePermissions(roleID, permissionIDs, operator)
|
||||
@@ -184,7 +184,7 @@ func (s *roleService) replaceRolePermissions(roleID int64, permissionIDs []int64
|
||||
for _, permissionID := range permissionIDs {
|
||||
permission := PermissionService.Get(permissionID)
|
||||
if permission == nil {
|
||||
return errorsx.InvalidParam("权限不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0236")
|
||||
}
|
||||
relation := &models.RolePermission{
|
||||
RoleID: roleID,
|
||||
|
||||
@@ -86,14 +86,14 @@ func (s *skillDefinitionService) GetByIDs(ids []int64) map[int64]models.SkillDef
|
||||
|
||||
func (s *skillDefinitionService) CreateSkillDefinition(req request.CreateSkillDefinitionRequest, operator *dto.AuthPrincipal) (*models.SkillDefinition, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
normalized, err := s.normalizeSkillDefinitionRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Take("code = ?", normalized.Code) != nil {
|
||||
return nil, errorsx.InvalidParam("Skill 编码已存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0058")
|
||||
}
|
||||
item := &models.SkillDefinition{
|
||||
Code: normalized.Code,
|
||||
@@ -114,21 +114,21 @@ func (s *skillDefinitionService) CreateSkillDefinition(req request.CreateSkillDe
|
||||
|
||||
func (s *skillDefinitionService) UpdateSkillDefinition(req request.UpdateSkillDefinitionRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
return errorsx.InvalidParam("Skill ID 不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0052")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("Skill 不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0053")
|
||||
}
|
||||
normalized, err := s.normalizeSkillDefinitionRequest(req.CreateSkillDefinitionRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists := s.Take("code = ? AND id <> ?", normalized.Code, req.ID); exists != nil {
|
||||
return errorsx.InvalidParam("Skill 编码已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0058")
|
||||
}
|
||||
return repositories.SkillDefinitionRepository.Updates(sqls.DB(), req.ID, map[string]any{
|
||||
"code": normalized.Code,
|
||||
@@ -153,13 +153,13 @@ func (s *skillDefinitionService) normalizeSkillDefinitionRequest(req request.Cre
|
||||
Remark: strings.TrimSpace(req.Remark),
|
||||
}
|
||||
if normalized.Code == "" {
|
||||
return nil, errorsx.InvalidParam("Skill 编码不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0057")
|
||||
}
|
||||
if normalized.Name == "" {
|
||||
return nil, errorsx.InvalidParam("Skill 名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0055")
|
||||
}
|
||||
if normalized.Instruction == "" {
|
||||
return nil, errorsx.InvalidParam("技能说明不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0207")
|
||||
}
|
||||
examples, err := normalizeSkillStringArray(req.Examples)
|
||||
if err != nil {
|
||||
@@ -182,11 +182,11 @@ func (s *skillDefinitionService) normalizeSkillDefinitionRequest(req request.Cre
|
||||
func normalizeSkillStringArray(input []string) ([]string, error) {
|
||||
buf, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("JSON 数组格式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0031")
|
||||
}
|
||||
var ret []string
|
||||
if err := json.Unmarshal(buf, &ret); err != nil {
|
||||
return nil, errorsx.InvalidParam("JSON 数组格式不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0031")
|
||||
}
|
||||
normalized := make([]string, 0, len(ret))
|
||||
seen := make(map[string]struct{}, len(ret))
|
||||
|
||||
@@ -22,13 +22,13 @@ type skillRuntimeService struct{}
|
||||
|
||||
func (s *skillRuntimeService) DebugRun(ctx context.Context, req request.SkillDebugRunRequest) (*response.SkillDebugRunResponse, error) {
|
||||
if req.AIAgentID <= 0 {
|
||||
return nil, errorsx.InvalidParam("aiAgentId不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0061")
|
||||
}
|
||||
if strings.TrimSpace(req.SkillCode) == "" {
|
||||
return nil, errorsx.InvalidParam("skillCode不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0071")
|
||||
}
|
||||
if strings.TrimSpace(req.UserMessage) == "" {
|
||||
return nil, errorsx.InvalidParam("userMessage不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0078")
|
||||
}
|
||||
if SkillDebugRunHook == nil {
|
||||
return nil, fmt.Errorf("skill debug runner is not initialized")
|
||||
@@ -38,13 +38,13 @@ func (s *skillRuntimeService) DebugRun(ctx context.Context, req request.SkillDeb
|
||||
|
||||
func (s *skillRuntimeService) DebugResume(ctx context.Context, req request.SkillDebugResumeRequest) (*response.SkillDebugRunResponse, error) {
|
||||
if req.AIAgentID <= 0 {
|
||||
return nil, errorsx.InvalidParam("aiAgentId不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0061")
|
||||
}
|
||||
if strings.TrimSpace(req.CheckPointID) == "" {
|
||||
return nil, errorsx.InvalidParam("checkPointId不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0063")
|
||||
}
|
||||
if strings.TrimSpace(req.UserMessage) == "" {
|
||||
return nil, errorsx.InvalidParam("userMessage不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0078")
|
||||
}
|
||||
if SkillDebugResumeHook == nil {
|
||||
return nil, fmt.Errorf("skill debug resume runner is not initialized")
|
||||
|
||||
@@ -132,16 +132,16 @@ func (s *OSSStorage) getBucket() (*oss.Bucket, error) {
|
||||
|
||||
func (s *OSSStorage) validate() error {
|
||||
if strings.TrimSpace(s.cfg.Endpoint) == "" {
|
||||
return errorsx.InvalidParam("OSS endpoint 未配置")
|
||||
return errorsx.InvalidParamI18n("error.e0051")
|
||||
}
|
||||
if strings.TrimSpace(s.cfg.Bucket) == "" {
|
||||
return errorsx.InvalidParam("OSS bucket 未配置")
|
||||
return errorsx.InvalidParamI18n("error.e0050")
|
||||
}
|
||||
if strings.TrimSpace(s.cfg.AccessKeyID) == "" {
|
||||
return errorsx.InvalidParam("OSS accessKeyId 未配置")
|
||||
return errorsx.InvalidParamI18n("error.e0048")
|
||||
}
|
||||
if strings.TrimSpace(s.cfg.AccessKeySecret) == "" {
|
||||
return errorsx.InvalidParam("OSS accessKeySecret 未配置")
|
||||
return errorsx.InvalidParamI18n("error.e0049")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -45,6 +45,6 @@ func NewProvider(provider enums.AssetProvider) (FileStorageProvider, error) {
|
||||
case enums.AssetProviderOSS:
|
||||
return NewOSSStorage(cfg.OSS), nil
|
||||
default:
|
||||
return nil, errorsx.InvalidParam("不支持的文件存储类型")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0082")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,24 +87,24 @@ func (s *tagService) FindByNameAndParentID(name string, parentID int64) *models.
|
||||
|
||||
func (s *tagService) CreateTag(req request.CreateTagRequest, operator *dto.AuthPrincipal) (*models.Tag, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("标签名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0239")
|
||||
}
|
||||
|
||||
if req.ParentID > 0 {
|
||||
parent := s.Get(req.ParentID)
|
||||
if parent == nil {
|
||||
return nil, errorsx.InvalidParam("父标签不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0251")
|
||||
}
|
||||
}
|
||||
|
||||
existing := s.FindByNameAndParentID(name, req.ParentID)
|
||||
if existing != nil {
|
||||
return nil, errorsx.InvalidParam("同级下已存在相同名称的标签")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0141")
|
||||
}
|
||||
|
||||
item := &models.Tag{
|
||||
@@ -132,32 +132,32 @@ func (s *tagService) NextSortNo(parentID int64) int {
|
||||
|
||||
func (s *tagService) UpdateTag(req request.UpdateTagRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
|
||||
item := s.Get(req.ID)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("标签不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0238")
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return errorsx.InvalidParam("标签名称不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0239")
|
||||
}
|
||||
|
||||
if req.ParentID > 0 {
|
||||
if req.ParentID == req.ID {
|
||||
return errorsx.InvalidParam("不能将标签设为自己的子标签")
|
||||
return errorsx.InvalidParamI18n("error.e0083")
|
||||
}
|
||||
parent := s.Get(req.ParentID)
|
||||
if parent == nil {
|
||||
return errorsx.InvalidParam("父标签不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0251")
|
||||
}
|
||||
}
|
||||
|
||||
existing := s.FindByNameAndParentID(name, req.ParentID)
|
||||
if existing != nil && existing.ID != req.ID {
|
||||
return errorsx.InvalidParam("同级下已存在相同名称的标签")
|
||||
return errorsx.InvalidParamI18n("error.e0141")
|
||||
}
|
||||
|
||||
return s.Updates(req.ID, map[string]any{
|
||||
@@ -184,17 +184,17 @@ func (s *tagService) UpdateSort(ids []int64) error {
|
||||
func (s *tagService) DeleteTag(id int64) error {
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("标签不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0238")
|
||||
}
|
||||
|
||||
if s.HasChildren(id) {
|
||||
return errorsx.InvalidParam("该标签下存在子标签,无法删除")
|
||||
return errorsx.InvalidParamI18n("error.e0310")
|
||||
}
|
||||
if ConversationTagService.Take("tag_id = ?", id) != nil {
|
||||
return errorsx.InvalidParam("该标签已关联会话,无法删除")
|
||||
return errorsx.InvalidParamI18n("error.e0311")
|
||||
}
|
||||
if TicketTagService.Take("tag_id = ?", id) != nil {
|
||||
return errorsx.InvalidParam("该标签已关联工单,无法删除")
|
||||
return errorsx.InvalidParamI18n("error.e0312")
|
||||
}
|
||||
|
||||
s.Delete(id)
|
||||
@@ -247,16 +247,16 @@ func (s *tagService) GetSelfAndDescendantIDs(tagID int64) []int64 {
|
||||
|
||||
func (s *tagService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
|
||||
item := s.Get(id)
|
||||
if item == nil {
|
||||
return errorsx.InvalidParam("标签不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0238")
|
||||
}
|
||||
|
||||
if status != int(enums.StatusOk) && status != int(enums.StatusDisabled) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
@@ -185,22 +185,22 @@ func (s *ticketService) GetTags(ticketID int64) []models.Tag {
|
||||
|
||||
func (s *ticketService) CreateTicket(req request.CreateTicketRequest, operator *dto.AuthPrincipal) (*models.Ticket, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
title := strings.TrimSpace(req.Title)
|
||||
description := strings.TrimSpace(req.Description)
|
||||
if title == "" {
|
||||
return nil, errorsx.InvalidParam("工单标题不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0181")
|
||||
}
|
||||
if description == "" {
|
||||
return nil, errorsx.InvalidParam("工单描述不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0179")
|
||||
}
|
||||
source := enums.TicketSource(strings.TrimSpace(req.Source))
|
||||
if source == "" {
|
||||
source = enums.TicketSourceManual
|
||||
}
|
||||
if !enums.IsValidTicketSource(string(source)) {
|
||||
return nil, errorsx.InvalidParam("工单来源不合法")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0180")
|
||||
}
|
||||
if err := s.validateTicketRefs(req.CustomerID, req.ConversationID, req.CurrentAssigneeID); err != nil {
|
||||
return nil, err
|
||||
@@ -254,11 +254,11 @@ func (s *ticketService) CreateTicket(req request.CreateTicketRequest, operator *
|
||||
|
||||
func (s *ticketService) CreateFromConversation(req request.CreateTicketFromConversationRequest, operator *dto.AuthPrincipal) (*models.Ticket, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
conversation := ConversationService.Get(req.ConversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
title := strings.TrimSpace(req.Title)
|
||||
if title == "" {
|
||||
@@ -288,19 +288,19 @@ func (s *ticketService) CreateFromConversation(req request.CreateTicketFromConve
|
||||
|
||||
func (s *ticketService) UpdateTicket(req request.UpdateTicketRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
title := strings.TrimSpace(req.Title)
|
||||
description := strings.TrimSpace(req.Description)
|
||||
if title == "" {
|
||||
return errorsx.InvalidParam("工单标题不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0181")
|
||||
}
|
||||
if description == "" {
|
||||
return errorsx.InvalidParam("工单描述不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0179")
|
||||
}
|
||||
ticket := s.Get(req.TicketID)
|
||||
if ticket == nil {
|
||||
return errorsx.InvalidParam("工单不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0178")
|
||||
}
|
||||
if err := s.validateAssignee(req.CurrentAssigneeID); err != nil {
|
||||
return err
|
||||
@@ -327,22 +327,22 @@ func (s *ticketService) UpdateTicket(req request.UpdateTicketRequest, operator *
|
||||
|
||||
func (s *ticketService) LinkCustomer(ticketID int64, customerID int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
ticket := s.Get(ticketID)
|
||||
if ticket == nil {
|
||||
return errorsx.InvalidParam("工单不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0178")
|
||||
}
|
||||
if customerID <= 0 || CustomerService.Get(customerID) == nil {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
if ticket.ConversationID > 0 {
|
||||
conversation := ConversationService.Get(ticket.ConversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if conversation.CustomerID > 0 && conversation.CustomerID != customerID {
|
||||
return errorsx.InvalidParam("会话与客户不匹配")
|
||||
return errorsx.InvalidParamI18n("error.e0118")
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
@@ -356,7 +356,7 @@ func (s *ticketService) LinkCustomer(ticketID int64, customerID int64, operator
|
||||
|
||||
func (s *ticketService) AssignTicket(req request.AssignTicketRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
var assignedEvent *events.TicketAssignedEvent
|
||||
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
@@ -377,15 +377,15 @@ func (s *ticketService) AssignTicket(req request.AssignTicketRequest, operator *
|
||||
|
||||
func (s *ticketService) ChangeStatus(req request.ChangeTicketStatusRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
status := strings.TrimSpace(req.Status)
|
||||
if !enums.IsValidTicketStatus(status) {
|
||||
return errorsx.InvalidParam("工单状态不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0182")
|
||||
}
|
||||
ticket := s.Get(req.TicketID)
|
||||
if ticket == nil {
|
||||
return errorsx.InvalidParam("工单不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0178")
|
||||
}
|
||||
now := time.Now()
|
||||
var handledAt *time.Time
|
||||
@@ -403,15 +403,15 @@ func (s *ticketService) ChangeStatus(req request.ChangeTicketStatusRequest, oper
|
||||
|
||||
func (s *ticketService) AddProgress(req request.CreateTicketProgressRequest, operator *dto.AuthPrincipal) (*models.TicketProgress, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
content := strings.TrimSpace(req.Content)
|
||||
if content == "" {
|
||||
return nil, errorsx.InvalidParam("处理进展不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0148")
|
||||
}
|
||||
ticket := s.Get(req.TicketID)
|
||||
if ticket == nil {
|
||||
return nil, errorsx.InvalidParam("工单不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0178")
|
||||
}
|
||||
now := time.Now()
|
||||
progress := &models.TicketProgress{
|
||||
@@ -438,7 +438,7 @@ func (s *ticketService) AddProgress(req request.CreateTicketProgressRequest, ope
|
||||
func (s *ticketService) GetDetail(id int64) (*TicketDetailAggregate, error) {
|
||||
ticket := s.Get(id)
|
||||
if ticket == nil {
|
||||
return nil, errorsx.InvalidParam("工单不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0178")
|
||||
}
|
||||
aggregate := &TicketDetailAggregate{
|
||||
Ticket: ticket,
|
||||
@@ -497,14 +497,14 @@ func (s *ticketService) GetSummary(operator *dto.AuthPrincipal, staleHours ...in
|
||||
func (s *ticketService) assignTicketTx(tx *gorm.DB, req request.AssignTicketRequest, operator *dto.AuthPrincipal) (*events.TicketAssignedEvent, error) {
|
||||
ticket := repositories.TicketRepository.Get(tx, req.TicketID)
|
||||
if ticket == nil {
|
||||
return nil, errorsx.InvalidParam("工单不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0178")
|
||||
}
|
||||
if err := s.validateRequiredAssignee(req.ToUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toUser := repositories.UserRepository.Get(tx, req.ToUserID)
|
||||
if toUser == nil || toUser.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("负责人不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0334")
|
||||
}
|
||||
var fromUser *models.User
|
||||
if ticket.CurrentAssigneeID > 0 {
|
||||
@@ -627,15 +627,15 @@ func (s *ticketService) enrichTicketTags(db *gorm.DB, aggregate *TicketListAggre
|
||||
|
||||
func (s *ticketService) validateTicketRefs(customerID, conversationID, assigneeID int64) error {
|
||||
if customerID > 0 && CustomerService.Get(customerID) == nil {
|
||||
return errorsx.InvalidParam("客户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0155")
|
||||
}
|
||||
if conversationID > 0 {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return errorsx.InvalidParam("会话不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0116")
|
||||
}
|
||||
if customerID > 0 && conversation.CustomerID != customerID {
|
||||
return errorsx.InvalidParam("会话与客户不匹配")
|
||||
return errorsx.InvalidParamI18n("error.e0118")
|
||||
}
|
||||
}
|
||||
return s.validateAssignee(assigneeID)
|
||||
@@ -650,11 +650,11 @@ func (s *ticketService) validateAssignee(userID int64) error {
|
||||
|
||||
func (s *ticketService) validateRequiredAssignee(userID int64) error {
|
||||
if userID <= 0 {
|
||||
return errorsx.InvalidParam("负责人不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0334")
|
||||
}
|
||||
user := UserService.Get(userID)
|
||||
if user == nil || user.Status != enums.StatusOk {
|
||||
return errorsx.InvalidParam("负责人不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0334")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -66,11 +66,11 @@ func (s *ticketTagService) ValidateTagIDs(tagIDs []int64) ([]int64, error) {
|
||||
}
|
||||
tags := repositories.TagRepository.Find(sqls.DB(), sqls.NewCnd().In("id", normalized))
|
||||
if len(tags) != len(normalized) {
|
||||
return nil, errorsx.InvalidParam("存在无效工单标签")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0153")
|
||||
}
|
||||
for i := range tags {
|
||||
if tags[i].Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("存在未启用的工单标签")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0154")
|
||||
}
|
||||
}
|
||||
return normalized, nil
|
||||
|
||||
@@ -41,21 +41,21 @@ func (s *ticketViewService) ListByUser(userID int64) []models.TicketView {
|
||||
|
||||
func (s *ticketViewService) Save(req request.SaveTicketViewRequest, operator *dto.AuthPrincipal) (*models.TicketView, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errorsx.InvalidParam("视图名称不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0303")
|
||||
}
|
||||
filtersJSON, err := json.Marshal(req.Filters)
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("视图筛选条件格式不正确")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0304")
|
||||
}
|
||||
now := time.Now()
|
||||
if req.ID > 0 {
|
||||
item := repositories.TicketViewRepository.Get(sqls.DB(), req.ID)
|
||||
if item == nil || item.UserID != operator.UserID {
|
||||
return nil, errorsx.InvalidParam("视图不存在")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0302")
|
||||
}
|
||||
if err := repositories.TicketViewRepository.Updates(sqls.DB(), req.ID, map[string]any{
|
||||
"name": name,
|
||||
@@ -82,11 +82,11 @@ func (s *ticketViewService) Save(req request.SaveTicketViewRequest, operator *dt
|
||||
|
||||
func (s *ticketViewService) Delete(id int64, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
item := repositories.TicketViewRepository.Get(sqls.DB(), id)
|
||||
if item == nil || item.UserID != operator.UserID {
|
||||
return errorsx.InvalidParam("视图不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0302")
|
||||
}
|
||||
return repositories.TicketViewRepository.Delete(sqls.DB(), id)
|
||||
}
|
||||
|
||||
@@ -95,21 +95,21 @@ func (s *toolCatalogService) ValidateToolCode(toolCode string) error {
|
||||
cfg := config.Current()
|
||||
toolCode = strings.TrimSpace(toolCode)
|
||||
if toolCode == "" {
|
||||
return errorsx.InvalidParam("toolCode不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0074")
|
||||
}
|
||||
if toolx.IsAgentDirectToolCode(toolCode) {
|
||||
return nil
|
||||
}
|
||||
serverCode, toolName := toolx.SplitMCPToolCode(toolCode)
|
||||
if serverCode == "" || toolName == "" {
|
||||
return errorsx.InvalidParam("toolCode格式不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0075")
|
||||
}
|
||||
if !cfg.MCP.Enabled {
|
||||
return errorsx.InvalidParam("MCP未启用")
|
||||
return errorsx.InvalidParamI18n("error.e0035")
|
||||
}
|
||||
server, ok := cfg.MCP.Servers[serverCode]
|
||||
if !ok || !server.Enabled {
|
||||
return errorsx.InvalidParam("toolCode 绑定的 MCP 服务不存在或未启用")
|
||||
return errorsx.InvalidParamI18n("error.e0073")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -91,19 +91,19 @@ func (s *userService) GetByEmail(email string) *models.User {
|
||||
func (s *userService) CreateUser(req request.CreateUserRequest, operator *dto.AuthPrincipal) (*models.User, string, error) {
|
||||
username := strings.TrimSpace(req.Username)
|
||||
if username == "" {
|
||||
return nil, "", errorsx.InvalidParam("用户名不能为空")
|
||||
return nil, "", errorsx.InvalidParamI18n("error.e0257")
|
||||
}
|
||||
if s.GetByUsername(username) != nil {
|
||||
return nil, "", errorsx.InvalidParam("用户名已存在")
|
||||
return nil, "", errorsx.InvalidParamI18n("error.e0259")
|
||||
}
|
||||
|
||||
mobile := utils.NormalizeNullableString(req.Mobile)
|
||||
email := utils.NormalizeNullableString(req.Email)
|
||||
if mobile != nil && s.GetByMobile(*mobile) != nil {
|
||||
return nil, "", errorsx.InvalidParam("手机号已存在")
|
||||
return nil, "", errorsx.InvalidParamI18n("error.e0206")
|
||||
}
|
||||
if email != nil && s.GetByEmail(*email) != nil {
|
||||
return nil, "", errorsx.InvalidParam("邮箱已存在")
|
||||
return nil, "", errorsx.InvalidParamI18n("error.e0338")
|
||||
}
|
||||
|
||||
plain, err := utils.GenerateRandomPassword(12)
|
||||
@@ -146,19 +146,19 @@ func (s *userService) CreateUser(req request.CreateUserRequest, operator *dto.Au
|
||||
func (s *userService) UpdateUser(req request.UpdateUserRequest, operator *dto.AuthPrincipal) error {
|
||||
user := s.Get(req.ID)
|
||||
if user == nil || user.DeletedAt != nil {
|
||||
return errorsx.InvalidParam("用户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0255")
|
||||
}
|
||||
|
||||
mobile := utils.NormalizeNullableString(req.Mobile)
|
||||
email := utils.NormalizeNullableString(req.Email)
|
||||
if mobile != nil {
|
||||
if existed := s.GetByMobile(*mobile); existed != nil && existed.ID != req.ID {
|
||||
return errorsx.InvalidParam("手机号已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0206")
|
||||
}
|
||||
}
|
||||
if email != nil {
|
||||
if existed := s.GetByEmail(*email); existed != nil && existed.ID != req.ID {
|
||||
return errorsx.InvalidParam("邮箱已存在")
|
||||
return errorsx.InvalidParamI18n("error.e0338")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ func (s *userService) UpdateUser(req request.UpdateUserRequest, operator *dto.Au
|
||||
func (s *userService) DeleteUser(id int64, operator *dto.AuthPrincipal) error {
|
||||
user := s.Get(id)
|
||||
if user == nil {
|
||||
return errorsx.InvalidParam("用户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0255")
|
||||
}
|
||||
|
||||
if err := s.Updates(id, map[string]any{
|
||||
@@ -195,10 +195,10 @@ func (s *userService) DeleteUser(id int64, operator *dto.AuthPrincipal) error {
|
||||
func (s *userService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||
user := s.Get(id)
|
||||
if user == nil {
|
||||
return errorsx.InvalidParam("用户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0255")
|
||||
}
|
||||
if !slices.Contains(enums.StatusValues, enums.Status(status)) {
|
||||
return errorsx.InvalidParam("状态值不合法")
|
||||
return errorsx.InvalidParamI18n("error.e0254")
|
||||
}
|
||||
if err := s.Updates(id, map[string]any{
|
||||
"status": status,
|
||||
@@ -227,7 +227,7 @@ func (s *userService) ResetPassword(userID int64, operator *dto.AuthPrincipal) (
|
||||
|
||||
func (s *userService) ChangeOwnPassword(password string, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil || operator.UserID <= 0 {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
return errorsx.UnauthorizedI18n("error.auth.expired")
|
||||
}
|
||||
return s.changePassword(operator.UserID, password, operator)
|
||||
}
|
||||
@@ -235,7 +235,7 @@ func (s *userService) ChangeOwnPassword(password string, operator *dto.AuthPrinc
|
||||
func (s *userService) AssignRoles(userID int64, roleIDs []int64, operator *dto.AuthPrincipal) error {
|
||||
user := s.Get(userID)
|
||||
if user == nil || user.DeletedAt != nil {
|
||||
return errorsx.InvalidParam("用户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0255")
|
||||
}
|
||||
if err := s.replaceUserRoles(userID, roleIDs, operator); err != nil {
|
||||
return err
|
||||
@@ -256,10 +256,10 @@ func (s *userService) replaceUserRolesDB(db *gorm.DB, userID int64, roleIDs []in
|
||||
for _, roleID := range roleIDs {
|
||||
role := RoleService.Get(roleID)
|
||||
if role == nil {
|
||||
return errorsx.InvalidParam("角色不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0305")
|
||||
}
|
||||
if role.Status != enums.StatusOk {
|
||||
return errorsx.InvalidParam("禁用角色不允许分配")
|
||||
return errorsx.InvalidParamI18n("error.e0291")
|
||||
}
|
||||
relation := &models.UserRole{
|
||||
UserID: userID,
|
||||
@@ -276,10 +276,10 @@ func (s *userService) replaceUserRolesDB(db *gorm.DB, userID int64, roleIDs []in
|
||||
func (s *userService) changePassword(userID int64, password string, operator *dto.AuthPrincipal) error {
|
||||
user := s.Get(userID)
|
||||
if user == nil || user.DeletedAt != nil {
|
||||
return errorsx.InvalidParam("用户不存在")
|
||||
return errorsx.InvalidParamI18n("error.e0255")
|
||||
}
|
||||
if strings.TrimSpace(password) == "" {
|
||||
return errorsx.InvalidParam("新密码不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0220")
|
||||
}
|
||||
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"agent-desk/internal/pkg/dto/response"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/pkg/openidentity"
|
||||
"agent-desk/internal/pkg/utils"
|
||||
"encoding/json"
|
||||
@@ -44,7 +45,7 @@ func newWsService() *wsService {
|
||||
func (s *wsService) HandleDashboardWS(ctx *gin.Context) {
|
||||
principal := AuthService.GetAuthPrincipal(ctx)
|
||||
if principal == nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, web.JsonError(errorsx.Unauthorized("未登录或登录已过期")))
|
||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, web.JsonErrorCode(errorsx.CodeAuthUnauthorized, i18nx.T(ctx, "error.auth.expired")))
|
||||
return
|
||||
}
|
||||
if err := s.upgradeConnection(ctx, principal, nil, realtimeRoleAdmin); err != nil {
|
||||
@@ -57,7 +58,7 @@ func (s *wsService) HandleDashboardWS(ctx *gin.Context) {
|
||||
func (s *wsService) HandleDashboardNotificationWS(ctx *gin.Context) {
|
||||
principal := AuthService.GetAuthPrincipal(ctx)
|
||||
if principal == nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, web.JsonError(errorsx.Unauthorized("未登录或登录已过期")))
|
||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, web.JsonErrorCode(errorsx.CodeAuthUnauthorized, i18nx.T(ctx, "error.auth.expired")))
|
||||
return
|
||||
}
|
||||
if err := s.upgradeConnection(ctx, principal, nil, realtimeRoleNotification); err != nil {
|
||||
@@ -70,7 +71,7 @@ func (s *wsService) HandleDashboardNotificationWS(ctx *gin.Context) {
|
||||
func (s *wsService) HandleOpenWS(ctx *gin.Context) {
|
||||
channel := ChannelService.GetEnabledChannel(ctx)
|
||||
if channel == nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, web.JsonErrorMsg("接入渠道不存在或已停用"))
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, web.JsonErrorCode(errorsx.CodeInvalidParam, i18nx.T(ctx, "error.e0209")))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func (s *wxWorkKFInboundService) SyncCallbackMessages(message kf.CallbackMessage
|
||||
func (s *wxWorkKFInboundService) consumeSyncMessage(item syncmsg.Message) error {
|
||||
msgID := strings.TrimSpace(item.MsgID)
|
||||
if msgID == "" {
|
||||
return errorsx.InvalidParam("企业微信消息ID不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0101")
|
||||
}
|
||||
if WxWorkKFMessageRefService.Take("wx_msg_id = ?", msgID) != nil {
|
||||
return nil
|
||||
@@ -369,7 +369,7 @@ func (s *wxWorkKFInboundService) recordOrphanEvent(item syncmsg.Message, content
|
||||
func (s *wxWorkKFInboundService) ensureConversation(base syncmsg.BaseMessage, profile map[string]any) (*models.Conversation, error) {
|
||||
externalID := strings.TrimSpace(base.ExternalUserID)
|
||||
if externalID == "" {
|
||||
return nil, errorsx.InvalidParam("企业微信客户ID不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0096")
|
||||
}
|
||||
channel, err := s.getChannelByOpenKfID(base.OpenKFID)
|
||||
if err != nil {
|
||||
@@ -474,7 +474,7 @@ func (s *wxWorkKFInboundService) createMessageRef(conversationID, messageID int6
|
||||
func (s *wxWorkKFInboundService) saveNextCursor(openKfID, nextCursor string) error {
|
||||
openKfID = strings.TrimSpace(openKfID)
|
||||
if openKfID == "" {
|
||||
return errorsx.InvalidParam("openKfID不能为空")
|
||||
return errorsx.InvalidParamI18n("error.e0068")
|
||||
}
|
||||
now := time.Now()
|
||||
state := WxWorkKFSyncStateService.Take("open_kf_id = ?", openKfID)
|
||||
@@ -517,7 +517,7 @@ func (s *wxWorkKFInboundService) appendConversationEvent(conversationID int64, c
|
||||
func (s *wxWorkKFInboundService) buildInboundAssetPayload(conversationID int64, mediaID string) (string, string, error) {
|
||||
mediaID = strings.TrimSpace(mediaID)
|
||||
if mediaID == "" {
|
||||
return "", "", errorsx.InvalidParam("企业微信媒体ID不能为空")
|
||||
return "", "", errorsx.InvalidParamI18n("error.e0095")
|
||||
}
|
||||
materialCli := wxwork.GetWorkCli().GetMaterial()
|
||||
data, err := materialCli.GetTempFile(mediaID)
|
||||
@@ -542,18 +542,18 @@ func (s *wxWorkKFInboundService) buildInboundAssetPayload(conversationID int64,
|
||||
func (s *wxWorkKFInboundService) getChannelByOpenKfID(openKfID string) (*models.Channel, error) {
|
||||
openKfID = strings.TrimSpace(openKfID)
|
||||
if openKfID == "" {
|
||||
return nil, errorsx.InvalidParam("企业微信 openKfID 不能为空")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0094")
|
||||
}
|
||||
channel := ChannelService.GetEnabledWxWorkKFChannelByOpenKfID(openKfID)
|
||||
if channel == nil {
|
||||
return nil, errorsx.InvalidParam("未找到匹配的企业微信接入渠道")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0231")
|
||||
}
|
||||
if channel.AIAgentID <= 0 {
|
||||
return nil, errorsx.InvalidParam("企业微信接入渠道未绑定AI Agent")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0098")
|
||||
}
|
||||
agent := AIAgentService.Get(channel.AIAgentID)
|
||||
if agent == nil || agent.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("企业微信接入渠道绑定的AI Agent不存在或已禁用")
|
||||
return nil, errorsx.InvalidParamI18n("error.e0099")
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user