conversation_controller.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. package controller
  2. import (
  3. "net/http"
  4. "strconv"
  5. "github.com/2930134478/AI-CS/backend/service"
  6. "github.com/2930134478/AI-CS/backend/utils"
  7. "github.com/gin-gonic/gin"
  8. )
  9. // ConversationController 负责处理会话相关的 HTTP 请求。
  10. type ConversationController struct {
  11. conversationService *service.ConversationService
  12. aiConfigService *service.AIConfigService // 用于获取开放的模型列表
  13. }
  14. // NewConversationController 创建 ConversationController 实例。
  15. func NewConversationController(
  16. conversationService *service.ConversationService,
  17. aiConfigService *service.AIConfigService,
  18. ) *ConversationController {
  19. return &ConversationController{
  20. conversationService: conversationService,
  21. aiConfigService: aiConfigService,
  22. }
  23. }
  24. type initConversationRequest struct {
  25. VisitorID uint `json:"visitor_id"`
  26. Website string `json:"website"`
  27. Referrer string `json:"referrer"`
  28. Browser string `json:"browser"`
  29. OS string `json:"os"`
  30. Language string `json:"language"`
  31. ChatMode string `json:"chat_mode"` // 对话模式:human(人工客服)、ai(AI客服)
  32. AIConfigID *uint `json:"ai_config_id"` // AI 配置 ID(访客选择的模型配置,AI 模式时必需)
  33. }
  34. type updateContactRequest struct {
  35. Email *string `json:"email"`
  36. Phone *string `json:"phone"`
  37. Notes *string `json:"notes"`
  38. }
  39. // InitConversation 为访客初始化或恢复会话。
  40. func (cc *ConversationController) InitConversation(c *gin.Context) {
  41. var req initConversationRequest
  42. if err := c.ShouldBindJSON(&req); err != nil || req.VisitorID == 0 {
  43. c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
  44. return
  45. }
  46. browser := req.Browser
  47. os := req.OS
  48. if browser == "" || os == "" {
  49. parsedBrowser, parsedOS := utils.ParseUserAgent(c.GetHeader("User-Agent"))
  50. if browser == "" {
  51. browser = parsedBrowser
  52. }
  53. if os == "" {
  54. os = parsedOS
  55. }
  56. }
  57. result, err := cc.conversationService.InitConversation(service.InitConversationInput{
  58. VisitorID: req.VisitorID,
  59. Website: req.Website,
  60. Referrer: req.Referrer,
  61. Browser: browser,
  62. OS: os,
  63. Language: req.Language,
  64. IPAddress: utils.GetClientIP(c),
  65. ChatMode: req.ChatMode,
  66. AIConfigID: req.AIConfigID,
  67. })
  68. if err != nil {
  69. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  70. return
  71. }
  72. c.JSON(http.StatusOK, gin.H{
  73. "conversation_id": result.ConversationID,
  74. "status": result.Status,
  75. })
  76. }
  77. // GetPublicAIModels 获取所有开放的模型配置(供访客选择)。
  78. func (cc *ConversationController) GetPublicAIModels(c *gin.Context) {
  79. modelType := c.DefaultQuery("model_type", "text")
  80. models, err := cc.aiConfigService.GetPublicModels(modelType)
  81. if err != nil {
  82. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  83. return
  84. }
  85. c.JSON(http.StatusOK, gin.H{"models": models})
  86. }
  87. // UpdateContactInfo 用于更新访客的联系信息。
  88. func (cc *ConversationController) UpdateContactInfo(c *gin.Context) {
  89. id, err := parseUintParam(c, "id")
  90. if err != nil {
  91. c.JSON(http.StatusBadRequest, gin.H{"error": "会话ID不合法"})
  92. return
  93. }
  94. var req updateContactRequest
  95. if err := c.ShouldBindJSON(&req); err != nil {
  96. c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
  97. return
  98. }
  99. if req.Email == nil && req.Phone == nil && req.Notes == nil {
  100. c.JSON(http.StatusBadRequest, gin.H{"error": "至少提供一个需要更新的字段"})
  101. return
  102. }
  103. result, err := cc.conversationService.UpdateConversationContact(service.UpdateConversationContactInput{
  104. ConversationID: uint(id),
  105. Email: req.Email,
  106. Phone: req.Phone,
  107. Notes: req.Notes,
  108. })
  109. if err != nil {
  110. if err == service.ErrConversationNotFound {
  111. c.JSON(http.StatusNotFound, gin.H{"error": "会话不存在"})
  112. } else {
  113. c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"})
  114. }
  115. return
  116. }
  117. c.JSON(http.StatusOK, gin.H{
  118. "email": result.Email,
  119. "phone": result.Phone,
  120. "notes": result.Notes,
  121. })
  122. }
  123. // ListConversations 返回当前活跃会话的列表。
  124. func (cc *ConversationController) ListConversations(c *gin.Context) {
  125. // 从查询参数获取 user_id(可选)
  126. var userID uint
  127. if userIDStr := c.Query("user_id"); userIDStr != "" {
  128. // 使用 strconv 解析查询参数(不是路径参数)
  129. if parsed, err := strconv.ParseUint(userIDStr, 10, 32); err == nil {
  130. userID = uint(parsed)
  131. }
  132. }
  133. conversations, err := cc.conversationService.ListConversations(userID)
  134. if err != nil {
  135. c.JSON(http.StatusInternalServerError, gin.H{"error": "查询对话列表失败"})
  136. return
  137. }
  138. items := make([]gin.H, 0, len(conversations))
  139. for _, conv := range conversations {
  140. item := gin.H{
  141. "id": conv.ID,
  142. "visitor_id": conv.VisitorID,
  143. "agent_id": conv.AgentID,
  144. "status": conv.Status,
  145. "created_at": formatTimeValue(conv.CreatedAt),
  146. "updated_at": formatTimeValue(conv.UpdatedAt),
  147. "unread_count": conv.UnreadCount,
  148. "has_participated": conv.HasParticipated, // 当前用户是否参与过该会话
  149. }
  150. // 添加 last_seen_at 字段(用于判断在线状态)
  151. if lastSeen := formatTimePointer(conv.LastSeenAt); lastSeen != "" {
  152. item["last_seen_at"] = lastSeen
  153. }
  154. if conv.LastMessage != nil {
  155. item["last_message"] = gin.H{
  156. "id": conv.LastMessage.ID,
  157. "content": conv.LastMessage.Content,
  158. "sender_is_agent": conv.LastMessage.SenderIsAgent,
  159. "message_type": conv.LastMessage.MessageType,
  160. "is_read": conv.LastMessage.IsRead,
  161. "read_at": formatTimePointer(conv.LastMessage.ReadAt),
  162. "created_at": formatTimeValue(conv.LastMessage.CreatedAt),
  163. }
  164. }
  165. items = append(items, item)
  166. }
  167. c.JSON(http.StatusOK, items)
  168. }
  169. // GetConversationDetail 返回会话的详细信息。
  170. func (cc *ConversationController) GetConversationDetail(c *gin.Context) {
  171. id, err := parseUintParam(c, "id")
  172. if err != nil {
  173. c.JSON(http.StatusBadRequest, gin.H{"error": "会话ID不合法"})
  174. return
  175. }
  176. // 从查询参数获取 user_id(可选,用于检查参与状态)
  177. var userID uint
  178. if userIDStr := c.Query("user_id"); userIDStr != "" {
  179. // 使用 strconv 解析查询参数(不是路径参数)
  180. if parsed, err := strconv.ParseUint(userIDStr, 10, 32); err == nil {
  181. userID = uint(parsed)
  182. }
  183. }
  184. detail, err := cc.conversationService.GetConversationDetail(uint(id), userID)
  185. if err != nil {
  186. if err == service.ErrConversationNotFound {
  187. c.JSON(http.StatusNotFound, gin.H{"error": "会话不存在"})
  188. } else {
  189. c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
  190. }
  191. return
  192. }
  193. response := gin.H{
  194. "id": detail.ID,
  195. "visitor_id": detail.VisitorID,
  196. "agent_id": detail.AgentID,
  197. "status": detail.Status,
  198. "website": detail.Website,
  199. "referrer": detail.Referrer,
  200. "browser": detail.Browser,
  201. "os": detail.OS,
  202. "language": detail.Language,
  203. "ip_address": detail.IPAddress,
  204. "location": detail.Location,
  205. "email": detail.Email,
  206. "phone": detail.Phone,
  207. "notes": detail.Notes,
  208. "created_at": formatTimeValue(detail.CreatedAt),
  209. "updated_at": formatTimeValue(detail.UpdatedAt),
  210. "unread_count": detail.UnreadCount,
  211. }
  212. if lastSeen := formatTimePointer(detail.LastSeen); lastSeen != "" {
  213. response["last_seen_at"] = lastSeen
  214. }
  215. if detail.LastMessage != nil {
  216. response["last_message"] = gin.H{
  217. "id": detail.LastMessage.ID,
  218. "content": detail.LastMessage.Content,
  219. "sender_is_agent": detail.LastMessage.SenderIsAgent,
  220. "message_type": detail.LastMessage.MessageType,
  221. "is_read": detail.LastMessage.IsRead,
  222. "read_at": formatTimePointer(detail.LastMessage.ReadAt),
  223. "created_at": formatTimeValue(detail.LastMessage.CreatedAt),
  224. }
  225. }
  226. c.JSON(http.StatusOK, response)
  227. }
  228. // SearchConversations 根据关键字进行会话的模糊搜索。
  229. func (cc *ConversationController) SearchConversations(c *gin.Context) {
  230. query := c.Query("q")
  231. if query == "" {
  232. c.JSON(http.StatusBadRequest, gin.H{"error": "搜索关键词不能为空"})
  233. return
  234. }
  235. // 从查询参数获取 user_id(可选,用于检查参与状态)
  236. var userID uint
  237. if userIDStr := c.Query("user_id"); userIDStr != "" {
  238. // 使用 strconv 解析查询参数(不是路径参数)
  239. if parsed, err := strconv.ParseUint(userIDStr, 10, 32); err == nil {
  240. userID = uint(parsed)
  241. }
  242. }
  243. conversations, err := cc.conversationService.SearchConversations(query, userID)
  244. if err != nil {
  245. c.JSON(http.StatusInternalServerError, gin.H{"error": "搜索失败"})
  246. return
  247. }
  248. items := make([]gin.H, 0, len(conversations))
  249. for _, conv := range conversations {
  250. item := gin.H{
  251. "id": conv.ID,
  252. "visitor_id": conv.VisitorID,
  253. "agent_id": conv.AgentID,
  254. "status": conv.Status,
  255. "created_at": formatTimeValue(conv.CreatedAt),
  256. "updated_at": formatTimeValue(conv.UpdatedAt),
  257. "unread_count": conv.UnreadCount,
  258. "has_participated": conv.HasParticipated, // 当前用户是否参与过该会话
  259. }
  260. // 添加 last_seen_at 字段(用于判断在线状态)
  261. if lastSeen := formatTimePointer(conv.LastSeenAt); lastSeen != "" {
  262. item["last_seen_at"] = lastSeen
  263. }
  264. if conv.LastMessage != nil {
  265. item["last_message"] = gin.H{
  266. "id": conv.LastMessage.ID,
  267. "content": conv.LastMessage.Content,
  268. "sender_is_agent": conv.LastMessage.SenderIsAgent,
  269. "message_type": conv.LastMessage.MessageType,
  270. "is_read": conv.LastMessage.IsRead,
  271. "read_at": formatTimePointer(conv.LastMessage.ReadAt),
  272. "created_at": formatTimeValue(conv.LastMessage.CreatedAt),
  273. }
  274. }
  275. items = append(items, item)
  276. }
  277. c.JSON(http.StatusOK, items)
  278. }