fix(widget): 完善自动回复与新会话进线提醒
CI / Backend (go vet + test) (push) Failing after 40m21s
CI / Frontend (typecheck + lint + build) (push) Successful in 1h21m3s

This commit is contained in:
yml2213
2026-11-03 19:29:32 +08:00
parent 0eb16c1ce2
commit 24fac1bcf1
15 changed files with 275 additions and 115 deletions
@@ -51,6 +51,15 @@ type WelcomeSegment struct {
Content string `json:"content"`
}
// WidgetReplyConfig 是访客端展示自动回复所需的租户配置。
type WidgetReplyConfig struct {
WelcomeMessage string
WelcomeMessages []WelcomeSegment
OfflinePrompt string
AgentNickname string
DisplayName string
}
var weekdayKeys = []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}
func defaultWelcomeSegments() []WelcomeSegment {
@@ -112,6 +121,37 @@ func ResolveWelcomeSegments(setting *model.TenantSetting) []WelcomeSegment {
return defaultWelcomeSegments()
}
// ResolveWidgetReplyConfig 将租户设置转换为访客端可直接使用的自动回复配置。
func ResolveWidgetReplyConfig(setting *model.TenantSetting, withinWorkHours bool) WidgetReplyConfig {
config := WidgetReplyConfig{
WelcomeMessage: DefaultWelcomeMessage,
WelcomeMessages: defaultWelcomeSegments(),
OfflinePrompt: DefaultOfflinePrompt,
AgentNickname: DefaultAgentNickname,
DisplayName: "在线客服",
}
if setting != nil {
config.WelcomeMessages = ResolveWelcomeSegments(setting)
config.WelcomeMessage = FirstWelcomeText(config.WelcomeMessages)
if setting.OfflinePrompt != "" {
config.OfflinePrompt = setting.OfflinePrompt
}
if setting.AgentNickname != "" {
config.AgentNickname = setting.AgentNickname
}
if setting.DisplayName != "" {
config.DisplayName = setting.DisplayName
}
}
if !withinWorkHours {
config.OfflinePrompt = DefaultWorktimePrompt
if setting != nil && setting.WorktimePrompt != "" {
config.OfflinePrompt = setting.WorktimePrompt
}
}
return config
}
// NormalizeWelcomeSegments 校验并规范化欢迎语段落;返回规范化列表与兼容用首段文本。
func NormalizeWelcomeSegments(input []WelcomeSegment) ([]WelcomeSegment, string, error) {
if len(input) == 0 {
@@ -343,6 +383,10 @@ type SettingsUpdateReq struct {
// TouchesAssignRule / TouchesAutoReply / TouchesWorktime / TouchesNotification
// 供 handler 按字段分区校验权限。
func (r *SettingsUpdateReq) TouchesBasic() bool {
return r.DisplayName != nil || r.AgentNickname != nil || r.Timezone != nil
}
func (r *SettingsUpdateReq) TouchesAssignRule() bool {
return r.AssignStrategy != nil || r.MaxActivePerAgent != nil
}
+30 -32
View File
@@ -152,31 +152,14 @@ func WidgetInit(in WidgetInitInput) (map[string]interface{}, error) {
if payload, err := ws.NewEvent("session_created", session.ID, session); err == nil {
ws.DefaultHub.BroadcastToTenantStaff(session.TenantID, payload)
}
if setting == nil || setting.NotifyNewSession {
notice := map[string]string{"visitor_name": customer.Name}
if payload, err := ws.NewEvent("session_created_notify", session.ID, notice); err == nil {
ws.DefaultHub.BroadcastToTenantStaff(session.TenantID, payload)
}
}
welcomeSegs := ResolveWelcomeSegments(setting)
welcome := FirstWelcomeText(welcomeSegs)
offlinePrompt := DefaultOfflinePrompt
agentNickname := DefaultAgentNickname
displayName := "在线客服"
worktimePrompt := DefaultWorktimePrompt
if setting != nil {
if setting.OfflinePrompt != "" {
offlinePrompt = setting.OfflinePrompt
}
if setting.AgentNickname != "" {
agentNickname = setting.AgentNickname
}
if setting.DisplayName != "" {
displayName = setting.DisplayName
}
if setting.WorktimePrompt != "" {
worktimePrompt = setting.WorktimePrompt
}
}
// 非工作时间优先展示非工作时间提示
if !withinWorkHours {
offlinePrompt = worktimePrompt
}
replyConfig := ResolveWidgetReplyConfig(setting, withinWorkHours)
resp := map[string]interface{}{
"session_id": session.ID,
@@ -188,19 +171,19 @@ func WidgetInit(in WidgetInitInput) (map[string]interface{}, error) {
"online_agent_count": onlineCount,
"within_work_hours": withinWorkHours,
"session_status": session.Status,
"welcome_message": welcome,
"welcome_messages": welcomeSegs,
"display_name": displayName,
"agent_nickname": agentNickname,
"welcome_message": replyConfig.WelcomeMessage,
"welcome_messages": replyConfig.WelcomeMessages,
"display_name": replyConfig.DisplayName,
"agent_nickname": replyConfig.AgentNickname,
}
if !canServeNow {
resp["offline_prompt"] = offlinePrompt
resp["offline_prompt"] = replyConfig.OfflinePrompt
}
if assignedAgent != nil {
resp["agent_id"] = assignedAgent.ID
resp["agent_name"] = assignedAgent.Nickname
} else if canServeNow {
resp["agent_name"] = agentNickname
resp["agent_name"] = replyConfig.AgentNickname
}
resp["landing_url"] = session.LandingURL
resp["current_url"] = session.CurrentURL
@@ -501,12 +484,27 @@ func WidgetMessages(session *model.Session, afterSeq, limit int) (map[string]int
if err != nil {
return nil, err
}
return map[string]interface{}{
setting, _ := GetOrCreateTenantSettings(session.TenantID)
withinWorkHours := IsWithinWorkHours(setting, time.Now())
onlineCount, _ := CountOnlineAgents(session.TenantID)
canServeNow := session.Status == "active" || (onlineCount > 0 && withinWorkHours)
replyConfig := ResolveWidgetReplyConfig(setting, withinWorkHours)
result := map[string]interface{}{
"messages": page.Messages,
"after_seq": page.AfterSeq,
"max_seq": page.MaxSeq,
"has_more": page.HasMore,
"session_status": session.Status,
"satisfaction_score": session.SatisfactionScore,
}, nil
"welcome_message": replyConfig.WelcomeMessage,
"welcome_messages": replyConfig.WelcomeMessages,
"display_name": replyConfig.DisplayName,
"agent_nickname": replyConfig.AgentNickname,
"agents_online": canServeNow,
}
if !canServeNow {
result["offline_prompt"] = replyConfig.OfflinePrompt
}
return result, nil
}