format-dialog-answer-from-websocket_receive_message.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import { jsonParse, jsonStringify } from '../../utils/utils';
  2. import generateClientUniqueId from '../../utils/generate-client-unique-id';
  3. import { formatCodePython, formatCodeSql } from './format-code-string';
  4. import { getIcon4Answer } from './get-icon-4-dialog';
  5. import {
  6. formatRefferenceString,
  7. formatRefferenceFlag,
  8. formatFileDownloadStr
  9. } from './format-dialog-answer-custom-markdown-str';
  10. import formatDialogAnswerAttachedHtmlBeforeRender from './format-dialog-answer-attached-html-before-render';
  11. /**
  12. * 格式化websocket发送消息的参数
  13. * 此方法返回的数据项,为智能体返回的回答的数据
  14. *
  15. * 请注意,通常情况下,此文件改动,可能意味着 ./format-dialog-answer-from-history.js 文件也需要改动
  16. * 此文件是针对 实时对话 进行的回答字符串的处理
  17. * ./format-dialog-answer-from-history.js 文件是针对历史会话回答的字符串进行处理
  18. * @param {string} message_str
  19. * @param {object} preMsg - 前一次的消息,如果是流式消息,则需要上一次的Content进行拼接,从而形成新的消息
  20. * @returns
  21. */
  22. export default function formatDialogAnswerFromWebsocketReceiveMessage(message_str, preMsg, agentInfo) {
  23. const res_content = jsonParse(message_str);
  24. // 复制一份原消息内容
  25. const copyPreMsg = jsonParse(jsonStringify(preMsg));
  26. const icon_src = getIcon4Answer(agentInfo);
  27. /**
  28. * 类别:
  29. * close
  30. * error
  31. * message
  32. * stream
  33. * system
  34. */
  35. const resType = res_content.type;
  36. const resMessage = res_content.message; // 消息内容
  37. const resRefference = res_content.reference; // 引用消息
  38. const resStepMessage = res_content.step_message; // 消息内容
  39. const resFiles = res_content.files; // 文件
  40. const resMessageFiles = res_content.message_files; // 关联的文件
  41. const resDownloadUrl = res_content.download_url; // 下载链接
  42. const resFileUrl = res_content.file_url; // 文件链接
  43. const resFileName = res_content.file_name; // 文件名称
  44. const resImageUrl = res_content.image_url; // 图片链接
  45. const resExcelUrl = res_content.excel_url; // excel链接
  46. const resExcelName = res_content.excel_name; // excel文件
  47. const resCode = res_content.code; // 代码内容
  48. const resSQL = res_content.sql; // SQL内容
  49. // 文档出卷返回的数据
  50. // 需要进行文件下载
  51. // file_name: "76157b5884554a56bf3ed27990d44f97_1734603024"
  52. // file_url: "/api/files/download/?agent_id=basic_question_talk&file_id=76157b5884554a56bf3ed27990d44f97_1734603024&file_type=word"
  53. // message: "已经为您生成了10道中等难度的选择题,并已将这些题目保存到名为76157b5884554a56bf3ed27990d44f97_1734603024的docx文件中。希望这些题目符合您的需求!如果有其他要求,请随时告知。"
  54. // type: "message"
  55. // ------------------------------------
  56. // 报表合并返回的数据:需要进行文件下载
  57. // 本次对话的消息内容
  58. const currentMsg = {
  59. /**
  60. * 对话消息类别
  61. * 0 历史消息
  62. * 1 新消息(本次进入页面新建的消息,但是已经结束轮次)
  63. * 2 当前的消息(此刻正在进行中的消息)
  64. */
  65. client_custom_chat_type: 2,
  66. /**
  67. * 此项的类别
  68. * a 智能体的回答
  69. * q 客户端的提问
  70. */
  71. client_custom_item_type: 'a', // 回答的答案
  72. /**
  73. * 会话展示时,可以通过此值,判断该如何进行内容显示
  74. * 当值为0时,可能显示加载中动画
  75. * 当值为1时,展示消息内容,展示 停止生成按钮
  76. * 当值为2时,可能展示反馈操作按钮
  77. */
  78. client_custom_chat_status: 1, // 此轮会话的状态值 0 加载中 1 进行中 2 已结束
  79. // 图标
  80. client_custom_icon: icon_src,
  81. question: res_content.question, // 用户的提问内容
  82. // answer: '', // 消息正文,markdown格式,需要进行解析
  83. // system: '', // 系统消息
  84. // 进行中的消息: 系统消息
  85. client_custom_running_msg: ''
  86. // file: '',
  87. // image_url: '',
  88. // code: '',
  89. // sql: '',
  90. // reference: ''
  91. };
  92. /**
  93. * 将前面websocket发来的数据,合并到当前的消息中
  94. */
  95. for (let key in copyPreMsg) {
  96. if (key === 'client_custom_chat_type' || key === 'client_custom_chat_status') {
  97. continue;
  98. } else {
  99. currentMsg[key] = copyPreMsg[key];
  100. }
  101. }
  102. // 0 未开始 1 进行中 2 已结束 3 错误
  103. let chat_status = 0;
  104. let chat_msg = '';
  105. if (resType === 'message') {
  106. // 消息
  107. chat_status = 1;
  108. currentMsg.answer = resMessage;
  109. if (resExcelUrl && resExcelName) {
  110. // 智能数据可能存在此数据
  111. // 并且可以确保,返回的是Excel类型,后缀名为 .xlsx,且仅有文件名,没有后缀
  112. currentMsg.client_custom_linked_download_files = {
  113. file_name: resExcelName + '.xlsx',
  114. file_url: resExcelUrl
  115. };
  116. }
  117. if (resFileUrl && resFileName) {
  118. // 出题组卷可能存在此数据
  119. // 并且可以确保返回的文件名没有后缀,且是 .docx 格式
  120. currentMsg.client_custom_linked_download_files = {
  121. file_name: resFileName + '.docx',
  122. file_url: resFileUrl
  123. };
  124. }
  125. if (resDownloadUrl) {
  126. // 文档报告可能存在此数据(type document_report)
  127. const real_url = decodeURIComponent(resDownloadUrl);
  128. const file_url_arr = decodeURIComponent(resDownloadUrl).split('/');
  129. const file_name = file_url_arr[file_url_arr.length - 1];
  130. // const file = {
  131. // file_type: 'word',
  132. // file_url: resDownloadUrl,
  133. // file_name: `${file_name}.docx` || '点击下载'
  134. // };
  135. // currentMsg.file = file;
  136. // const download_file_str = formatFileDownloadStr(file_name, real_url);
  137. // currentMsg.answer = currentMsg.agentInfo + download_file_str;
  138. currentMsg.client_custom_linked_download_files = {
  139. file_name: file_name,
  140. file_url: real_url
  141. };
  142. }
  143. // -------------------------------------------------------------
  144. // -------------------------------------------------------------
  145. // -------------------------------------------------------------
  146. // -------------------------------------------------------------
  147. // -------------------------------------------------------------
  148. // -------------------------------------------------------------
  149. // -------------------------------------------------------------
  150. // 智能数据:代码和图片处理
  151. // 上传一个excel,提问:生成一个饼状图
  152. if (resCode) {
  153. // 智能数据可能存在此字段
  154. const code_formated = formatCodePython(resCode);
  155. currentMsg.client_custom_linked_code = code_formated;
  156. }
  157. if (resSQL) {
  158. // 智能数据可能存在此字段
  159. // 代码数据不能使用markdown解析器进行解析,否则会乱码
  160. const sql_formated = formatCodeSql(resSQL);
  161. // const sql_formated = formatMarkdownString(resSQL);
  162. currentMsg.client_custom_linked_sql = sql_formated;
  163. }
  164. if (resImageUrl) {
  165. // 智能数据可能会存在此值
  166. currentMsg.client_custom_linked_image = resImageUrl;
  167. }
  168. // ------------------------------------------------------
  169. // ------------------------------------------------------
  170. // ------------------------------------------------------
  171. // ------------------------------------------------------
  172. // ------------------------------------------------------
  173. // 知识问答:文档引用
  174. // 处理可能存在的文档引用效果
  175. // 在知识问答板块中,正文存在文档引用效果
  176. currentMsg.answer = formatRefferenceFlag(currentMsg.answer);
  177. // 文档引用效果
  178. if (resRefference) {
  179. // 文档引用,在知识问答板块中,存在一些文档引用内容
  180. // 需要在对话的答案底部显示文档引用的节点
  181. // 将文档引用相关的数据拼接到答案的底部
  182. const ref_str = formatRefferenceString(resRefference, 'client_custom_linked_refference_full_content');
  183. if (ref_str) {
  184. currentMsg.answer = currentMsg.answer + `${ref_str}`;
  185. }
  186. // 添加字段:客户端自定义的相关字段,link完整数据
  187. // 在答案展示部分,需要根据此数据展示文档引用效果
  188. currentMsg.client_custom_linked_refference_full_content = resRefference;
  189. }
  190. }
  191. if (resType === 'stream') {
  192. // 流消息
  193. chat_status = 1;
  194. // 拼接数据
  195. if (preMsg && preMsg.answer) {
  196. currentMsg.answer = preMsg.answer + resMessage;
  197. } else {
  198. currentMsg.answer = resMessage;
  199. }
  200. if (resFiles) {
  201. // 历史记录也有此同样处理
  202. // 文档合并有此数据
  203. if (resFiles[0]) {
  204. const fileInfo = resFiles[0];
  205. const f_url = fileInfo.file_url;
  206. const mock_url = `http://127.0.0.1${f_url}`;
  207. const urlObj = new URL(mock_url);
  208. const file_type = urlObj.searchParams.get('file_type');
  209. let f_name = fileInfo.file_name;
  210. if (file_type === 'word') {
  211. f_name = f_name + '.docx';
  212. } else {
  213. // 对于非 word类型,未知文件名
  214. }
  215. currentMsg.client_custom_linked_download_files = {
  216. file_name: f_name,
  217. file_url: fileInfo.file_url
  218. };
  219. }
  220. }
  221. if (resDownloadUrl) {
  222. // 报表合并
  223. const real_url = decodeURIComponent(resDownloadUrl);
  224. const file_url_arr = decodeURIComponent(resDownloadUrl).split('/');
  225. const file_name = file_url_arr[file_url_arr.length - 1];
  226. currentMsg.client_custom_linked_download_files = {
  227. file_name: file_name,
  228. file_url: real_url
  229. };
  230. }
  231. }
  232. if (resType === 'system') {
  233. // 系统消息
  234. chat_status = 1;
  235. // currentMsg.answer = resMessage;
  236. currentMsg.client_custom_running_msg = resMessage;
  237. }
  238. if (resType === 'close') {
  239. // 会话结束
  240. chat_status = 2;
  241. currentMsg.client_custom_chat_status = 2; // 次轮会话已结束
  242. currentMsg.client_custom_chat_type = 1; // 修改消息类型
  243. currentMsg.client_custom_running_msg = ''; // 已结束:系统消息不再展示
  244. // TODO: 合并原先的内容,形成最终结果
  245. // 如果有新消息,生成一个新消息返回,如果没有新消息,则返回原消息
  246. if (resMessageFiles) {
  247. // 小数绘图存在此数据
  248. const arr = [];
  249. if (Array.isArray(resMessageFiles)) {
  250. resMessageFiles.forEach((item, index) => {
  251. arr.push(item);
  252. });
  253. // fullImageUrls.url = arr;
  254. } else {
  255. // fullImageUrls.url = arr;
  256. }
  257. // arr的值类似于: ['https://xxx.com/ancd.jpg'];
  258. }
  259. if (resMessage) {
  260. // close里依然有message,合并message(小数绘图的文生图里有这种可能性)
  261. currentMsg.answer = resMessage;
  262. }
  263. }
  264. if (resType === 'error') {
  265. // 错误消息
  266. chat_status = 3;
  267. currentMsg.client_custom_chat_status = 2; // 本轮会话已结束
  268. currentMsg.client_custom_chat_type = 1; // 修改消息类型: 改为 结束
  269. currentMsg.answer = resMessage;
  270. }
  271. /**
  272. * 依据智能体回答的内容
  273. * 生成一个唯一的ID
  274. */
  275. currentMsg.client_custom_unique_id = generateClientUniqueId(currentMsg.answer);
  276. // ---------------------------------------------------------------
  277. // ---------------------------------------------------------------
  278. // ---------------------------------------------------------------
  279. // ---------------------------------------------------------------
  280. // /**
  281. // * 在answer字符串渲染到界面之前,在answer后拼接的html字符串
  282. // */
  283. // let temp_html_str = '';
  284. // // 处理python代码
  285. // if (currentMsg.client_custom_linked_code) {
  286. // temp_html_str = temp_html_str + currentMsg.client_custom_linked_code;
  287. // }
  288. // // 由于SQL和代码不能通过markdown解析,所以此处进行字符串的拼接
  289. // if (currentMsg.client_custom_linked_sql) {
  290. // temp_html_str = temp_html_str + currentMsg.client_custom_linked_sql;
  291. // }
  292. // // 处理图片展示
  293. // if (currentMsg.client_custom_linked_image) {
  294. // temp_html_str = temp_html_str + `<img src="${currentMsg.client_custom_linked_image}" />`;
  295. // }
  296. // // 拼接文件的下载链接
  297. // if (currentMsg.client_custom_linked_download_files) {
  298. // const file_name = currentMsg.client_custom_linked_download_files.file_name;
  299. // const file_url = currentMsg.client_custom_linked_download_files.file_url;
  300. // const download_file_str = formatFileDownloadStr(file_name, file_url);
  301. // temp_html_str = temp_html_str + download_file_str;
  302. // }
  303. // /**
  304. // * 在answer字符串渲染到界面之前,在answer后拼接的html字符串
  305. // */
  306. // currentMsg.client_custom_attached_html_before_render = temp_html_str;
  307. formatDialogAnswerAttachedHtmlBeforeRender(currentMsg);
  308. return {
  309. status: chat_status, // 本轮会话的状态:0 未开始 1 进行中 2 已结束 3 错误
  310. message: chat_msg,
  311. data: currentMsg
  312. };
  313. }