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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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. parent_id: ''
  104. // client_custom_item_think: false, // 是否有思考
  105. // client_custom_item_think_content: '', //思考内容
  106. // client_custom_item_internet: false, //是否联网
  107. // client_custom_item_internet_list: [], //联网内容
  108. // client_custom_chat_message_id: '' //会话的message_id
  109. };
  110. // if (Object.keys(message_obj).includes('message_id')) {
  111. // }
  112. if (Object.keys(message_obj).includes('isDeep')) {
  113. currentMsg.client_custom_item_think = message_obj.isDeep;
  114. }
  115. if (Object.keys(message_obj).includes('isInternet')) {
  116. currentMsg.client_custom_item_internet = message_obj.isInternet;
  117. if (res_content.title === 'SearXNG 搜索') {
  118. currentMsg.client_custom_item_internet_list = res_content.outputs.json;
  119. }
  120. }
  121. /**
  122. * 将前面websocket发来的数据,合并到当前的消息中
  123. */
  124. for (let key in copyPreMsg) {
  125. if (key === 'client_custom_chat_type' || key === 'client_custom_chat_status') {
  126. continue;
  127. } else {
  128. currentMsg[key] = copyPreMsg[key];
  129. }
  130. }
  131. // 0 未开始 1 进行中 2 已结束 3 错误
  132. let chat_status = 0;
  133. let chat_msg = '';
  134. if (resType === 'message') {
  135. // 消息
  136. chat_status = 1;
  137. currentMsg.answer = resMessage;
  138. // currentMsg.client_custom_running_msg = ''; // 已有正常消息,系统消息置空,不再显示
  139. if (resExcelUrl && resExcelName) {
  140. // 智能数据可能存在此数据
  141. // 并且可以确保,返回的是Excel类型,后缀名为 .xlsx,且仅有文件名,没有后缀
  142. currentMsg.client_custom_linked_download_files = {
  143. file_name: resExcelName + '.xlsx',
  144. file_url: resExcelUrl
  145. };
  146. }
  147. if (resFileUrl && resFileName) {
  148. // 出题组卷可能存在此数据
  149. // 并且可以确保返回的文件名没有后缀,且是 .docx 格式
  150. currentMsg.client_custom_linked_download_files = {
  151. file_name: resFileName + '.docx',
  152. file_url: resFileUrl
  153. };
  154. }
  155. if (resDownloadUrl) {
  156. // 文档报告可能存在此数据(type document_report)
  157. const real_url = decodeURIComponent(resDownloadUrl);
  158. const file_url_arr = decodeURIComponent(resDownloadUrl).split('/');
  159. const file_name = file_url_arr[file_url_arr.length - 1];
  160. // const file = {
  161. // file_type: 'word',
  162. // file_url: resDownloadUrl,
  163. // file_name: `${file_name}.docx` || '点击下载'
  164. // };
  165. // currentMsg.file = file;
  166. // const download_file_str = formatFileDownloadStr(file_name, real_url);
  167. // currentMsg.answer = currentMsg.agentInfo + download_file_str;
  168. currentMsg.client_custom_linked_download_files = {
  169. file_name: file_name,
  170. file_url: real_url
  171. };
  172. }
  173. // -------------------------------------------------------------
  174. // -------------------------------------------------------------
  175. // -------------------------------------------------------------
  176. // -------------------------------------------------------------
  177. // -------------------------------------------------------------
  178. // -------------------------------------------------------------
  179. // -------------------------------------------------------------
  180. // 智能数据:代码和图片处理
  181. // 上传一个excel,提问:生成一个饼状图
  182. if (resCode) {
  183. // 智能数据可能存在此字段
  184. const code_formated = formatCodePython(resCode);
  185. currentMsg.client_custom_linked_code = code_formated;
  186. }
  187. if (resSQL) {
  188. // 智能数据可能存在此字段
  189. // 代码数据不能使用markdown解析器进行解析,否则会乱码
  190. const sql_formated = formatCodeSql(resSQL);
  191. // const sql_formated = formatMarkdownString(resSQL);
  192. currentMsg.client_custom_linked_sql = sql_formated;
  193. }
  194. if (resImageUrl) {
  195. // 智能数据可能会存在此值
  196. currentMsg.client_custom_linked_image = resImageUrl;
  197. }
  198. // ------------------------------------------------------
  199. // ------------------------------------------------------
  200. // ------------------------------------------------------
  201. // ------------------------------------------------------
  202. // ------------------------------------------------------
  203. // 知识问答:文档引用
  204. // 处理可能存在的文档引用效果
  205. // 在知识问答板块中,正文存在文档引用效果
  206. currentMsg.answer = formatRefferenceFlag(currentMsg.answer);
  207. // 文档引用效果
  208. if (resRefference) {
  209. // 文档引用,在知识问答板块中,存在一些文档引用内容
  210. // 需要在对话的答案底部显示文档引用的节点
  211. // 将文档引用相关的数据拼接到答案的底部
  212. const ref_str = formatRefferenceString(resRefference, 'client_custom_linked_refference_full_content');
  213. if (ref_str) {
  214. currentMsg.answer = currentMsg.answer + `${ref_str}`;
  215. }
  216. // 添加字段:客户端自定义的相关字段,link完整数据
  217. // 在答案展示部分,需要根据此数据展示文档引用效果
  218. currentMsg.client_custom_linked_refference_full_content = resRefference;
  219. }
  220. }
  221. if (resType === 'stream') {
  222. // 流消息
  223. chat_status = 1;
  224. if (message_obj.isDeep) {
  225. if (!fountThink) {
  226. currentMsg.client_custom_chat_deep_loading = true;
  227. currentMsg.deep_answer = currentMsg.deep_answer += res_content.answer;
  228. if (res_content.answer == '</think>') {
  229. currentMsg.client_custom_chat_deep_loading = false;
  230. fountThink = true;
  231. }
  232. } else {
  233. currentMsg.answer = currentMsg.answer += res_content.answer;
  234. }
  235. } else {
  236. currentMsg.answer = currentMsg.answer += res_content.answer;
  237. }
  238. if (message_obj.parent_id) {
  239. currentMsg.parent_id = message_obj.parent_id;
  240. }
  241. // 测试代码
  242. // if (!message_obj.isDeep) {
  243. // currentMsg.client_custom_chat_deep_loading = true;
  244. // currentMsg.deep_answer = currentMsg.deep_answer += res_content.answer;
  245. // }
  246. // // // 拼接数据
  247. // if (preMsg && preMsg.answer) {
  248. // currentMsg.answer = preMsg.answer + image + resMessage;
  249. // } else {
  250. // currentMsg.answer = resMessage;
  251. // }
  252. if (resFiles) {
  253. // 历史记录也有此同样处理
  254. // 文档合并有此数据
  255. if (resFiles[0]) {
  256. const fileInfo = resFiles[0];
  257. const f_url = fileInfo.file_url;
  258. const mock_url = `http://127.0.0.1${f_url}`;
  259. const urlObj = new URL(mock_url);
  260. const file_type = urlObj.searchParams.get('file_type');
  261. let f_name = fileInfo.file_name;
  262. if (file_type === 'word') {
  263. f_name = f_name + '.docx';
  264. } else {
  265. // 对于非 word类型,未知文件名
  266. }
  267. currentMsg.client_custom_linked_download_files = {
  268. file_name: f_name,
  269. file_url: fileInfo.file_url
  270. };
  271. }
  272. }
  273. if (resDownloadUrl) {
  274. // 报表合并
  275. const real_url = decodeURIComponent(resDownloadUrl);
  276. const file_url_arr = decodeURIComponent(resDownloadUrl).split('/');
  277. const file_name = file_url_arr[file_url_arr.length - 1];
  278. currentMsg.client_custom_linked_download_files = {
  279. file_name: file_name,
  280. file_url: real_url
  281. };
  282. }
  283. }
  284. if (resType === 'system') {
  285. // 系统消息
  286. chat_status = 1;
  287. // 系统消息:记录系统消息,在对话中进行显示
  288. currentMsg.client_custom_running_msg = resMessage;
  289. } else {
  290. // 非系统消息类型:系统消息不再展示
  291. currentMsg.client_custom_running_msg = '';
  292. }
  293. /**
  294. * 处理workflow类型的消息
  295. * workflow_started:工作流开始
  296. * node_started:工作流节点开始
  297. * node_finished:工作流节点结束
  298. * workflow_finished:工作流完成
  299. * */
  300. if (['workflow_started', 'node_started', 'node_finished'].includes(resType)) {
  301. chat_status = 1;
  302. currentMsg.client_custom_running_msg = res_content.title;
  303. }
  304. if (['workflow_finished'].includes(resType)) {
  305. chat_status = 1;
  306. if (res_content.outputs.output) {
  307. currentMsg.answer = res_content.outputs.output || '';
  308. }
  309. if (res_content.outputs.file) {
  310. currentMsg.client_custom_linked_download_files = {
  311. file_name: res_content.outputs.file.filename,
  312. file_url: res_content.outputs.file.url
  313. };
  314. }
  315. if (res_content.outputs.fileList) {
  316. const file_list = res_content.outputs.fileList;
  317. currentMsg.client_custom_linked_download_files_List = [];
  318. for (const file of file_list) {
  319. currentMsg.client_custom_linked_download_files_List.push({
  320. file_name: file.filename,
  321. file_url: file.url
  322. });
  323. }
  324. }
  325. chat_status = 2;
  326. }
  327. if (resType === 'message_file') {
  328. chat_status = 1;
  329. if (res_content.type == 'image') {
  330. currentMsg.client_custom_linked_image = res_content.url;
  331. }
  332. if (res_content.type == 'document') {
  333. if (res_content.url) {
  334. const parts = path.split('/');
  335. const fileName = parts.pop();
  336. currentMsg.client_custom_linked_download_files = {
  337. file_name: fileName,
  338. file_url: res_content.url
  339. };
  340. }
  341. }
  342. }
  343. if (resType === 'message_end') {
  344. // 会话结束
  345. chat_status = 2;
  346. currentMsg.client_custom_chat_status = 2; // 次轮会话已结束
  347. currentMsg.client_custom_chat_type = 1; // 修改消息类型
  348. // currentMsg.client_custom_running_msg = ''; // 已结束:系统消息不再展示
  349. // TODO: 合并原先的内容,形成最终结果
  350. // 如果有新消息,生成一个新消息返回,如果没有新消息,则返回原消息
  351. if (resMessageFiles) {
  352. // 小数绘图存在此数据
  353. const arr = [];
  354. if (Array.isArray(resMessageFiles)) {
  355. resMessageFiles.forEach((item, index) => {
  356. arr.push(item);
  357. });
  358. // fullImageUrls.url = arr;
  359. } else {
  360. // fullImageUrls.url = arr;
  361. }
  362. // arr的值类似于: ['https://xxx.com/ancd.jpg'];
  363. }
  364. if (resMessage) {
  365. // close里依然有message,合并message(小数绘图的文生图里有这种可能性)
  366. currentMsg.answer = resMessage;
  367. }
  368. currentMsg.client_custom_chat_deep_loading = false;
  369. }
  370. if (resType === 'error') {
  371. // 错误消息
  372. chat_status = 3;
  373. currentMsg.client_custom_chat_status = 2; // 本轮会话已结束
  374. currentMsg.client_custom_chat_type = 1; // 修改消息类型: 改为 结束
  375. currentMsg.answer = resMessage;
  376. }
  377. /**
  378. * 依据智能体回答的内容
  379. * 生成一个唯一的ID
  380. */
  381. currentMsg.client_custom_unique_id = generateClientUniqueId(currentMsg.answer);
  382. // ---------------------------------------------------------------
  383. // ---------------------------------------------------------------
  384. // ---------------------------------------------------------------
  385. // ---------------------------------------------------------------
  386. // /**
  387. // * 在answer字符串渲染到界面之前,在answer后拼接的html字符串
  388. // */
  389. // let temp_html_str = '';
  390. // // 处理python代码
  391. // if (currentMsg.client_custom_linked_code) {
  392. // temp_html_str = temp_html_str + currentMsg.client_custom_linked_code;
  393. // }
  394. // // 由于SQL和代码不能通过markdown解析,所以此处进行字符串的拼接
  395. // if (currentMsg.client_custom_linked_sql) {
  396. // temp_html_str = temp_html_str + currentMsg.client_custom_linked_sql;
  397. // }
  398. // // 处理图片展示
  399. // if (currentMsg.client_custom_linked_image) {
  400. // temp_html_str = temp_html_str + `<img src="${currentMsg.client_custom_linked_image}" />`;
  401. // }
  402. // // 拼接文件的下载链接
  403. // if (currentMsg.client_custom_linked_download_files) {
  404. // const file_name = currentMsg.client_custom_linked_download_files.file_name;
  405. // const file_url = currentMsg.client_custom_linked_download_files.file_url;
  406. // const download_file_str = formatFileDownloadStr(file_name, file_url);
  407. // temp_html_str = temp_html_str + download_file_str;
  408. // }
  409. // /**
  410. // * 在answer字符串渲染到界面之前,在answer后拼接的html字符串
  411. // */
  412. // currentMsg.client_custom_attached_html_before_render = temp_html_str;
  413. formatDialogAnswerAttachedHtmlBeforeRender(currentMsg);
  414. if (message_obj.message_id) {
  415. currentMsg.client_custom_chat_message_id = message_obj.message_id;
  416. }
  417. currentMsg.client_custom_chat_message_id = message_obj.message_id || '';
  418. return {
  419. status: chat_status, // 本轮会话的状态:0 未开始 1 进行中 2 已结束 3 错误
  420. message: chat_msg,
  421. data: currentMsg
  422. };
  423. }