submit_api_chat_logic.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package chatrecords
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "encoding/json"
  7. "fmt"
  8. "github.com/gofrs/uuid/v5"
  9. "io"
  10. "net/http"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "wechat-api/ent/employee"
  15. "wechat-api/ent/wxcard"
  16. "wechat-api/ent/wxcarduser"
  17. "wechat-api/hook/dify"
  18. "wechat-api/hook/fastgpt"
  19. "wechat-api/internal/utils/jwt"
  20. "wechat-api/internal/svc"
  21. "wechat-api/internal/types"
  22. "github.com/zeromicro/go-zero/core/logx"
  23. )
  24. type ChatMessage struct {
  25. Id string `json:"id"`
  26. MessageId string `json:"message_id"`
  27. SessionId uint64 `json:"session_id"`
  28. ConversationId string `json:"conversation_id,optional"`
  29. Answer string `json:"answer"`
  30. Finish bool `json:"finish"`
  31. }
  32. type SubmitApiChatLogic struct {
  33. logx.Logger
  34. ctx context.Context
  35. svcCtx *svc.ServiceContext
  36. }
  37. func NewSubmitApiChatLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SubmitApiChatLogic {
  38. return &SubmitApiChatLogic{
  39. Logger: logx.WithContext(ctx),
  40. ctx: ctx,
  41. svcCtx: svcCtx}
  42. }
  43. func (l *SubmitApiChatLogic) SubmitApiChat(req *types.ChatRecordsInfo, w http.ResponseWriter) {
  44. userId := l.ctx.Value("userId").(uint64)
  45. userInfo, err := l.svcCtx.DB.WxCardUser.Query().Where(wxcarduser.ID(userId)).Only(l.ctx)
  46. if err != nil {
  47. return
  48. }
  49. // session_id 字段确保必须有
  50. var sessionId uint64
  51. if req.BotId == nil || *req.BotId == 0 {
  52. return
  53. }
  54. if req.Content == nil || *req.Content == "" {
  55. return
  56. }
  57. if req.SessionId != nil && *req.SessionId > 0 {
  58. sessionId = *req.SessionId
  59. } else {
  60. newSession, err := l.svcCtx.DB.ChatSession.Create().
  61. SetName(*req.Content).
  62. SetUserID(userId).
  63. SetBotID(*req.BotId).
  64. SetBotType(*req.BotType).
  65. Save(l.ctx)
  66. if err != nil {
  67. return
  68. }
  69. sessionId = newSession.ID
  70. }
  71. // 记录下问题
  72. _, err = l.svcCtx.DB.ChatRecords.Create().
  73. SetUserID(userId).
  74. SetSessionID(sessionId).
  75. SetBotType(*req.BotType).
  76. SetBotID(*req.BotId).
  77. SetContentType(1).
  78. SetContent(*req.Content).
  79. Save(l.ctx)
  80. if err != nil {
  81. return
  82. }
  83. if *req.BotType == 2 { // 从FastGPT里获取回答
  84. card, err := l.svcCtx.DB.WxCard.Query().Where(wxcard.ID(*req.BotId)).Only(l.ctx)
  85. if err != nil {
  86. return
  87. }
  88. fastgptSendChat(l, w, *req.Content, card.APIBase, card.APIKey, sessionId, userId, *req.BotId, *req.BotType)
  89. } else if *req.BotType == 3 { // 从数字员工里获取回答
  90. employeeRow, err := l.svcCtx.DB.Employee.Query().Where(employee.ID(*req.BotId)).Only(l.ctx)
  91. if err != nil {
  92. return
  93. }
  94. //TODO 判断用户是否能与智能体聊天(智能体VIP, 要求用户也必须是VIP)
  95. if employeeRow.IsVip > 0 && userInfo.IsVip == 0 {
  96. return
  97. }
  98. difySendChat(l, w, *req.Content, employeeRow.APIBase, employeeRow.APIKey, sessionId, userId, *req.BotId, *req.BotType)
  99. }
  100. }
  101. // fastgptSendChat 往 FastGPT 发送内容并获得响应信息
  102. func fastgptSendChat(l *SubmitApiChatLogic, w http.ResponseWriter, content, apiBase, apiKey string, sessionId, userId, botId uint64, botType uint8) {
  103. userInfo, _ := l.svcCtx.DB.WxCardUser.Query().Where(wxcarduser.ID(userId)).First(l.ctx)
  104. var chatReq fastgpt.ChatReq
  105. chatReq.Stream = true
  106. message := make([]fastgpt.Message, 0, 1)
  107. message = append(message, fastgpt.Message{
  108. Content: content,
  109. Role: "user",
  110. })
  111. chatReq.Messages = message
  112. chatReq.ChatId = jwt.HashidsEncode(int(sessionId))
  113. chatReq.Variables = fastgpt.Variables{
  114. Uid: jwt.HashidsEncode(int(userId)),
  115. Name: userInfo.Nickname,
  116. }
  117. // 設置請求體 (這裡是一個簡單的範例,你可以根據需要調整)
  118. jsonData, _ := json.Marshal(chatReq)
  119. fmt.Printf("request data:%v\n", string(jsonData))
  120. // 建立HTTP請求
  121. url := apiBase + fastgpt.GetChatUrl()
  122. request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
  123. if err != nil {
  124. fmt.Printf("Error creating request:%v", err)
  125. return
  126. }
  127. // 設置請求頭
  128. request.Header.Set("Content-Type", "application/json")
  129. request.Header.Set("Authorization", "Bearer "+apiKey)
  130. request.Header.Set("Transfer-Encoding", "chunked")
  131. // 發送請求
  132. client := &http.Client{
  133. Timeout: time.Second * 60,
  134. }
  135. response, err := client.Do(request)
  136. defer response.Body.Close()
  137. // 讀取響應
  138. reader := bufio.NewReader(response.Body)
  139. w.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
  140. w.Header().Set("Connection", "keep-alive")
  141. w.Header().Set("Cache-Control", "no-cache")
  142. flusher, ok := w.(http.Flusher)
  143. if !ok {
  144. http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
  145. return
  146. }
  147. var answer string
  148. for {
  149. line, err := reader.ReadString('\n')
  150. line = strings.Trim(line, " \n")
  151. //fmt.Printf("line = %v\n", line)
  152. if err == io.EOF {
  153. break
  154. }
  155. if len(line) > 6 {
  156. line = line[6:]
  157. chatData := fastgpt.ChatResp{}
  158. err = json.Unmarshal([]byte(line), &chatData)
  159. if err != nil {
  160. fmt.Printf("json unmarshall error:%v\n", err)
  161. break
  162. }
  163. var finish bool
  164. if len(chatData.Choices) == 1 && chatData.Choices[0].FinishReason == "stop" {
  165. finish = true
  166. }
  167. uuidV4, _ := uuid.NewV4() //唯一ID
  168. jsonData := ChatMessage{}
  169. jsonData.Id = chatData.Id
  170. jsonData.SessionId = sessionId
  171. jsonData.Answer = chatData.Choices[0].Delta.Content
  172. jsonData.MessageId = uuidV4.String()
  173. jsonData.Finish = finish
  174. lineData, _ := json.Marshal(jsonData)
  175. // 拼接回答
  176. answer = answer + jsonData.Answer
  177. _, err = fmt.Fprintf(w, "%s", "data: "+string(lineData)+"\r\n")
  178. //fmt.Printf("response=%v\n", string(lineData))
  179. if err != nil {
  180. logAnswer(l, userId, sessionId, botId, botType, answer)
  181. fmt.Printf("Error writing to client:%v \n", err)
  182. break
  183. }
  184. flusher.Flush()
  185. if finish {
  186. logAnswer(l, userId, sessionId, botId, botType, answer)
  187. break
  188. }
  189. }
  190. }
  191. }
  192. // difySendChat 往 Dify 发送内容并获得响应信息
  193. func difySendChat(l *SubmitApiChatLogic, w http.ResponseWriter, content, apiBase, apiKey string, sessionId, userId, botId uint64, botType uint8) {
  194. userInfo, _ := l.svcCtx.DB.WxCardUser.Query().Where(wxcarduser.ID(userId)).First(l.ctx)
  195. var chatReq dify.ChatReq
  196. chatReq.ResponseMode = "streaming"
  197. chatReq.Query = content
  198. chatReq.User = fmt.Sprintf("%d:%s", userId, userInfo.Nickname)
  199. // 这里 sessionId 要与某个 conversation_id 关联,否则查询结果不准
  200. rdsKeySessionId := strconv.Itoa(int(sessionId))
  201. rdsValue := l.svcCtx.Rds.HGet(l.ctx, "miniprogram_dify_conversation_keys", rdsKeySessionId).Val()
  202. chatReq.ConversationId = rdsValue
  203. // 設置請求體 (這裡是一個簡單的範例,你可以根據需要調整)
  204. jsonData, _ := json.Marshal(chatReq)
  205. fmt.Printf("request data:%v\n", string(jsonData))
  206. // 建立HTTP請求
  207. url := apiBase + dify.GetChatUrl() // 替換為正確的FastGPT API端點
  208. request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
  209. if err != nil {
  210. fmt.Printf("Error creating request:%v", err)
  211. return
  212. }
  213. // 設置請求頭
  214. request.Header.Set("Content-Type", "application/json")
  215. request.Header.Set("Authorization", "Bearer "+apiKey)
  216. request.Header.Set("Transfer-Encoding", "chunked")
  217. // 發送請求
  218. client := &http.Client{
  219. Timeout: time.Second * 60,
  220. }
  221. response, err := client.Do(request)
  222. defer response.Body.Close()
  223. // 讀取響應
  224. reader := bufio.NewReader(response.Body)
  225. w.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
  226. w.Header().Set("Connection", "keep-alive")
  227. w.Header().Set("Cache-Control", "no-cache")
  228. flusher, ok := w.(http.Flusher)
  229. if !ok {
  230. http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
  231. return
  232. }
  233. var answer string
  234. for {
  235. line, err := reader.ReadString('\n')
  236. line = strings.Trim(line, " \n")
  237. //fmt.Printf("line = %v\n", line)
  238. if err == io.EOF {
  239. break
  240. }
  241. if len(line) > 6 {
  242. line = line[6:]
  243. chatData := dify.ChatResp{}
  244. err = json.Unmarshal([]byte(line), &chatData)
  245. if err != nil {
  246. fmt.Printf("json unmarshall error:%v\n", err)
  247. fmt.Printf("line:%v\n", line)
  248. break
  249. }
  250. var finish bool
  251. if chatData.Event == "message_end" {
  252. finish = true
  253. }
  254. // 将 ConversationId 与 sessionId 建立关联关系
  255. if chatData.ConversationId != "" {
  256. l.svcCtx.Rds.HSet(l.ctx, "miniprogram_dify_conversation_keys", rdsKeySessionId, chatData.ConversationId)
  257. }
  258. jsonData := ChatMessage{}
  259. jsonData.Id = chatData.Id
  260. jsonData.SessionId = sessionId
  261. jsonData.Answer = chatData.Answer
  262. jsonData.MessageId = chatData.MessageId
  263. jsonData.Finish = finish
  264. jsonData.ConversationId = chatData.ConversationId
  265. lineData, _ := json.Marshal(jsonData)
  266. // 拼接回答
  267. answer = answer + jsonData.Answer
  268. _, err = fmt.Fprintf(w, "%s", "data: "+string(lineData)+"\r\n")
  269. //fmt.Printf("response=%v\n", string(lineData))
  270. if err != nil {
  271. logAnswer(l, userId, sessionId, botId, botType, answer)
  272. fmt.Printf("Error writing to client:%v \n", err)
  273. break
  274. }
  275. flusher.Flush()
  276. if finish {
  277. logAnswer(l, userId, sessionId, botId, botType, answer)
  278. break
  279. }
  280. }
  281. }
  282. }
  283. // logAnswer 保存Ai回答的内容
  284. func logAnswer(l *SubmitApiChatLogic, userId, sessionId, botId uint64, botType uint8, answer string) {
  285. _, err := l.svcCtx.DB.ChatRecords.Create().
  286. SetUserID(userId).
  287. SetSessionID(sessionId).
  288. SetBotType(botType).
  289. SetBotID(botId).
  290. SetContentType(2).
  291. SetContent(answer).
  292. Save(l.ctx)
  293. if err != nil {
  294. fmt.Printf("save answer failed: %v", err)
  295. }
  296. }