This commit is contained in:
mlogclub
2026-04-09 10:01:23 +08:00
commit efe801b8bf
707 changed files with 110595 additions and 0 deletions
@@ -0,0 +1,82 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/utils"
"cs-agent/internal/services"
)
func BuildAgentProfileList(items []models.AgentProfile) []response.AgentProfileResponse {
if len(items) == 0 {
return []response.AgentProfileResponse{}
}
userIDs := make([]int64, 0, len(items))
teamIDs := make([]int64, 0, len(items))
for _, item := range items {
if item.UserID > 0 {
userIDs = append(userIDs, item.UserID)
}
if item.TeamID > 0 {
teamIDs = append(teamIDs, item.TeamID)
}
}
users := services.UserService.FindByIds(userIDs)
teams := services.AgentTeamService.FindByIds(teamIDs)
userMap := make(map[int64]*models.User, len(users))
for i := range users {
userMap[users[i].ID] = &users[i]
}
teamMap := make(map[int64]*models.AgentTeam, len(teams))
for i := range teams {
teamMap[teams[i].ID] = &teams[i]
}
results := make([]response.AgentProfileResponse, 0, len(items))
for _, item := range items {
if result := doBuildAgentProfileResponse(&item, userMap[item.UserID], teamMap[item.TeamID]); result != nil {
results = append(results, *result)
}
}
return results
}
func BuildAgentProfileResponse(item *models.AgentProfile) *response.AgentProfileResponse {
user := services.UserService.Get(item.UserID)
team := services.AgentTeamService.Get(item.TeamID)
return doBuildAgentProfileResponse(item, user, team)
}
func doBuildAgentProfileResponse(item *models.AgentProfile, user *models.User, team *models.AgentTeam) *response.AgentProfileResponse {
if item == nil {
return nil
}
ret := &response.AgentProfileResponse{
ID: item.ID,
UserID: item.UserID,
TeamID: item.TeamID,
AgentCode: item.AgentCode,
DisplayName: item.DisplayName,
Avatar: item.Avatar,
ServiceStatus: item.ServiceStatus,
MaxConcurrentCount: item.MaxConcurrentCount,
PriorityLevel: item.PriorityLevel,
AutoAssignEnabled: item.AutoAssignEnabled,
ReceiveOfflineMessage: item.ReceiveOfflineMessage,
LastOnlineAt: utils.FormatTimePtr(item.LastOnlineAt),
LastStatusAt: utils.FormatTimePtr(item.LastStatusAt),
Remark: item.Remark,
}
if user != nil {
ret.Username = user.Username
ret.Nickname = user.Nickname
}
if team != nil {
ret.TeamName = team.Name
}
return ret
}
@@ -0,0 +1,30 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
)
func BuildAgentRunLog(item *models.AgentRunLog) response.AgentRunLogResponse {
if item == nil {
return response.AgentRunLogResponse{}
}
return response.AgentRunLogResponse{
ID: item.ID,
ConversationID: item.ConversationID,
MessageID: item.MessageID,
AIAgentID: item.AIAgentID,
AIConfigID: item.AIConfigID,
UserMessage: item.UserMessage,
PlannedAction: item.PlannedAction,
PlannedSkillCode: item.PlannedSkillCode,
PlannedToolCode: item.PlannedToolCode,
PlanReason: item.PlanReason,
FinalAction: item.FinalAction,
ReplyText: item.ReplyText,
ErrorMessage: item.ErrorMessage,
LatencyMs: item.LatencyMs,
TraceData: item.TraceData,
CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"),
}
}
+27
View File
@@ -0,0 +1,27 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
)
func BuildAsset(item *models.Asset) response.AssetResponse {
ret := response.AssetResponse{
ID: item.ID,
AssetID: item.AssetID,
Provider: item.Provider,
Filename: item.Filename,
FileSize: item.FileSize,
MimeType: item.MimeType,
URL: item.URL,
StorageKey: item.StorageKey,
Status: item.Status,
CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: item.UpdatedAt.Format("2006-01-02 15:04:05"),
CreateUserID: item.CreateUserID,
CreateUserName: item.CreateUserName,
UpdateUserID: item.UpdateUserID,
UpdateUserName: item.UpdateUserName,
}
return ret
}
+32
View File
@@ -0,0 +1,32 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"time"
)
func BuildCompany(item *models.Company) *response.CompanyResponse {
if item == nil {
return nil
}
return &response.CompanyResponse{
ID: item.ID,
Name: item.Name,
Code: item.Code,
Status: item.Status,
Remark: item.Remark,
CreatedAt: item.CreatedAt.Format(time.DateTime),
UpdatedAt: item.UpdatedAt.Format(time.DateTime),
}
}
func BuildCompanyList(list []models.Company) []response.CompanyResponse {
results := make([]response.CompanyResponse, 0, len(list))
for _, item := range list {
if company := BuildCompany(&item); company != nil {
results = append(results, *company)
}
}
return results
}
+267
View File
@@ -0,0 +1,267 @@
package builders
import (
"strings"
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/enums"
"cs-agent/internal/pkg/utils"
"cs-agent/internal/services"
"github.com/mlogclub/simple/sqls"
)
func BuildConversation(item *models.Conversation) response.ConversationResponse {
agentReadState, customerReadState := services.ConversationReadStateService.GetConversationReadStates(item.ID)
ret := response.ConversationResponse{
ID: item.ID,
AIAgentID: item.AIAgentID,
CustomerID: item.CustomerID,
ExternalSource: item.ExternalSource,
ExternalID: item.ExternalID,
Subject: item.Subject,
Status: item.Status,
ServiceMode: item.ServiceMode,
Priority: item.Priority,
CurrentAssigneeID: item.CurrentAssigneeID,
LastMessageID: item.LastMessageID,
LastMessageAt: utils.FormatTime(item.LastMessageAt),
LastActiveAt: utils.FormatTime(item.LastActiveAt),
LastMessageSummary: item.LastMessageSummary,
CustomerUnreadCount: item.CustomerUnreadCount,
AgentUnreadCount: item.AgentUnreadCount,
CustomerLastReadMessageID: readStateMessageID(customerReadState),
CustomerLastReadSeqNo: readStateSeqNo(customerReadState),
CustomerLastReadAt: readStateAt(customerReadState),
AgentLastReadMessageID: readStateMessageID(agentReadState),
AgentLastReadSeqNo: readStateSeqNo(agentReadState),
AgentLastReadAt: readStateAt(agentReadState),
ClosedAt: utils.FormatTimePtr(item.ClosedAt),
ClosedBy: item.ClosedBy,
CloseReason: item.CloseReason,
}
if item.CurrentAssigneeID > 0 {
if user := services.UserService.Get(item.CurrentAssigneeID); user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
}
if item.ClosedBy > 0 {
if user := services.UserService.Get(item.ClosedBy); user != nil {
ret.ClosedByName = user.Nickname
if ret.ClosedByName == "" {
ret.ClosedByName = user.Username
}
}
}
return ret
}
func BuildParticipantResponses(conversationID int64) []response.ConversationParticipantResponse {
list := services.ConversationParticipantService.Find(sqls.NewCnd().Eq("conversation_id", conversationID).Asc("id"))
if len(list) == 0 {
return nil
}
ret := make([]response.ConversationParticipantResponse, 0, len(list))
for _, item := range list {
ret = append(ret, response.ConversationParticipantResponse{
ID: item.ID,
ParticipantType: item.ParticipantType,
ParticipantID: item.ParticipantID,
ExternalParticipantID: item.ExternalParticipantID,
JoinedAt: utils.FormatTimePtr(item.JoinedAt),
LeftAt: utils.FormatTimePtr(item.LeftAt),
Status: item.Status,
})
}
return ret
}
func BuildMessages(list []models.Message) []response.MessageResponse {
if len(list) == 0 {
return nil
}
agentReadState, customerReadState := services.ConversationReadStateService.GetConversationReadStates(list[0].ConversationID)
aiSenderNames, userSenderNames := collectMessageSenderNameMaps(list)
agentProfiles := collectAgentProfilesByMessages(list)
ret := make([]response.MessageResponse, 0, len(list))
for i := range list {
ret = append(ret, BuildMessageWithReadStates(&list[i], agentReadState, customerReadState, aiSenderNames, userSenderNames, agentProfiles))
}
return ret
}
func BuildMessage(item *models.Message) response.MessageResponse {
agentReadState, customerReadState := services.ConversationReadStateService.GetConversationReadStates(item.ConversationID)
return BuildMessageWithReadStates(item, agentReadState, customerReadState, nil, nil, nil)
}
func BuildMessageWithReadStates(item *models.Message, agentReadState, customerReadState *models.ConversationReadState, aiSenderNames, userSenderNames map[int64]string, agentProfiles map[int64]*models.AgentProfile) response.MessageResponse {
content, payload := buildMessageResponseContent(item)
ret := response.MessageResponse{
ID: item.ID,
ConversationID: item.ConversationID,
ClientMsgID: item.ClientMsgID,
SenderType: item.SenderType,
SenderID: item.SenderID,
MessageType: item.MessageType,
Content: content,
Payload: payload,
SeqNo: item.SeqNo,
SendStatus: item.SendStatus,
SentAt: utils.FormatTimePtr(item.SentAt),
DeliveredAt: utils.FormatTimePtr(item.DeliveredAt),
ReadAt: utils.FormatTimePtr(item.ReadAt),
CustomerRead: isMessageRead(item, customerReadState),
CustomerReadAt: readMessageAt(item, customerReadState),
AgentRead: isMessageRead(item, agentReadState),
AgentReadAt: readMessageAt(item, agentReadState),
RecalledAt: utils.FormatTimePtr(item.RecalledAt),
QuotedMessageID: item.QuotedMessageID,
}
if item.SenderID > 0 {
if item.SenderType == enums.IMSenderTypeAI {
if aiSenderNames != nil {
ret.SenderName = aiSenderNames[item.SenderID]
} else if aiAgent := services.AIAgentService.Get(item.SenderID); aiAgent != nil {
ret.SenderName = aiAgent.Name
}
} else if item.SenderType == enums.IMSenderTypeAgent {
profile := agentProfiles[item.SenderID]
if profile != nil {
if dn := strings.TrimSpace(profile.DisplayName); dn != "" {
ret.SenderName = dn
}
if av := strings.TrimSpace(profile.Avatar); av != "" {
ret.SenderAvatar = av
}
}
if ret.SenderName == "" {
if userSenderNames != nil {
ret.SenderName = userSenderNames[item.SenderID]
} else if user := services.UserService.Get(item.SenderID); user != nil {
ret.SenderName = user.Nickname
if ret.SenderName == "" {
ret.SenderName = user.Username
}
}
}
} else if userSenderNames != nil {
ret.SenderName = userSenderNames[item.SenderID]
} else if user := services.UserService.Get(item.SenderID); user != nil {
ret.SenderName = user.Nickname
if ret.SenderName == "" {
ret.SenderName = user.Username
}
}
}
return ret
}
func buildMessageResponseContent(item *models.Message) (content, payload string) {
if item == nil {
return "", ""
}
if item.RecalledAt != nil || item.SendStatus == int(enums.IMMessageStatusRecalled) {
return "该消息已撤回", ""
}
return item.Content, item.Payload
}
func collectAgentProfilesByMessages(list []models.Message) map[int64]*models.AgentProfile {
var agentUserIDs []int64
seen := make(map[int64]struct{})
for i := range list {
m := &list[i]
if m.SenderType != enums.IMSenderTypeAgent || m.SenderID <= 0 {
continue
}
if _, ok := seen[m.SenderID]; ok {
continue
}
seen[m.SenderID] = struct{}{}
agentUserIDs = append(agentUserIDs, m.SenderID)
}
if len(agentUserIDs) == 0 {
return nil
}
profiles := services.AgentProfileService.Find(sqls.NewCnd().In("user_id", agentUserIDs))
out := make(map[int64]*models.AgentProfile, len(profiles))
for i := range profiles {
out[profiles[i].UserID] = &profiles[i]
}
return out
}
func collectMessageSenderNameMaps(list []models.Message) (aiNames map[int64]string, userNames map[int64]string) {
aiNames = make(map[int64]string)
userNames = make(map[int64]string)
var aiIDs, userIDs []int64
seenAI := make(map[int64]struct{})
seenUser := make(map[int64]struct{})
for i := range list {
m := &list[i]
if m.SenderID <= 0 {
continue
}
if m.SenderType == enums.IMSenderTypeAI {
if _, ok := seenAI[m.SenderID]; ok {
continue
}
seenAI[m.SenderID] = struct{}{}
aiIDs = append(aiIDs, m.SenderID)
continue
}
if _, ok := seenUser[m.SenderID]; ok {
continue
}
seenUser[m.SenderID] = struct{}{}
userIDs = append(userIDs, m.SenderID)
}
for _, a := range services.AIAgentService.FindByIds(aiIDs) {
aiNames[a.ID] = a.Name
}
for _, u := range services.UserService.FindByIds(userIDs) {
name := u.Nickname
if name == "" {
name = u.Username
}
userNames[u.ID] = name
}
return aiNames, userNames
}
func isMessageRead(item *models.Message, state *models.ConversationReadState) bool {
return item != nil && state != nil && state.LastReadSeqNo >= item.SeqNo
}
func readMessageAt(item *models.Message, state *models.ConversationReadState) string {
if !isMessageRead(item, state) {
return ""
}
return utils.FormatTimePtr(state.LastReadAt)
}
func readStateMessageID(state *models.ConversationReadState) int64 {
if state == nil {
return 0
}
return state.LastReadMessageID
}
func readStateSeqNo(state *models.ConversationReadState) int64 {
if state == nil {
return 0
}
return state.LastReadSeqNo
}
func readStateAt(state *models.ConversationReadState) string {
if state == nil {
return ""
}
return utils.FormatTimePtr(state.LastReadAt)
}
+39
View File
@@ -0,0 +1,39 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/utils"
"cs-agent/internal/services"
"time"
)
func BuildCustomer(item *models.Customer) *response.CustomerResponse {
if item == nil {
return nil
}
return &response.CustomerResponse{
ID: item.ID,
Name: item.Name,
Gender: item.Gender,
CompanyID: item.CompanyID,
Company: BuildCompany(services.CompanyService.Get(item.CompanyID)),
LastActiveAt: utils.FormatTimePtr(item.LastActiveAt),
PrimaryMobile: item.PrimaryMobile,
PrimaryEmail: item.PrimaryEmail,
Status: item.Status,
Remark: item.Remark,
CreatedAt: item.CreatedAt.Format(time.DateTime),
UpdatedAt: item.UpdatedAt.Format(time.DateTime),
}
}
func BuildCustomerList(list []models.Customer) []response.CustomerResponse {
results := make([]response.CustomerResponse, 0, len(list))
for _, item := range list {
if customer := BuildCustomer(&item); customer != nil {
results = append(results, *customer)
}
}
return results
}
@@ -0,0 +1,36 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/utils"
"time"
)
func BuildCustomerContactResponse(item *models.CustomerContact) response.CustomerContactResponse {
if item == nil {
return response.CustomerContactResponse{}
}
return response.CustomerContactResponse{
ID: item.ID,
CustomerID: item.CustomerID,
ContactType: item.ContactType,
ContactValue: item.ContactValue,
IsPrimary: item.IsPrimary,
IsVerified: item.IsVerified,
VerifiedAt: utils.FormatTimePtr(item.VerifiedAt),
Source: item.Source,
Status: item.Status,
Remark: item.Remark,
CreatedAt: item.CreatedAt.Format(time.DateTime),
UpdatedAt: item.UpdatedAt.Format(time.DateTime),
}
}
func BuildCustomerContactList(list []models.CustomerContact) []response.CustomerContactResponse {
results := make([]response.CustomerContactResponse, 0, len(list))
for i := range list {
results = append(results, BuildCustomerContactResponse(&list[i]))
}
return results
}
+152
View File
@@ -0,0 +1,152 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/enums"
"encoding/json"
)
func BuildKnowledgeBase(item *models.KnowledgeBase) response.KnowledgeBaseResponse {
return response.KnowledgeBaseResponse{
ID: item.ID,
Name: item.Name,
Description: item.Description,
KnowledgeType: item.KnowledgeType,
KnowledgeTypeName: enums.GetKnowledgeBaseTypeLabel(enums.KnowledgeBaseType(item.KnowledgeType)),
Status: item.Status,
StatusName: enums.GetStatusLabel(item.Status),
DefaultTopK: item.DefaultTopK,
DefaultScoreThreshold: item.DefaultScoreThreshold,
DefaultRerankLimit: item.DefaultRerankLimit,
ChunkProvider: item.ChunkProvider,
ChunkTargetTokens: item.ChunkTargetTokens,
ChunkMaxTokens: item.ChunkMaxTokens,
ChunkOverlapTokens: item.ChunkOverlapTokens,
AnswerMode: item.AnswerMode,
AnswerModeName: enums.GetKnowledgeAnswerModeLabel(enums.KnowledgeAnswerMode(item.AnswerMode)),
FallbackMode: item.FallbackMode,
FallbackModeName: enums.GetKnowledgeFallbackModeLabel(enums.KnowledgeFallbackMode(item.FallbackMode)),
Remark: item.Remark,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
CreateUserName: item.CreateUserName,
UpdateUserName: item.UpdateUserName,
}
}
func BuildKnowledgeDocument(item *models.KnowledgeDocument) response.KnowledgeDocumentResponse {
return response.KnowledgeDocumentResponse{
ID: item.ID,
KnowledgeBaseID: item.KnowledgeBaseID,
Title: item.Title,
Status: item.Status,
StatusName: enums.GetStatusLabel(item.Status),
IndexStatus: item.IndexStatus,
IndexStatusName: enums.GetKnowledgeDocumentIndexStatusLabel(item.IndexStatus),
IndexedAt: item.IndexedAt,
IndexError: item.IndexError,
ContentHash: item.ContentHash,
ContentType: item.ContentType,
Content: item.Content,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
CreateUserName: item.CreateUserName,
UpdateUserName: item.UpdateUserName,
}
}
func BuildKnowledgeFAQ(item *models.KnowledgeFAQ) response.KnowledgeFAQResponse {
return response.KnowledgeFAQResponse{
ID: item.ID,
KnowledgeBaseID: item.KnowledgeBaseID,
Question: item.Question,
Answer: item.Answer,
SimilarQuestions: parseSimilarQuestions(item.SimilarQuestions),
Status: item.Status,
StatusName: enums.GetStatusLabel(item.Status),
IndexStatus: item.IndexStatus,
IndexStatusName: enums.GetKnowledgeDocumentIndexStatusLabel(item.IndexStatus),
IndexedAt: item.IndexedAt,
IndexError: item.IndexError,
Remark: item.Remark,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
CreateUserName: item.CreateUserName,
UpdateUserName: item.UpdateUserName,
}
}
func BuildKnowledgeRetrieveLog(item *models.KnowledgeRetrieveLog) response.KnowledgeRetrieveLogResponse {
return response.KnowledgeRetrieveLogResponse{
ID: item.ID,
KnowledgeBaseID: item.KnowledgeBaseID,
Channel: item.Channel,
ChannelName: enums.GetKnowledgeRetrieveChannelLabel(enums.KnowledgeRetrieveChannel(item.Channel)),
Scene: item.Scene,
SceneName: enums.GetKnowledgeRetrieveSceneLabel(enums.KnowledgeRetrieveScene(item.Scene)),
SessionID: item.SessionID,
ConversationID: item.ConversationID,
RequestID: item.RequestID,
Question: item.Question,
RewriteQuestion: item.RewriteQuestion,
Answer: item.Answer,
AnswerStatus: item.AnswerStatus,
AnswerStatusName: enums.GetKnowledgeAnswerStatusLabel(enums.KnowledgeAnswerStatus(item.AnswerStatus)),
HitCount: item.HitCount,
TopScore: item.TopScore,
ChunkProvider: item.ChunkProvider,
ChunkTargetTokens: item.ChunkTargetTokens,
ChunkMaxTokens: item.ChunkMaxTokens,
ChunkOverlapTokens: item.ChunkOverlapTokens,
RerankEnabled: item.RerankEnabled,
RerankLimit: item.RerankLimit,
CitationCount: item.CitationCount,
UsedChunkCount: item.UsedChunkCount,
LatencyMs: item.LatencyMs,
RetrieveMs: item.RetrieveMs,
GenerateMs: item.GenerateMs,
PromptTokens: item.PromptTokens,
CompletionTokens: item.CompletionTokens,
ModelName: item.ModelName,
TraceData: item.TraceData,
CreatedAt: item.CreatedAt,
}
}
func BuildKnowledgeRetrieveHitResponse(item *models.KnowledgeRetrieveHit) response.KnowledgeRetrieveHitResponse {
return response.KnowledgeRetrieveHitResponse{
ID: item.ID,
RetrieveLogID: item.RetrieveLogID,
KnowledgeBaseID: item.KnowledgeBaseID,
ChunkID: item.ChunkID,
DocumentID: item.DocumentID,
DocumentTitle: item.DocumentTitle,
FaqID: item.FaqID,
FaqQuestion: item.FaqQuestion,
ChunkNo: item.ChunkNo,
Title: item.Title,
SectionPath: item.SectionPath,
ChunkType: item.ChunkType,
ChunkTypeName: enums.GetKnowledgeChunkTypeLabel(enums.KnowledgeChunkType(item.ChunkType)),
Provider: item.Provider,
RankNo: item.RankNo,
Score: item.Score,
RerankScore: item.RerankScore,
UsedInAnswer: item.UsedInAnswer,
IsCitation: item.IsCitation,
Snippet: item.Snippet,
CreatedAt: item.CreatedAt,
}
}
func parseSimilarQuestions(raw string) []string {
if raw == "" {
return []string{}
}
var items []string
if err := json.Unmarshal([]byte(raw), &items); err != nil {
return []string{}
}
return items
}
+35
View File
@@ -0,0 +1,35 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/enums"
)
func BuildSkillDefinitionResponse(item *models.SkillDefinition) response.SkillDefinitionResponse {
return response.SkillDefinitionResponse{
ID: item.ID,
Code: item.Code,
Name: item.Name,
Description: item.Description,
Prompt: item.Prompt,
ExecutionMode: string(item.ExecutionMode),
ExecutionModeName: enums.GetSkillExecutionModeLabel(item.ExecutionMode),
ExecutionConfig: item.ExecutionConfig,
Priority: item.Priority,
Status: int(item.Status),
StatusName: getSkillStatusName(item.Status),
Remark: item.Remark,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
CreateUserName: item.CreateUserName,
UpdateUserName: item.UpdateUserName,
}
}
func getSkillStatusName(status enums.Status) string {
if label := enums.GetStatusLabel(status); label != "" {
return label
}
return "未知"
}
+75
View File
@@ -0,0 +1,75 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"time"
)
func BuildTagResponse(item *models.Tag) response.TagResponse {
if item == nil {
return response.TagResponse{}
}
return response.TagResponse{
ID: item.ID,
ParentID: item.ParentID,
Name: item.Name,
Remark: item.Remark,
SortNo: item.SortNo,
Status: item.Status,
CreatedAt: item.CreatedAt.Format(time.DateTime),
UpdatedAt: item.UpdatedAt.Format(time.DateTime),
}
}
func BuildTagResponses(list []models.Tag) []response.TagResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TagResponse, 0, len(list))
for i := range list {
results = append(results, BuildTagResponse(&list[i]))
}
return results
}
func BuildTagTreeResponses(list []models.Tag) []*response.TagTreeResponse {
if len(list) == 0 {
return nil
}
nodeMap := make(map[int64]*response.TagTreeResponse, len(list))
roots := make([]*response.TagTreeResponse, 0)
for i := range list {
item := &list[i]
nodeMap[item.ID] = &response.TagTreeResponse{
ID: item.ID,
ParentID: item.ParentID,
Name: item.Name,
Remark: item.Remark,
SortNo: item.SortNo,
Status: item.Status,
CreatedAt: item.CreatedAt.Format(time.DateTime),
UpdatedAt: item.UpdatedAt.Format(time.DateTime),
Children: make([]*response.TagTreeResponse, 0),
}
}
for i := range list {
item := &list[i]
node := nodeMap[item.ID]
if item.ParentID == 0 {
roots = append(roots, node)
continue
}
parent, ok := nodeMap[item.ParentID]
if !ok {
roots = append(roots, node)
continue
}
parent.Children = append(parent.Children, node)
}
return roots
}
+567
View File
@@ -0,0 +1,567 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/enums"
"cs-agent/internal/pkg/utils"
"cs-agent/internal/services"
"encoding/json"
"strings"
"github.com/mlogclub/simple/sqls"
)
type TicketBuildContext struct {
TagsByTicketID map[int64][]models.Tag
Priorities map[int64]*models.TicketPriorityConfig
ResolutionCodes map[string]*models.TicketResolutionCode
Users map[int64]*models.User
Teams map[int64]*models.AgentTeam
Customers map[int64]*models.Customer
SLAByTicketID map[int64][]models.TicketSLARecord
WatchedTicketIDs map[int64]struct{}
}
type TicketDetailBuildContext struct {
Users map[int64]*models.User
Teams map[int64]*models.AgentTeam
AgentProfiles map[int64]*models.AgentProfile
RelatedTickets map[int64]*models.Ticket
Customers map[int64]*models.Customer
AIAgents map[int64]*models.AIAgent
}
func BuildTicket(item *models.Ticket) *response.TicketResponse {
return BuildTicketWithContext(item, nil)
}
func BuildTicketWithContext(item *models.Ticket, ctx *TicketBuildContext) *response.TicketResponse {
if item == nil {
return nil
}
ret := &response.TicketResponse{
ID: item.ID,
TicketNo: item.TicketNo,
Title: item.Title,
Description: item.Description,
Source: item.Source,
Channel: item.Channel,
CustomerID: item.CustomerID,
ConversationID: item.ConversationID,
Type: item.Type,
Priority: item.Priority,
Severity: item.Severity,
Status: item.Status,
CurrentTeamID: item.CurrentTeamID,
CurrentAssigneeID: item.CurrentAssigneeID,
PendingReason: item.PendingReason,
CloseReason: item.CloseReason,
ResolutionCode: item.ResolutionCode,
ResolutionSummary: item.ResolutionSummary,
FirstResponseAt: utils.FormatTimePtr(item.FirstResponseAt),
ResolvedAt: utils.FormatTimePtr(item.ResolvedAt),
ClosedAt: utils.FormatTimePtr(item.ClosedAt),
DueAt: utils.FormatTimePtr(item.DueAt),
NextReplyDeadlineAt: utils.FormatTimePtr(item.NextReplyDeadlineAt),
ResolveDeadlineAt: utils.FormatTimePtr(item.ResolveDeadlineAt),
ReopenedCount: item.ReopenedCount,
CreatedAt: utils.FormatTime(item.CreatedAt),
UpdatedAt: utils.FormatTime(item.UpdatedAt),
}
if ctx != nil {
if _, ok := ctx.WatchedTicketIDs[item.ID]; ok {
ret.WatchedByMe = true
}
}
if ctx != nil && ctx.TagsByTicketID != nil {
ret.Tags = BuildTagResponses(ctx.TagsByTicketID[item.ID])
} else {
ret.Tags = BuildTagResponses(services.TicketService.GetTags(item.ID))
}
if item.Priority > 0 && ctx != nil && ctx.Priorities != nil {
if priority := ctx.Priorities[item.Priority]; priority != nil {
ret.PriorityName = priority.Name
}
} else if item.Priority > 0 {
if priority := services.TicketPriorityConfigService.Get(item.Priority); priority != nil {
ret.PriorityName = priority.Name
}
}
if item.ResolutionCode != "" && ctx != nil && ctx.ResolutionCodes != nil {
if code := ctx.ResolutionCodes[item.ResolutionCode]; code != nil {
ret.ResolutionCodeName = code.Name
}
} else if item.ResolutionCode != "" {
if code := services.TicketResolutionCodeService.Take("code = ? AND status <> ?", item.ResolutionCode, enums.StatusDeleted); code != nil {
ret.ResolutionCodeName = code.Name
}
}
if item.CurrentAssigneeID > 0 && ctx != nil && ctx.Users != nil {
if user := ctx.Users[item.CurrentAssigneeID]; user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
} else if item.CurrentAssigneeID > 0 {
if user := services.UserService.Get(item.CurrentAssigneeID); user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
}
if item.CurrentTeamID > 0 && ctx != nil && ctx.Teams != nil {
if team := ctx.Teams[item.CurrentTeamID]; team != nil {
ret.CurrentTeamName = team.Name
}
} else if item.CurrentTeamID > 0 {
if team := services.AgentTeamService.Get(item.CurrentTeamID); team != nil {
ret.CurrentTeamName = team.Name
}
}
if item.CustomerID > 0 && ctx != nil && ctx.Customers != nil {
ret.Customer = BuildCustomer(ctx.Customers[item.CustomerID])
} else if item.CustomerID > 0 {
ret.Customer = BuildCustomer(services.CustomerService.Get(item.CustomerID))
}
if ctx != nil && ctx.SLAByTicketID != nil {
ret.SLA = BuildTicketSLAList(ctx.SLAByTicketID[item.ID])
} else {
ret.SLA = BuildTicketSLAList(
services.TicketSLARecordService.Find(
sqls.NewCnd().Eq("ticket_id", item.ID).Asc("id"),
),
)
}
return ret
}
func BuildTicketList(list []models.Ticket) []response.TicketResponse {
return BuildTicketListWithContext(list, nil)
}
func BuildTicketListWithContext(list []models.Ticket, ctx *TicketBuildContext) []response.TicketResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketResponse, 0, len(list))
for i := range list {
if item := BuildTicketWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketComment(item *models.TicketComment) *response.TicketCommentResponse {
return BuildTicketCommentWithContext(item, nil)
}
func BuildTicketCommentWithContext(item *models.TicketComment, ctx *TicketDetailBuildContext) *response.TicketCommentResponse {
if item == nil {
return nil
}
ret := &response.TicketCommentResponse{
ID: item.ID,
TicketID: item.TicketID,
CommentType: item.CommentType,
AuthorType: item.AuthorType,
AuthorID: item.AuthorID,
ContentType: item.ContentType,
Content: item.Content,
Payload: item.Payload,
CreatedAt: utils.FormatTime(item.CreatedAt),
}
ret.AuthorName = buildTicketOperatorName(item.AuthorType, item.AuthorID, ctx)
return ret
}
func BuildTicketCommentList(list []models.TicketComment) []response.TicketCommentResponse {
return BuildTicketCommentListWithContext(list, nil)
}
func BuildTicketCommentListWithContext(list []models.TicketComment, ctx *TicketDetailBuildContext) []response.TicketCommentResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketCommentResponse, 0, len(list))
for i := range list {
if item := BuildTicketCommentWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketEventLog(item *models.TicketEventLog) *response.TicketEventLogResponse {
return BuildTicketEventLogWithContext(item, nil)
}
func BuildTicketEventLogWithContext(item *models.TicketEventLog, ctx *TicketDetailBuildContext) *response.TicketEventLogResponse {
if item == nil {
return nil
}
return &response.TicketEventLogResponse{
ID: item.ID,
TicketID: item.TicketID,
EventType: item.EventType,
OperatorType: item.OperatorType,
OperatorID: item.OperatorID,
OperatorName: buildTicketOperatorName(item.OperatorType, item.OperatorID, ctx),
OldValue: item.OldValue,
NewValue: item.NewValue,
Content: item.Content,
Payload: item.Payload,
CreatedAt: utils.FormatTime(item.CreatedAt),
}
}
func BuildTicketEventLogList(list []models.TicketEventLog) []response.TicketEventLogResponse {
return BuildTicketEventLogListWithContext(list, nil)
}
func BuildTicketEventLogListWithContext(list []models.TicketEventLog, ctx *TicketDetailBuildContext) []response.TicketEventLogResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketEventLogResponse, 0, len(list))
for i := range list {
if item := BuildTicketEventLogWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketSLAList(list []models.TicketSLARecord) []response.TicketSLAResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketSLAResponse, 0, len(list))
for i := range list {
item := &list[i]
results = append(results, response.TicketSLAResponse{
SLAType: item.SLAType,
TargetMinutes: item.TargetMinutes,
Status: item.Status,
StartedAt: utils.FormatTimePtr(item.StartedAt),
PausedAt: utils.FormatTimePtr(item.PausedAt),
StoppedAt: utils.FormatTimePtr(item.StoppedAt),
BreachedAt: utils.FormatTimePtr(item.BreachedAt),
ElapsedMin: item.ElapsedMin,
})
}
return results
}
func BuildTicketDetail(aggregate *services.TicketDetailAggregate) *response.TicketDetailResponse {
return BuildTicketDetailWithContext(aggregate, nil)
}
func BuildTicketDetailWithContext(aggregate *services.TicketDetailAggregate, ctx *TicketDetailBuildContext) *response.TicketDetailResponse {
if aggregate == nil || aggregate.Ticket == nil {
return nil
}
if ctx == nil {
users := make(map[int64]*models.User, len(aggregate.Users)+len(aggregate.OperatorUsers))
for id, item := range aggregate.Users {
users[id] = item
}
for id, item := range aggregate.OperatorUsers {
users[id] = item
}
ctx = &TicketDetailBuildContext{
Users: users,
Teams: aggregate.Teams,
AgentProfiles: aggregate.AgentProfiles,
RelatedTickets: aggregate.RelatedMap,
Customers: aggregate.OperatorCustomers,
AIAgents: aggregate.OperatorAIAgents,
}
}
ret := &response.TicketDetailResponse{
Ticket: *BuildTicket(aggregate.Ticket),
Watchers: BuildTicketWatcherListWithContext(aggregate.Watchers, ctx),
Collaborators: BuildTicketCollaboratorListWithContext(aggregate.Collaborators, ctx),
RelatedTickets: BuildTicketRelationListWithContext(aggregate.RelatedTickets, ctx),
}
if len(aggregate.Comments) > 0 {
ret.Comments = make([]response.TicketCommentResponse, 0, len(aggregate.Comments))
for i := range aggregate.Comments {
if item := BuildTicketCommentWithContext(&aggregate.Comments[i], ctx); item != nil {
ret.Comments = append(ret.Comments, *item)
}
}
}
if len(aggregate.Events) > 0 {
ret.Events = make([]response.TicketEventLogResponse, 0, len(aggregate.Events))
for i := range aggregate.Events {
if item := BuildTicketEventLogWithContext(&aggregate.Events[i], ctx); item != nil {
ret.Events = append(ret.Events, *item)
}
}
}
if aggregate.Customer != nil {
ret.Ticket.Customer = BuildCustomer(aggregate.Customer)
}
ret.Ticket.Tags = BuildTagResponses(aggregate.Tags)
ret.Ticket.SLA = BuildTicketSLAList(aggregate.SLAs)
for i := range ret.Comments {
if ret.Comments[i].AuthorName == "" {
ret.Comments[i].AuthorName = buildTicketOperatorName(ret.Comments[i].AuthorType, ret.Comments[i].AuthorID, ctx)
}
}
for i := range ret.Events {
if ret.Events[i].OperatorName == "" {
ret.Events[i].OperatorName = buildTicketOperatorName(ret.Events[i].OperatorType, ret.Events[i].OperatorID, ctx)
}
}
return ret
}
func BuildTicketRelation(item *models.TicketRelation) *response.TicketRelationResponse {
return BuildTicketRelationWithContext(item, nil)
}
func BuildTicketRelationWithContext(item *models.TicketRelation, ctx *TicketDetailBuildContext) *response.TicketRelationResponse {
if item == nil {
return nil
}
ret := &response.TicketRelationResponse{
ID: item.ID,
TicketID: item.TicketID,
RelatedTicketID: item.RelatedTicketID,
RelationType: item.RelationType,
}
related := (*models.Ticket)(nil)
if ctx != nil && ctx.RelatedTickets != nil {
related = ctx.RelatedTickets[item.RelatedTicketID]
}
if related == nil {
related = services.TicketService.Get(item.RelatedTicketID)
}
if related != nil {
ret.RelatedTicketNo = related.TicketNo
ret.RelatedTicketTitle = related.Title
ret.RelatedTicketStatus = related.Status
ret.UpdatedAt = utils.FormatTime(related.UpdatedAt)
if related.CurrentTeamID > 0 && ctx != nil && ctx.Teams != nil {
if team := ctx.Teams[related.CurrentTeamID]; team != nil {
ret.CurrentTeamName = team.Name
}
} else if related.CurrentTeamID > 0 {
if team := services.AgentTeamService.Get(related.CurrentTeamID); team != nil {
ret.CurrentTeamName = team.Name
}
}
if related.CurrentAssigneeID > 0 && ctx != nil && ctx.Users != nil {
ret.CurrentAssigneeName = buildTicketUserDisplayName(ctx.Users[related.CurrentAssigneeID])
} else if related.CurrentAssigneeID > 0 {
if user := services.UserService.Get(related.CurrentAssigneeID); user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
}
}
return ret
}
func BuildTicketRelationList(list []models.TicketRelation) []response.TicketRelationResponse {
return BuildTicketRelationListWithContext(list, nil)
}
func BuildTicketRelationListWithContext(list []models.TicketRelation, ctx *TicketDetailBuildContext) []response.TicketRelationResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketRelationResponse, 0, len(list))
for i := range list {
if item := BuildTicketRelationWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketSummary(summary *services.TicketSummaryAggregate) *response.TicketSummaryResponse {
if summary == nil {
return nil
}
return &response.TicketSummaryResponse{
All: summary.All,
Mine: summary.Mine,
Watching: summary.Watching,
Collaboration: summary.Collaboration,
Participating: summary.Participating,
Mentioned: summary.Mentioned,
Unassigned: summary.Unassigned,
PendingCustomer: summary.PendingCustomer,
PendingInternal: summary.PendingInternal,
Overdue: summary.Overdue,
}
}
func BuildTicketRiskOverview(overview *services.TicketRiskOverviewAggregate) *response.TicketRiskOverviewResponse {
if overview == nil {
return nil
}
ret := &response.TicketRiskOverviewResponse{
Overdue: overview.Overdue,
HighRisk: overview.HighRisk,
Unassigned: overview.Unassigned,
PendingInternal: overview.PendingInternal,
PendingCustomer: overview.PendingCustomer,
RiskWindowMins: overview.RiskWindowMins,
}
if len(overview.Reasons) > 0 {
ret.Reasons = make([]response.TicketRiskReasonResponse, 0, len(overview.Reasons))
for _, item := range overview.Reasons {
ret.Reasons = append(ret.Reasons, response.TicketRiskReasonResponse{
Code: item.Code,
Title: item.Title,
Description: item.Description,
Count: item.Count,
})
}
}
return ret
}
func BuildTicketWatcherList(list []models.TicketWatcher) []response.TicketWatcherResponse {
return BuildTicketWatcherListWithContext(list, nil)
}
func BuildTicketWatcherListWithContext(list []models.TicketWatcher, ctx *TicketDetailBuildContext) []response.TicketWatcherResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketWatcherResponse, 0, len(list))
for i := range list {
item := &list[i]
out := response.TicketWatcherResponse{
ID: item.ID,
UserID: item.UserID,
}
if ctx != nil && ctx.Users != nil {
out.UserName = buildTicketUserDisplayName(ctx.Users[item.UserID])
} else if user := services.UserService.Get(item.UserID); user != nil {
out.UserName = buildTicketUserDisplayName(user)
}
results = append(results, out)
}
return results
}
func BuildTicketCollaboratorList(list []models.TicketCollaborator) []response.TicketCollaboratorResponse {
return BuildTicketCollaboratorListWithContext(list, nil)
}
func BuildTicketCollaboratorListWithContext(list []models.TicketCollaborator, ctx *TicketDetailBuildContext) []response.TicketCollaboratorResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketCollaboratorResponse, 0, len(list))
for i := range list {
item := &list[i]
out := response.TicketCollaboratorResponse{
ID: item.ID,
UserID: item.UserID,
}
if ctx != nil && ctx.Users != nil {
out.UserName = buildTicketUserDisplayName(ctx.Users[item.UserID])
} else if user := services.UserService.Get(item.UserID); user != nil {
out.UserName = buildTicketUserDisplayName(user)
}
if ctx != nil && ctx.AgentProfiles != nil {
if profile := ctx.AgentProfiles[item.UserID]; profile != nil && profile.TeamID > 0 {
if team := ctx.Teams[profile.TeamID]; team != nil {
out.TeamName = team.Name
}
}
} else if profile := services.AgentProfileService.GetByUserID(item.UserID); profile != nil && profile.TeamID > 0 {
if team := services.AgentTeamService.Get(profile.TeamID); team != nil {
out.TeamName = team.Name
}
}
results = append(results, out)
}
return results
}
func BuildTicketView(item *models.TicketView) *response.TicketViewResponse {
if item == nil {
return nil
}
ret := &response.TicketViewResponse{
ID: item.ID,
Name: item.Name,
SortNo: item.SortNo,
}
if strings.TrimSpace(item.FiltersJSON) != "" {
_ = json.Unmarshal([]byte(item.FiltersJSON), &ret.Filters)
}
return ret
}
func BuildTicketViewList(list []models.TicketView) []response.TicketViewResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketViewResponse, 0, len(list))
for i := range list {
if item := BuildTicketView(&list[i]); item != nil {
results = append(results, *item)
}
}
return results
}
func buildTicketUserDisplayName(user *models.User) string {
if user == nil {
return ""
}
if user.Nickname != "" {
return user.Nickname
}
return user.Username
}
func buildTicketOperatorName(senderType enums.IMSenderType, senderID int64, ctx *TicketDetailBuildContext) string {
if senderID <= 0 {
return ""
}
switch senderType {
case enums.IMSenderTypeAgent:
if ctx != nil && ctx.Users != nil {
if user := ctx.Users[senderID]; user != nil {
return buildTicketUserDisplayName(user)
}
}
if user := services.UserService.Get(senderID); user != nil {
return buildTicketUserDisplayName(user)
}
case enums.IMSenderTypeCustomer:
if ctx != nil && ctx.Customers != nil {
if customer := ctx.Customers[senderID]; customer != nil {
return customer.Name
}
}
if customer := services.CustomerService.Get(senderID); customer != nil {
return customer.Name
}
case enums.IMSenderTypeAI:
if ctx != nil && ctx.AIAgents != nil {
if ai := ctx.AIAgents[senderID]; ai != nil {
return ai.Name
}
}
if ai := services.AIAgentService.Get(senderID); ai != nil {
return ai.Name
}
}
return ""
}
@@ -0,0 +1,61 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
)
func BuildTicketResolutionCode(item *models.TicketResolutionCode) *response.TicketResolutionCodeResponse {
if item == nil {
return nil
}
return &response.TicketResolutionCodeResponse{
ID: item.ID,
Name: item.Name,
Code: item.Code,
SortNo: item.SortNo,
Status: item.Status,
Remark: item.Remark,
}
}
func BuildTicketResolutionCodeList(list []models.TicketResolutionCode) []response.TicketResolutionCodeResponse {
if len(list) == 0 {
return make([]response.TicketResolutionCodeResponse, 0)
}
ret := make([]response.TicketResolutionCodeResponse, 0, len(list))
for i := range list {
if item := BuildTicketResolutionCode(&list[i]); item != nil {
ret = append(ret, *item)
}
}
return ret
}
func BuildTicketPriorityConfig(item *models.TicketPriorityConfig) *response.TicketPriorityConfigResponse {
if item == nil {
return nil
}
return &response.TicketPriorityConfigResponse{
ID: item.ID,
Name: item.Name,
SortNo: item.SortNo,
FirstResponseMinutes: item.FirstResponseMinutes,
ResolutionMinutes: item.ResolutionMinutes,
Status: item.Status,
Remark: item.Remark,
}
}
func BuildTicketPriorityConfigList(list []models.TicketPriorityConfig) []response.TicketPriorityConfigResponse {
if len(list) == 0 {
return make([]response.TicketPriorityConfigResponse, 0)
}
ret := make([]response.TicketPriorityConfigResponse, 0, len(list))
for i := range list {
if item := BuildTicketPriorityConfig(&list[i]); item != nil {
ret = append(ret, *item)
}
}
return ret
}
+68
View File
@@ -0,0 +1,68 @@
package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/utils"
"cs-agent/internal/services"
)
type UserBuildOptions struct {
Roles bool
Permissions bool
}
func BuildUserList(items []models.User, options UserBuildOptions) []response.UserResponse {
results := make([]response.UserResponse, 0, len(items))
for _, item := range items {
results = append(results, *BuildUserResponse(&item, options))
}
return results
}
func BuildUserResponse(item *models.User, options UserBuildOptions) *response.UserResponse {
if item == nil {
return nil
}
ret := &response.UserResponse{
ID: item.ID,
Username: item.Username,
Nickname: item.Nickname,
Avatar: item.Avatar,
Status: item.Status,
LastLoginAt: utils.FormatTimePtr(item.LastLoginAt),
LastLoginIP: item.LastLoginIP,
}
if item.Mobile != nil {
ret.Mobile = *item.Mobile
}
if item.Email != nil {
ret.Email = *item.Email
}
if options.Roles {
ret.Roles = buildAssignedRoles(item.ID)
}
if options.Permissions {
permissionCodes, _ := services.AuthService.GetUserPermissions(item.ID)
ret.Permissions = permissionCodes
}
return ret
}
func buildAssignedRoles(userID int64) []response.RoleResponse {
roles, _ := services.AuthService.GetUserRoles(userID)
results := make([]response.RoleResponse, 0, len(roles))
for _, role := range roles {
results = append(results, response.RoleResponse{
ID: role.ID,
Name: role.Name,
Code: role.Code,
Status: role.Status,
IsSystem: role.IsSystem,
SortNo: role.SortNo,
})
}
return results
}