refactor: remove Subject field from Conversation model and related logic, replace with CustomerName
This commit is contained in:
@@ -84,9 +84,6 @@ func buildAnalyzeConversationResult(conversation models.Conversation, messages [
|
||||
|
||||
func buildConversationSummary(conversation models.Conversation, messages []models.Message, input AnalyzeConversationInput) string {
|
||||
parts := make([]string, 0, 4)
|
||||
if strings.TrimSpace(conversation.Subject) != "" {
|
||||
parts = append(parts, "会话主题:"+strings.TrimSpace(conversation.Subject))
|
||||
}
|
||||
if input.ObservedIssue != "" {
|
||||
parts = append(parts, "当前问题:"+input.ObservedIssue)
|
||||
} else if strings.TrimSpace(conversation.LastMessageSummary) != "" {
|
||||
@@ -103,7 +100,6 @@ func buildConversationSummary(conversation models.Conversation, messages []model
|
||||
|
||||
func buildConversationCorpus(conversation models.Conversation, messages []models.Message, input AnalyzeConversationInput) string {
|
||||
parts := make([]string, 0, len(messages)+4)
|
||||
parts = append(parts, strings.TrimSpace(conversation.Subject))
|
||||
parts = append(parts, strings.TrimSpace(conversation.LastMessageSummary))
|
||||
parts = append(parts, input.Goal, input.ObservedIssue, input.AdditionalContext)
|
||||
for i := range messages {
|
||||
@@ -200,9 +196,6 @@ func recommendQuestions(intent string, signals []string, input AnalyzeConversati
|
||||
|
||||
func buildConversationAnalysisFacts(conversation models.Conversation, messages []models.Message) []string {
|
||||
facts := make([]string, 0, 4)
|
||||
if strings.TrimSpace(conversation.Subject) != "" {
|
||||
facts = append(facts, "会话主题:"+strings.TrimSpace(conversation.Subject))
|
||||
}
|
||||
if strings.TrimSpace(conversation.LastMessageSummary) != "" {
|
||||
facts = append(facts, "最近摘要:"+strings.TrimSpace(conversation.LastMessageSummary))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
func TestBuildAnalyzeConversationResult_RecommendsHandoffForComplaint(t *testing.T) {
|
||||
conversation := models.Conversation{
|
||||
Subject: "用户投诉扣费异常",
|
||||
LastMessageSummary: "用户反馈被重复扣费,并要求人工处理",
|
||||
}
|
||||
messages := []models.Message{
|
||||
@@ -33,7 +32,6 @@ func TestBuildAnalyzeConversationResult_RecommendsHandoffForComplaint(t *testing
|
||||
|
||||
func TestBuildAnalyzeConversationResult_RecommendsPrepareTicket(t *testing.T) {
|
||||
conversation := models.Conversation{
|
||||
Subject: "订单无法支付",
|
||||
LastMessageSummary: "用户要求登记问题并尽快处理",
|
||||
}
|
||||
messages := []models.Message{
|
||||
|
||||
@@ -114,7 +114,7 @@ func (g *CreateTicketGraph) buildCreateRequest(argumentsInJSON string) (request.
|
||||
req.Priority = args.Priority
|
||||
req.Severity = args.Severity
|
||||
if req.Title == "" {
|
||||
req.Title = strings.TrimSpace(g.conversation.Subject)
|
||||
req.Title = strings.TrimSpace(g.conversation.LastMessageSummary)
|
||||
}
|
||||
if req.Description == "" {
|
||||
req.Description = strings.TrimSpace(g.conversation.LastMessageSummary)
|
||||
|
||||
@@ -100,8 +100,6 @@ func buildDraftTitle(conversation models.Conversation, input PrepareTicketDraftI
|
||||
return limitText(input.Title, 80)
|
||||
case input.Issue != "":
|
||||
return limitText(input.Issue, 80)
|
||||
case strings.TrimSpace(conversation.Subject) != "":
|
||||
return limitText(conversation.Subject, 80)
|
||||
case strings.TrimSpace(conversation.LastMessageSummary) != "":
|
||||
return limitText(conversation.LastMessageSummary, 80)
|
||||
default:
|
||||
@@ -144,9 +142,6 @@ func hasSufficientIssueContext(input PrepareTicketDraftInput, description string
|
||||
|
||||
func buildConversationFacts(conversation models.Conversation, messages []models.Message) []string {
|
||||
facts := make([]string, 0, 4)
|
||||
if strings.TrimSpace(conversation.Subject) != "" {
|
||||
facts = append(facts, "会话主题:"+strings.TrimSpace(conversation.Subject))
|
||||
}
|
||||
if strings.TrimSpace(conversation.LastMessageSummary) != "" {
|
||||
facts = append(facts, "最近摘要:"+strings.TrimSpace(conversation.LastMessageSummary))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
func TestBuildPrepareTicketDraftResult_UsesConversationFallbacks(t *testing.T) {
|
||||
conversation := models.Conversation{
|
||||
Subject: "企业微信登录异常",
|
||||
LastMessageSummary: "用户反馈企业微信扫码后页面空白,无法进入工作台",
|
||||
}
|
||||
messages := []models.Message{
|
||||
@@ -38,7 +37,6 @@ func TestBuildPrepareTicketDraftResult_UsesConversationFallbacks(t *testing.T) {
|
||||
|
||||
func TestBuildPrepareTicketDraftResult_ReadyWithExplicitIssue(t *testing.T) {
|
||||
conversation := models.Conversation{
|
||||
Subject: "订单支付失败",
|
||||
LastMessageSummary: "用户反馈连续支付失败",
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
func TestTriageServiceRequestResult_PrepareTicket(t *testing.T) {
|
||||
conversation := models.Conversation{
|
||||
Subject: "支付失败需要登记工单",
|
||||
LastMessageSummary: "用户要求建单跟进支付失败问题",
|
||||
}
|
||||
messages := []models.Message{
|
||||
@@ -31,7 +30,6 @@ func TestTriageServiceRequestResult_PrepareTicket(t *testing.T) {
|
||||
|
||||
func TestTriageServiceRequestResult_Handoff(t *testing.T) {
|
||||
conversation := models.Conversation{
|
||||
Subject: "投诉转人工",
|
||||
LastMessageSummary: "用户要求人工处理扣费投诉",
|
||||
}
|
||||
messages := []models.Message{
|
||||
|
||||
@@ -19,7 +19,7 @@ func BuildConversation(item *models.Conversation) response.ConversationResponse
|
||||
AIAgentID: item.AIAgentID,
|
||||
ChannelID: item.ChannelID,
|
||||
CustomerID: item.CustomerID,
|
||||
Subject: item.Subject,
|
||||
CustomerName: buildConversationCustomerName(item.CustomerID),
|
||||
Status: item.Status,
|
||||
ServiceMode: item.ServiceMode,
|
||||
Priority: item.Priority,
|
||||
@@ -62,6 +62,16 @@ func BuildConversation(item *models.Conversation) response.ConversationResponse
|
||||
return ret
|
||||
}
|
||||
|
||||
func buildConversationCustomerName(customerID int64) string {
|
||||
if customerID <= 0 {
|
||||
return ""
|
||||
}
|
||||
if customer := services.CustomerService.Get(customerID); customer != nil {
|
||||
return strings.TrimSpace(customer.Name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func BuildParticipantResponses(conversationID int64) []response.ConversationParticipantResponse {
|
||||
list := services.ConversationParticipantService.Find(sqls.NewCnd().Eq("conversation_id", conversationID).Asc("id"))
|
||||
if len(list) == 0 {
|
||||
|
||||
@@ -36,7 +36,7 @@ func (c *ConversationController) AnyList() *web.JsonResult {
|
||||
|
||||
if keyword, _ := params.Get(c.Ctx, "keyword"); strs.IsNotBlank(keyword) {
|
||||
keywordLike := "%" + strings.TrimSpace(keyword) + "%"
|
||||
cnd.Where("subject LIKE ? OR last_message_summary LIKE ?", keywordLike, keywordLike)
|
||||
cnd.Where("last_message_summary LIKE ? OR customer_id IN (SELECT id FROM t_customer WHERE name LIKE ?)", keywordLike, keywordLike)
|
||||
}
|
||||
|
||||
// 标签搜索
|
||||
|
||||
@@ -327,7 +327,6 @@ type Conversation struct {
|
||||
AIAgentID int64 `gorm:"type:bigint;not null;default:0;index"` // AIAgentID 为当前会话绑定的 AI Agent ID。
|
||||
ChannelID int64 `gorm:"type:bigint;not null;default:0;index"` // ChannelID 为该会话来源接入渠道ID。
|
||||
CustomerID int64 `gorm:"type:bigint;not null;default:0;index"` // CustomerID 为会话所属客户 ID。
|
||||
Subject string `gorm:"type:varchar(255);not null;default:''"` // Subject 为会话标题或摘要。
|
||||
Status enums.IMConversationStatus `gorm:"type:int;not null;default:1;index"` // Status 为会话状态,如待接入、处理中、已关闭。
|
||||
ServiceMode enums.IMConversationServiceMode `gorm:"type:int;not null;default:3;index"` // ServiceMode 为服务模式,如仅AI、仅人工、AI优先人工接管。
|
||||
Priority int `gorm:"type:int;not null;default:0;index"` // Priority 为会话优先级。
|
||||
|
||||
@@ -22,7 +22,7 @@ type ConversationResponse struct {
|
||||
AIAgentID int64 `json:"aiAgentId"`
|
||||
ChannelID int64 `json:"channelId"`
|
||||
CustomerID int64 `json:"customerId"`
|
||||
Subject string `json:"subject"`
|
||||
CustomerName string `json:"customerName"`
|
||||
Status enums.IMConversationStatus `json:"status"`
|
||||
ServiceMode enums.IMConversationServiceMode `json:"serviceMode"`
|
||||
Priority int `json:"priority"`
|
||||
|
||||
@@ -2,10 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"cs-agent/internal/events"
|
||||
@@ -61,7 +58,8 @@ func (s *conversationService) ListConversations(userID int64, filter request.Age
|
||||
|
||||
if strs.IsNotBlank(keyword) {
|
||||
keyword = strings.TrimSpace(keyword)
|
||||
cnd.Where("subject LIKE ? OR last_message_summary LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||||
keywordLike := "%" + keyword + "%"
|
||||
cnd.Where("last_message_summary LIKE ? OR customer_id IN (SELECT id FROM t_customer WHERE name LIKE ?)", keywordLike, keywordLike)
|
||||
}
|
||||
|
||||
switch filter {
|
||||
@@ -103,8 +101,6 @@ func (s *conversationService) getLatestNotFinishedByCustomerID(db *gorm.DB, cust
|
||||
}
|
||||
|
||||
func (s *conversationService) Create(externalInfo openidentity.ExternalInfo, channelID, aiAgentID int64) (*models.Conversation, error) {
|
||||
subject := s.buildDefaultSubject(externalInfo)
|
||||
|
||||
aiAgent := AIAgentService.Get(aiAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("AI Agent not found")
|
||||
@@ -127,7 +123,6 @@ func (s *conversationService) Create(externalInfo openidentity.ExternalInfo, cha
|
||||
AIAgentID: aiAgentID,
|
||||
ChannelID: channelID,
|
||||
CustomerID: customerID,
|
||||
Subject: subject,
|
||||
Status: s.resolveInitialStatus(aiAgent.ServiceMode),
|
||||
ServiceMode: aiAgent.ServiceMode,
|
||||
Priority: 0,
|
||||
@@ -667,7 +662,10 @@ func (s *conversationService) BuildConversationSummary(conversation *models.Conv
|
||||
if strings.TrimSpace(conversation.LastMessageSummary) != "" {
|
||||
return conversation.LastMessageSummary
|
||||
}
|
||||
return strings.TrimSpace(conversation.Subject)
|
||||
if customer := CustomerService.Get(conversation.CustomerID); customer != nil {
|
||||
return strings.TrimSpace(customer.Name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *conversationService) canCloseConversation(conversation *models.Conversation, operator *dto.AuthPrincipal) bool {
|
||||
@@ -806,22 +804,6 @@ func (s *conversationService) canLinkConversationCustomer(conv *models.Conversat
|
||||
}
|
||||
}
|
||||
|
||||
func (s *conversationService) buildDefaultSubject(externalInfo openidentity.ExternalInfo) string {
|
||||
if strs.IsNotBlank(externalInfo.ExternalName) {
|
||||
return externalInfo.ExternalName
|
||||
}
|
||||
return fmt.Sprintf("访客%s", hashUUID(externalInfo.ExternalID))
|
||||
}
|
||||
|
||||
func hashUUID(uuid string) string {
|
||||
if uuid == "" {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
h := md5.Sum([]byte(uuid))
|
||||
return hex.EncodeToString(h[:])[:8]
|
||||
}
|
||||
|
||||
func (s *conversationService) resolveInitialStatus(serviceMode enums.IMConversationServiceMode) enums.IMConversationStatus {
|
||||
switch serviceMode {
|
||||
case enums.IMConversationServiceModeHumanOnly:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"log/slog"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
@@ -154,6 +156,15 @@ func buildExternalCustomerName(externalInfo openidentity.ExternalInfo) string {
|
||||
return "访客" + hashUUID(externalInfo.ExternalID)
|
||||
}
|
||||
|
||||
func hashUUID(uuid string) string {
|
||||
if uuid == "" {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
h := md5.Sum([]byte(uuid))
|
||||
return hex.EncodeToString(h[:])[:8]
|
||||
}
|
||||
|
||||
func (s *customerService) CreateCustomer(req request.CreateCustomerRequest, operator *dto.AuthPrincipal) (*models.Customer, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
|
||||
@@ -55,7 +55,7 @@ func buildConversationAssignedNotifyBody(conversation *models.Conversation, assi
|
||||
}
|
||||
lines := []string{
|
||||
fmt.Sprintf("会话ID: #%d", conversation.ID),
|
||||
fmt.Sprintf("会话主题: %s", strs.DefaultIfBlank(conversation.Subject, "-")),
|
||||
fmt.Sprintf("会话摘要: %s", strs.DefaultIfBlank(services.ConversationService.BuildConversationSummary(conversation), "-")),
|
||||
fmt.Sprintf("接入渠道: %s", resolveConversationChannelLabel(conversation)),
|
||||
fmt.Sprintf("当前状态: %s", enums.GetIMConversationStatusLabel(conversation.Status)),
|
||||
fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(assigneeID)),
|
||||
|
||||
@@ -852,7 +852,7 @@ func (s *ticketService) CreateFromConversation(req request.CreateTicketFromConve
|
||||
}
|
||||
title := strings.TrimSpace(req.Title)
|
||||
if title == "" {
|
||||
title = strings.TrimSpace(conversation.Subject)
|
||||
title = ConversationService.BuildConversationSummary(conversation)
|
||||
}
|
||||
description := strings.TrimSpace(req.Description)
|
||||
if description == "" {
|
||||
|
||||
@@ -249,7 +249,7 @@ export function ConversationDetailDialog({
|
||||
onOpenChange={onOpenChange}
|
||||
title={
|
||||
<div className="flex items-center gap-3">
|
||||
<span>{currentConversation?.subject || "会话详情"}</span>
|
||||
<span>{currentConversation?.customerName || "会话详情"}</span>
|
||||
|
||||
<div>
|
||||
{statusMeta ? (
|
||||
|
||||
@@ -597,7 +597,7 @@ export default function DashboardConversationsPage() {
|
||||
setActionLoadingId(item.id)
|
||||
try {
|
||||
await markConversationRead(item.id)
|
||||
toast.success(`已标记已读:${item.subject || `#${item.id}`}`)
|
||||
toast.success(`已标记已读:${item.customerName || `#${item.id}`}`)
|
||||
await loadConversations()
|
||||
if (detailItem?.id === item.id) {
|
||||
await refreshDetail()
|
||||
@@ -613,7 +613,7 @@ export default function DashboardConversationsPage() {
|
||||
setActionLoadingId(item.id)
|
||||
try {
|
||||
await dispatchConversation(item.id)
|
||||
toast.success(`已触发自动分配:${item.subject || `#${item.id}`}`)
|
||||
toast.success(`已触发自动分配:${item.customerName || `#${item.id}`}`)
|
||||
await loadConversations()
|
||||
if (detailItem?.id === item.id) {
|
||||
await refreshDetail()
|
||||
@@ -724,7 +724,7 @@ export default function DashboardConversationsPage() {
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="max-w-60">
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium">{item.subject || `会话 #${item.id}`}</div>
|
||||
<div className="font-medium">{item.customerName || `客户 #${item.customerId || item.id}`}</div>
|
||||
<div className="mt-1 text-sm text-muted-foreground">
|
||||
渠道ID:{item.channelId || "-"}
|
||||
</div>
|
||||
@@ -755,7 +755,7 @@ export default function DashboardConversationsPage() {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant="outline" size="icon-sm" />}
|
||||
aria-label={`更多操作 ${item.subject || item.id}`}
|
||||
aria-label={`更多操作 ${item.customerName || item.id}`}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -480,7 +480,7 @@ export function ChatPanel() {
|
||||
<DialogTitle>确认认领会话</DialogTitle>
|
||||
<DialogDescription>
|
||||
{conversation
|
||||
? `确认认领“${conversation.subject}”吗?认领后会话会进入我的列表。`
|
||||
? `确认认领“${conversation.customerName || `客户 #${conversation.customerId || conversation.id}`}”吗?认领后会话会进入我的列表。`
|
||||
: "确认认领当前会话吗?"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function ConversationList({ onAfterSelect }: ConversationListProps) {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="min-w-0 flex-1 truncate font-medium text-sm leading-4">
|
||||
{conversation.subject}
|
||||
{conversation.customerName || `客户 #${conversation.customerId || conversation.id}`}
|
||||
</span>
|
||||
{conversation.agentUnreadCount > 0 ? (
|
||||
<div className="flex size-4.5 shrink-0 items-center justify-center rounded-full bg-primary text-[10px] text-primary-foreground">
|
||||
|
||||
@@ -272,7 +272,7 @@ export default function ConversationsPage() {
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="min-w-0 truncate font-medium leading-tight">
|
||||
{conversation.subject}
|
||||
{conversation.customerName || `客户 #${conversation.customerId || conversation.id}`}
|
||||
</p>
|
||||
<span
|
||||
className={`inline-flex shrink-0 items-center gap-1 rounded-full border px-2 text-[11px] ${
|
||||
@@ -474,7 +474,7 @@ export default function ConversationsPage() {
|
||||
conversation
|
||||
? {
|
||||
id: conversation.id,
|
||||
subject: conversation.subject,
|
||||
customerName: conversation.customerName,
|
||||
customerId: conversation.customerId ?? 0,
|
||||
lastMessageSummary: conversation.lastMessageSummary,
|
||||
currentAssigneeId: conversation.currentAssigneeId,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { EditDialog } from "./edit"
|
||||
|
||||
type ConversationSeed = {
|
||||
id: number
|
||||
subject: string
|
||||
customerName: string
|
||||
customerId?: number
|
||||
lastMessageSummary?: string
|
||||
currentAssigneeId?: number
|
||||
@@ -28,7 +28,7 @@ export function CreateTicketFromConversationDialog({
|
||||
}: CreateTicketFromConversationDialogProps) {
|
||||
const initialValues = conversation
|
||||
? {
|
||||
title: conversation.subject || "",
|
||||
title: conversation.customerName || "",
|
||||
description: conversation.lastMessageSummary || "",
|
||||
priority: 2,
|
||||
severity: 1,
|
||||
|
||||
@@ -880,7 +880,7 @@ export default function TicketDetailPage() {
|
||||
{sourceConversation ? (
|
||||
<SurfacePanel className="p-2.5">
|
||||
<div className="text-sm font-medium">
|
||||
{sourceConversation.subject || "未命名会话"}
|
||||
{sourceConversation.customerName || "未命名客户"}
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-2 text-xs text-muted-foreground">
|
||||
<span>
|
||||
|
||||
@@ -109,7 +109,8 @@ export type ConversationParticipant = {
|
||||
export type AdminConversation = {
|
||||
id: number
|
||||
channelId: number
|
||||
subject: string
|
||||
customerId: number
|
||||
customerName: string
|
||||
status: number
|
||||
serviceMode: number
|
||||
priority: number
|
||||
|
||||
@@ -37,7 +37,7 @@ export type AgentConversation = {
|
||||
aiAgentId?: number
|
||||
channelId?: number
|
||||
customerId?: number
|
||||
subject: string
|
||||
customerName: string
|
||||
status: number
|
||||
serviceMode: number
|
||||
priority: number
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export type ImConversationParticipant = {
|
||||
export type ImConversation = {
|
||||
id: number
|
||||
channelId: number
|
||||
subject: string
|
||||
customerName: string
|
||||
status: number
|
||||
serviceMode: number
|
||||
priority: number
|
||||
|
||||
Reference in New Issue
Block a user