format-dialog-answer-from-sse_receive_message.js 17 KB

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