ConversationChatContainer.vue 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867
  1. <template>
  2. <div class="conversation-chat-container">
  3. <div class="conversation-chat-main" ref="chat_scrren_container">
  4. <div class="conversation-chat-main-left"></div>
  5. <div class="conversation-chat-main-content" :style="style_conversation_chat_content">
  6. <DialogHeader
  7. v-if="!show_welcome && !is_init_model"
  8. class="conversation-chat-header"
  9. :agent_name="current_agent_name"
  10. :style="style_header"
  11. />
  12. <div class="conversation-chat-content" :style="style_body">
  13. <div class="conversation-chat-main-container" :style="style_conversation_chat_main_container">
  14. <template v-if="show_welcome">
  15. <!-- 有会话ID 获取历史记录,显示加载中状态 -->
  16. <!-- <div v-if="session_id" class="conversation-chat-area history-list-loading"> -->
  17. <!-- <div class="scroll-left"></div> -->
  18. <!-- <div class="history-list-loading-container"><a-spin /></div> -->
  19. <!-- <div class="scroll-right"></div> -->
  20. <!-- </div> -->
  21. <!-- 无会话ID,直接对话,此时可现实欢迎消息:欢迎使用报表合并、欢迎使用文档智能、欢迎使用小数绘图 -->
  22. <!-- <DialogWelcome v-if="!session_id" class="conversation-chat-area" /> -->
  23. <DialogWelcome class="conversation-chat-area" />
  24. </template>
  25. <DialogList
  26. v-if="!show_welcome"
  27. ref="list_scroller"
  28. class="conversation-chat-area"
  29. :height="list_height"
  30. :width="chat_width / 2"
  31. :agent_id="agent_id"
  32. :list="chat_display_list"
  33. :suggestion="chat_suggestion"
  34. :answer_action_list="answer_action_list"
  35. :is_show_right_page="show_right"
  36. @share="handleChatShare"
  37. @edit="handleEditChat"
  38. @retry="handleRetryChat"
  39. @toSearchPage="toSearchPage"
  40. @toShowMindMap="toShowMindMap"
  41. @send="handleSendChat"
  42. @submit="handleSuggestSubmit"
  43. :style="style_conversation_chat_list"
  44. >
  45. <!-- <template v-slot:left>
  46. <div class="scroll-left"></div>
  47. </template>
  48. <template v-slot:right>
  49. <div class="scroll-right"></div>
  50. </template> -->
  51. </DialogList>
  52. </div>
  53. <DialogInput
  54. v-show="!is_init_model"
  55. ref="dialog_input"
  56. class="conversation-chat-input"
  57. :style="style_conversation_chat_list"
  58. :ask_input_text="ask_input_text"
  59. :disabled="input_disabled"
  60. :enable_text_input="enable_text_input"
  61. :enable_file_upload="enable_file_upload"
  62. :file_accept="file_accept"
  63. :agent_id="agent_id"
  64. @sizeChange="handleInputSizeChange"
  65. @actionConfigChange="handleActionConfigChange"
  66. @submit="handleInputSubmit"
  67. >
  68. <template v-if="show_input_others_opts" v-slot:top>
  69. <DialogInputTop
  70. :user_input_form="conversation_params.user_input_form"
  71. @initModel="initModel"
  72. ref="dialog_input_top"
  73. />
  74. </template>
  75. <template v-slot:top>
  76. <DialogStopSse v-if="show_stop_sse" @stopResponse="stopResponse"></DialogStopSse>
  77. </template>
  78. </DialogInput>
  79. <DialogCommonUsed
  80. v-if="show_welcome"
  81. :style="style_conversation_chat_list"
  82. @updateAskTxt="updatAskTxt"
  83. ></DialogCommonUsed>
  84. </div>
  85. <DialogFooter v-show="!is_init_model" class="conversation-chat-footer" :style="style_footer" />
  86. </div>
  87. <div class="conversation-chat-main-right" :style="style_conversation_chat_right">
  88. <div class="conversation-chat-expand" :style="style_conversation_chat_expand" v-if="is_show_right_page">
  89. <div class="conversation-chat-mind-map" ref="fullScreen_mindMap">
  90. <!-- 全屏时显示的退出按钮 -->
  91. <button v-if="isFullScreen" class="exit-button" @click="exitFullScreen">X</button>
  92. <MindMap
  93. v-if="show_mind_map"
  94. ref="mind_map"
  95. :content="mindMap_content"
  96. :box_style="box_style"
  97. :svg_style="svg_style"
  98. @extend="extendMindMap"
  99. ></MindMap>
  100. <div class="mind_map_error" v-if="mindMap_err.length">{{ mindMap_err }}</div>
  101. <div class="conversation-chat-mind-map-tools">
  102. <ConversationChatMindMapTools
  103. :isFullScreen="isFullScreen"
  104. @toZoomIn="zoomIn"
  105. @toZoomOut="zoomOut"
  106. @toZoomReset="zoomReset"
  107. @toDownload="downloadMap"
  108. @toFullScreen="toFullScreen"
  109. @toExitFullScreen="exitFullScreen"
  110. ></ConversationChatMindMapTools>
  111. </div>
  112. </div>
  113. <div class="conversation-chat-mind-map-spin" v-if="loading_mind_map">
  114. <a-spin tip="加载中..."></a-spin>
  115. </div>
  116. <div class="conversation-chat-web-page" v-if="show_web_page">
  117. <ConversationChatWebPage :search_list="search_list" @toClose="closeWebPage"></ConversationChatWebPage>
  118. </div>
  119. </div>
  120. <div class="conversation-chat-expand" v-if="show_word_viewer">
  121. <ConversationChatWordViewer :edit_txt="edit_txt_value" @close="closeChatWord"></ConversationChatWordViewer>
  122. </div>
  123. </div>
  124. </div>
  125. <ChatAsideExpandBtn v-if="show_right_page_btn" @toggleRight="toggleRight"></ChatAsideExpandBtn>
  126. </div>
  127. </template>
  128. <script setup>
  129. import { ref, onMounted, onUnmounted, watch, computed, nextTick } from 'vue';
  130. import { useI18n } from 'vue-i18n';
  131. import { marked } from 'marked';
  132. // import { Message } from '@arco-design/web-vue';
  133. import { Message } from '../../3rd-libs/arco-vue-libs';
  134. import { jsonStringify } from '../../utils/utils';
  135. import DialogHeader from '../../modules/conversation-chat/DialogHeader.vue';
  136. import DialogWelcome from '../../modules/conversation-chat/DialogWelcome.vue';
  137. import DialogList from '../../modules/conversation-chat/DialogList.vue';
  138. import DialogInput from '../../modules/conversation-chat/DialogInput.vue';
  139. // import DialogInputAttachedAttrs from '../../modules/conversation-chat/DialogInputAttachedAttrs.vue';
  140. import DialogInputTop from '../../modules/conversation-chat/DialogInputTop.vue';
  141. import DialogFooter from '../../modules/conversation-chat/DialogFooter.vue';
  142. import ChatAsideExpandBtn from '../../modules/conversation-aside/ChatAsideExpandBtn.vue';
  143. import MindMap from '../../components/MindMap.vue';
  144. import ConversationChatMindMapTools from '../../modules/conversation-aside/Conversation-chat-mind-tools.vue';
  145. import ConversationChatWebPage from '../../modules/conversation-aside/Conversation-chat-web-page.vue';
  146. import DialogCommonUsed from '../../modules/conversation-chat/DialogCommonUsed.vue';
  147. import DialogStopSse from '../../modules/conversation-chat/DialogStopSse.vue';
  148. import useWebSocketChat from '../../modules/conversation-dialog/use-websocket-chat';
  149. import useFetchEventSource from '../../modules/conversation-dialog/use-sse-chat';
  150. import {
  151. getHistoryLogs,
  152. getDialogChatId,
  153. uploadFileByAgentInfo,
  154. uploadNewChatInfo,
  155. getMindMapData,
  156. getMapById
  157. } from '../../modules/conversation-dialog/api-dialog';
  158. import formatDialogSendOptions from '../../modules/conversation-dialog/format-dialog-send-options';
  159. import formatDialogSendSseOptions from '../../modules/conversation-dialog/format-dialog-send-sse-options';
  160. import formatDialogHistory from '../../modules/conversation-dialog/format-dialog-history';
  161. import formatDialogQuestionByClient from '../../modules/conversation-dialog/format-dialog-question-by-client';
  162. import formatDialogAnswerForLoading from '../../modules/conversation-dialog/format-dialog-answer-for-loading';
  163. import formatDialogAnswerForException from '../../modules/conversation-dialog/format-dialog-answer-for-exception';
  164. import formatDialogAnswerFromWebsocketReceiveMessage from '../../modules/conversation-dialog/format-dialog-answer-from-websocket_receive_message';
  165. import formatDialogAnswerFromSseReceiveMessage from '../../modules/conversation-dialog/format-dialog-answer-from-Sse_receive_message';
  166. import ConversationChatWordViewer from './ConversationChatWordViewer.vue';
  167. import useEleSizeV1 from '../../utils/use-ele-size-v1';
  168. import useInputClear from './use-input-clear';
  169. import useInputGetOptions from './use-input-get-options';
  170. import useScroll2Bottom from './use-scroll-bottom';
  171. import { IconEye } from '@arco-design/web-vue/es/icon';
  172. import { file, files } from 'jszip';
  173. import { getFileType } from '../../modules/form-dynamic/file-accept';
  174. const props = defineProps({
  175. /**
  176. * 智能体的ID
  177. * 此参数新对话中不存在
  178. */
  179. agent_id: String,
  180. /**
  181. * 智能体的一些关键信息
  182. */
  183. CHAT_AGENT_INFOMATION: Object,
  184. /**
  185. * 会话ID
  186. * 如果有此参数,则意味着获取该对话的记录内容
  187. */
  188. session_id: String,
  189. chat_history: String,
  190. /**
  191. * 动态表单数据
  192. * 根据智能体的agent_id 获取到动态表单数据
  193. * */
  194. conversation_params: Object
  195. });
  196. const emit = defineEmits(['updateSessionId']);
  197. const i18n = useI18n();
  198. /**
  199. * input的一些操作方法
  200. */
  201. const inputClearFn1 = useInputClear('dialog_input'); // 主表单清空
  202. const inputClearFn2 = useInputClear('dialog_input_top'); // 顶部附加表单的数据
  203. const getInputTopOptions = useInputGetOptions('dialog_input_top'); // 获取附加表单参数的方法
  204. /**
  205. * 对话区域滚动到底部
  206. */
  207. const scroll2Bottom = useScroll2Bottom('list_scroller');
  208. const scroll2ListBottom = () => {
  209. nextTick(() => {
  210. scroll2Bottom();
  211. });
  212. };
  213. /**
  214. * 整个对话区域的一些尺寸定义
  215. */
  216. const header_height = 30;
  217. const footer_height = 30;
  218. const input_height = ref(100);
  219. /**
  220. * 获取到对话区域的宽度和高度
  221. */
  222. const { width: chat_width, height: chat_height } = useEleSizeV1('chat_scrren_container');
  223. /**
  224. * 对话列表区域的高度
  225. */
  226. const list_height = computed(() => {
  227. return chat_height.value - header_height - footer_height - input_height.value - 20;
  228. });
  229. const input_style = computed(() => {
  230. return {
  231. // height: input_height + 'px'
  232. };
  233. });
  234. const style_header = computed(() => {
  235. return { height: header_height + 'px' };
  236. });
  237. const style_body = computed(() => {
  238. return { height: chat_height - 30 - 30 + 'px' };
  239. });
  240. const style_footer = computed(() => {
  241. return { height: footer_height + 'px' };
  242. });
  243. const style_conversation_chat_content = computed(() => {
  244. if (is_show_right_page.value || show_word_viewer.value) {
  245. return {
  246. width: 55 + '%'
  247. };
  248. } else {
  249. return {
  250. width: 100 + '%'
  251. };
  252. }
  253. });
  254. const style_conversation_chat_right = computed(() => {
  255. if (is_show_right_page.value || show_word_viewer.value) {
  256. return {
  257. width: 35 + '%',
  258. transition: 'width 0.2s ease'
  259. };
  260. } else {
  261. return {
  262. width: 0,
  263. transition: 'width 0.2s ease'
  264. };
  265. }
  266. });
  267. const style_conversation_chat_expand = computed(() => {
  268. if (loading_mind_map.value) {
  269. return {
  270. position: 'relative'
  271. };
  272. } else {
  273. return {
  274. position: ''
  275. };
  276. }
  277. });
  278. const style_conversation_chat_main_container = computed(() => {
  279. if (!show_welcome.value) {
  280. return {
  281. height: 80 + '%',
  282. transition: 'all 0.2s ease'
  283. };
  284. } else {
  285. return {
  286. height: 20 + '%',
  287. transition: 'all 0.2s ease'
  288. };
  289. }
  290. });
  291. const style_conversation_chat_list = computed(() => {
  292. if (is_show_right_page.value || show_word_viewer.value) {
  293. return {
  294. paddingRight: 3 + '%',
  295. paddingLeft: 3 + '%',
  296. boxSizing: 'border-box'
  297. };
  298. } else {
  299. return {
  300. paddingRight: 20 + '%',
  301. paddingLeft: 20 + '%',
  302. boxSizing: 'border-box'
  303. };
  304. }
  305. });
  306. // ------------------------------------------------------------------
  307. // ------------------------------------------------------------------
  308. // ------------------------------------------------------------------
  309. /**
  310. * 对话数据列表
  311. */
  312. const chat_log_list = ref([]); // 历史对话的数据列表
  313. const chat_new_list = ref([]); // 新对话的数据列表
  314. const chat_suggestion = ref([]); // 对话推荐项目
  315. /**
  316. * 当前对话使用的ID
  317. * 当用户有输入后,点击提交,此时创建新对话,新对话的ID为此ID
  318. * 之后用户可以一直使用此ID进行对话。
  319. */
  320. const chat_round_id = ref(null);
  321. /**
  322. * 对话发起的次数记录
  323. * 用户每发起一次会话,此值会加1input_disable
  324. */
  325. const chat_round_sent_count = ref(0);
  326. /**
  327. * 新对话,客户端的提问数据
  328. * 用以临时保存客户端的提问数据
  329. * 每当用户确认进行提问时,将新提问赋给此值
  330. * 一旦对话消息通过websocket发送到服务器,此值可清空
  331. */
  332. const chat_new_question = ref({});
  333. /**
  334. * 界面展示的对话列表
  335. * 当用户从历史会话进入时,历史会话记录有数据,当用户基于历史会话进行对话时,新会话也有数据,
  336. * 当用户从agent进入时,仅有新会话,没有历史会话
  337. *
  338. * 页面渲染的会话列表是 历史会话 + 新会话数据
  339. */
  340. const chat_display_list = computed(() => {
  341. return [].concat(chat_log_list.value, chat_new_list.value);
  342. });
  343. /**
  344. * 是否禁用输入组件
  345. */
  346. const input_disabled = ref(false);
  347. /**
  348. * 是否显示停止按钮
  349. * */
  350. const show_stop_sse = ref(false);
  351. /**
  352. * 菜单ID编号
  353. * 1 报表合并
  354. * 2 知识问答
  355. * 3 文档智能
  356. * 4 智能问答
  357. * 5 智能数据
  358. * 6 小数绘图
  359. * 7 文档出卷
  360. * 8 出题组卷
  361. * 9 文档报告
  362. */
  363. const current_menu_id = computed(() => {
  364. const agent = props.CHAT_AGENT_INFOMATION.agent;
  365. const menu_id = agent.menuId / 1;
  366. return menu_id;
  367. });
  368. const current_agent_name = computed(() => {
  369. const agent = props.CHAT_AGENT_INFOMATION.agent;
  370. const name = agent.name || '新会话';
  371. return name;
  372. });
  373. const current_agent_mode = computed(() => {
  374. const agent = props.CHAT_AGENT_INFOMATION.agent;
  375. const mode = agent.mode;
  376. return mode;
  377. });
  378. /**
  379. * 表单区域:是否显示附加参数表单
  380. *
  381. * 根据动态表单返回的数据中user_input_form中是否有数据来控制是否显示附加参数表单
  382. */
  383. const show_input_others_opts = computed(() => {
  384. if (
  385. props.conversation_params &&
  386. props.conversation_params.user_input_form &&
  387. props.conversation_params.user_input_form.length
  388. ) {
  389. return true;
  390. }
  391. return false;
  392. // const menu_id = current_menu_id.value;
  393. // /**
  394. // * 7 文档出卷
  395. // * 9 文档报告
  396. // */
  397. // if (menu_id === 7 || menu_id === 9) {
  398. // return true;
  399. // }
  400. // return false;
  401. });
  402. /**
  403. * 是否支持文本输入
  404. * 返回 true,输入区域将不存在文本输入框
  405. * 返回 false,输入区域将可以进行文本输入
  406. *
  407. *通过conversation_params.retriever_resource.enabled来控制是否支持文本输入
  408. */
  409. const enable_text_input = computed(() => {
  410. let enabled = false;
  411. if (props.conversation_params && props.conversation_params.retriever_resource) {
  412. enabled = props.conversation_params.retriever_resource.enabled;
  413. }
  414. return enabled;
  415. });
  416. /**
  417. * 对话文本是否必须
  418. * 此值如果为 true,则在发起对话之前,会进行 文本的存在性检查
  419. * 此值如果为 false,则在发起对话之前,不会进行 文件存在性检查
  420. */
  421. const is_chat_text_required = computed(() => {
  422. const is_enable = enable_text_input.value;
  423. if (!is_enable) {
  424. return false;
  425. }
  426. return true;
  427. });
  428. /**
  429. * 默认的文本值
  430. */
  431. const default_text_value = computed(() => {
  432. const menu_id = current_menu_id.value;
  433. /**
  434. * 某些情况下,无需输入文本
  435. * 但是在对话部分,需要展示用户的提问,此时可以给一个默认的文本值
  436. * 例如在报表合并情况下,仅需要上传文件,不需要输入文字
  437. */
  438. if (menu_id === 1) {
  439. return '合并Excel';
  440. }
  441. return '';
  442. });
  443. /**
  444. * 表单区域:是否允许文件上传
  445. * 返回 ture,输入区域将会有文件上传按钮
  446. * 返回 false,输入区域将没有文件上传按钮
  447. *
  448. * 根据 conversation_params.file_upload.enabled来控制是否允许文件上传
  449. */
  450. const enable_file_upload = computed(() => {
  451. let enabled = false;
  452. if (props.conversation_params && props.conversation_params.file_upload) {
  453. enabled = props.conversation_params.file_upload.enabled;
  454. }
  455. return enabled;
  456. });
  457. /**
  458. * 对话文件是否必须上传
  459. * 此值如果返回 true,则 在对话时,会进行文件检查
  460. * 此值如果返回 false,则 在对话时,不会检查文件的存在性
  461. */
  462. const is_chat_file_required = computed(() => {
  463. const is_enable = enable_file_upload.value;
  464. if (!is_enable) {
  465. return false;
  466. }
  467. const menu_id = current_menu_id.value;
  468. // 报表合并: 文件必须
  469. if (menu_id === 1) {
  470. // 当第一次会话时,要求必须上传文件
  471. // 非第一次,不做要求
  472. if (chat_round_sent_count.value === 0) {
  473. return true;
  474. }
  475. }
  476. // 文档出卷:文件必须上传
  477. if (menu_id === 7) {
  478. // 当第一次对话时,必须上传文件,
  479. // 非第一次,不做要求
  480. if (chat_round_sent_count.value === 0) {
  481. return true;
  482. }
  483. }
  484. return false;
  485. });
  486. /**
  487. * 此值在 enable_file_upload === true 的前提下有效
  488. * 表单区域:文件上传允许的文件类型
  489. * 对应表单参数的 accept
  490. */
  491. const file_accept = computed(() => {
  492. const menu_id = current_menu_id.value;
  493. /**
  494. * 1 报表合并:上传几个文档,进行合并 允许 .xlsx
  495. * 3 文档智能:上传文档,让智能体进行解析 允许: .pdf/.docx
  496. * 5 智能数据:上传Excel,生成饼状图 允许 .xlsx
  497. * 6 小数绘图:上传图片,描述图片的内容 允许 .png/jpg/jpeg
  498. * 9 文档报告: 允许 .docx
  499. */
  500. if (menu_id === 1) {
  501. return '.xlsx';
  502. }
  503. if (menu_id === 3) {
  504. /**
  505. * 2025-2-8 16:50
  506. * 王永新和赵源在测试deepseek时发现 文档智能 能上传的文件类型有限
  507. * 要求将accept调整为和dify的 文档 类型一致
  508. */
  509. // return '.pdf,.docx';
  510. return '.txt,.md,.markdown,.pdf,.html,.xlsx,.xls,.docx,.csv,.eml,.msg,.pptx,.ppt,.xml,.epub';
  511. }
  512. // 智能数据
  513. if (menu_id === 5) {
  514. return '.xlsx,.xls,.csv';
  515. }
  516. if (menu_id === 6) {
  517. return '.jpg,.jpeg,.png';
  518. }
  519. if (menu_id === 9) {
  520. return '.docx';
  521. }
  522. return '*';
  523. });
  524. /**
  525. * 答案可用的操作按钮
  526. * 可用的值有 [] 或者 ['copy']
  527. * 后续可能有新值添加
  528. */
  529. const answer_action_list = computed(() => {
  530. const menu_id = current_menu_id.value;
  531. // 报表合并无操作按钮
  532. if (menu_id === 1) {
  533. return [];
  534. }
  535. return ['copy', 'retry', 'edit', 'download'];
  536. });
  537. /**
  538. * 控制对话列表滚动条展示
  539. * 当右侧有页面打开,隐藏滚动条
  540. *
  541. */
  542. const show_right = computed(() => {
  543. return is_show_right_page.value || show_word_viewer.value;
  544. });
  545. /**
  546. * 内容分享操作
  547. * @param chat_id
  548. */
  549. const handleChatShare = (chat_id) => {};
  550. const show_word_viewer = ref(false);
  551. // 要编辑的文本内容
  552. const edit_txt_value = ref('');
  553. const closeChatWord = () => {
  554. show_word_viewer.value = false;
  555. };
  556. // 更新输入框中的文本
  557. const ask_input_text = ref('');
  558. //
  559. const updatAskTxt = (txt) => {
  560. ask_input_text.value = txt;
  561. };
  562. /**
  563. * 编辑文本
  564. * */
  565. const handleEditChat = (item) => {
  566. // edit_txt_value.value = marked(item.answer || '');
  567. edit_txt_value.value = item.answer;
  568. is_show_right_page.value = false;
  569. show_word_viewer.value = true;
  570. };
  571. const retry_action_option = ref(null);
  572. const handleActionConfigChange = (config) => {
  573. retry_action_option.value = config;
  574. };
  575. /**
  576. * 重新生成
  577. * */
  578. const handleRetryChat = async (item) => {
  579. //根据message_id查找对应的问题
  580. const question_idx = chat_display_list.value.findIndex(
  581. (it) => it.client_custom_chat_message_id === item.client_custom_chat_message_id
  582. );
  583. if (question_idx != -1) {
  584. const question_item = chat_display_list.value[question_idx - 1];
  585. let chatMode = retry_action_option.value?.chatMode || 1;
  586. let isDeep = retry_action_option.value?.isDeep || 1;
  587. if (question_item.client_custom_upload_file_list?.length) {
  588. chatMode = 1;
  589. }
  590. if (question_item.client_custom_knowledge_id?.length) {
  591. chatMode = 3;
  592. }
  593. const options = {
  594. files: question_item.client_custom_upload_file_list || [],
  595. text: question_item.question,
  596. config: {
  597. knowledgeId: question_item.client_custom_knowledge_id || [],
  598. isDeep,
  599. chatMode
  600. }
  601. };
  602. toSubmitNewChat(options);
  603. }
  604. };
  605. // 编辑问题重新问答
  606. const handleSendChat = async (item) => {
  607. let chatMode = retry_action_option.value?.chatMode || 1;
  608. let isDeep = retry_action_option.value?.isDeep || 1;
  609. let parent_id = '';
  610. const idx = chat_display_list.value.findIndex((it) => it.client_custom_unique_id === item.client_custom_unique_id);
  611. if (idx !== -1 && idx > 0) {
  612. parent_id = chat_display_list.value[idx - 1].parent_id || '';
  613. }
  614. if (item.client_custom_upload_file_list?.length) {
  615. chatMode = 1;
  616. }
  617. if (item.client_custom_knowledge_id?.length) {
  618. chatMode = 3;
  619. }
  620. if (item.client_custom_upload_file_list?.length) {
  621. chatMode = 1;
  622. }
  623. if (item.client_custom_knowledge_id?.length) {
  624. chatMode = 3;
  625. }
  626. const options = {
  627. files: item.client_custom_upload_file_list || [],
  628. text: item.content,
  629. parent_id,
  630. config: {
  631. knowledgeId: item.client_custom_knowledge_id || [],
  632. isDeep,
  633. chatMode
  634. }
  635. };
  636. toSubmitNewChat(options);
  637. };
  638. /**
  639. * 在聊天界面点击推荐项
  640. * 继续对话
  641. */
  642. const handleSuggestSubmit = (suggest) => {};
  643. /**
  644. * 处理页面中的错误消息
  645. * @param msg
  646. */
  647. const handleErrorMessage = (msg) => {
  648. Message.error({ content: msg, position: 'top' });
  649. };
  650. // ---------------------------------------------
  651. /**
  652. * 异常处理
  653. * 异常处理过程是一个会话模拟的过程
  654. * 先在界面上显示加载动画,然后紧接着显示错误提示
  655. * 让用户感知到智能体有处理数据的过程
  656. * 所以在异常处理阶段,有一些需要处理的关键点,比如欢迎界面的显示(如果在显示,则需要隐藏,否则对话显示不了)
  657. * @param exceptionInfo
  658. */
  659. const handleChatRequestionException = (exceptionInfo) => {
  660. // 已结束
  661. const answerData = formatDialogAnswerForException(props.CHAT_AGENT_INFOMATION, exceptionInfo);
  662. // 原来的最后一项弹出
  663. chat_new_list.value.pop();
  664. // 添加新的最后一项
  665. chat_new_list.value.push(answerData);
  666. // 对话已结束:文件上传、文本输入区域放开使用
  667. input_disabled.value = false;
  668. // 对话区域滚动到底部
  669. scroll2ListBottom();
  670. };
  671. /**
  672. * 输入区域的尺寸发生了变化
  673. * 整个对话区域的尺寸也需要依据此变化而变化
  674. * @param size
  675. */
  676. const handleInputSizeChange = (size) => {
  677. const input_width = size.width;
  678. // const input_height = size.height;
  679. setTimeout(() => {
  680. input_height.value = size.height;
  681. });
  682. };
  683. /**
  684. * 是否显示欢迎页
  685. * 当用户进入对话页面,还未正式开始对话时
  686. * 对话区域会显示欢迎内容
  687. * 当用户开始对话时,欢迎内容消失,对话列表开始展示
  688. */
  689. const show_welcome = ref(false);
  690. const mindMap_str = ref('');
  691. /**
  692. * mindMap组件对象
  693. *
  694. * */
  695. const mind_map = ref();
  696. /**
  697. * mindMap 显示的内容
  698. *
  699. * */
  700. const mindMap_content = ref('');
  701. const mindMap_err = ref('');
  702. /**
  703. * 图谱的查询 文本字段
  704. * */
  705. const mindMap_txt = ref('');
  706. const minMap_messageId = ref('');
  707. const fullScreen_mindMap = ref(null);
  708. const isFullScreen = ref(false);
  709. /**
  710. * mindMap 盒子的尺寸
  711. * */
  712. const box_style = computed(() => {
  713. if (is_show_right_page.value) {
  714. return {
  715. width: '100%',
  716. height: 'calc(100% - 30px)'
  717. };
  718. }
  719. });
  720. /**
  721. * mindMap 导图实际尺寸的尺寸
  722. * */
  723. const svg_style = computed(() => {
  724. if (is_show_right_page.value) {
  725. return {
  726. width: '100%',
  727. height: '100%'
  728. };
  729. }
  730. });
  731. /**
  732. * mindMap 展开按钮事件
  733. * node:当前node数据
  734. * full:全部node数据
  735. * el:当前点击的元素
  736. * */
  737. const extendMindMap = async (item) => {
  738. const { node, full, el } = item;
  739. mindMap_txt.value = decodeHtmlCharCodes(node.content);
  740. getMindMapData({ messageId: minMap_messageId.value, query: mindMap_txt.value }).then((res) => {
  741. if (res.code / 1 === 200) {
  742. mindMap_str.value = res.data.mindmap;
  743. mindMap_content.value = res.data.mindmap;
  744. } else {
  745. mindMap_content.value = mindMap_str.value;
  746. }
  747. });
  748. };
  749. // 转码函数 展开节点用到
  750. function decodeHtmlCharCodes(str) {
  751. const textarea = document.createElement('textarea');
  752. textarea.innerHTML = str;
  753. return textarea.value;
  754. }
  755. const is_show_right_page = ref(false); //控制右侧页面展示显示
  756. const show_mind_map = ref(false); //控制右侧思维导图是否显示
  757. const loading_mind_map = ref(false); //初始控制思维导图父盒子显示加载中
  758. const show_web_page = ref(false); //控制网页搜索展示
  759. // 关闭网页搜索
  760. const closeWebPage = () => {
  761. show_web_page.value = false;
  762. is_show_right_page.value = false;
  763. };
  764. // 展示右侧页面按钮
  765. const show_right_page_btn = computed(() => {
  766. return !show_word_viewer.value && !show_welcome.value;
  767. }); //控制右侧顶部按钮开关
  768. /**
  769. * 控制右侧显示/隐藏
  770. * */
  771. const toggleRight = () => {
  772. is_show_right_page.value = !is_show_right_page.value;
  773. if (is_show_right_page.value) {
  774. mindMap_content.value = mindMap_str.value;
  775. setTimeout(() => {
  776. show_mind_map.value = true;
  777. }, 200);
  778. show_web_page.value = true;
  779. } else {
  780. show_mind_map.value = false;
  781. show_web_page.value = false;
  782. }
  783. };
  784. /**
  785. * 放大
  786. * */
  787. const zoomIn = () => {
  788. mind_map.value.zoomIn();
  789. };
  790. /**
  791. * 缩小
  792. * */
  793. const zoomOut = () => {
  794. mind_map.value.zoomOut();
  795. };
  796. /**
  797. * 自适应
  798. * */
  799. const zoomReset = () => {
  800. mind_map.value.zoomReset();
  801. };
  802. const original_obj = ref({
  803. height: '',
  804. width: '',
  805. position: '',
  806. zIndex: ''
  807. }); // 用来存储原始高度
  808. /**
  809. * 全屏
  810. * */
  811. const toFullScreen = () => {
  812. if (isFullScreen.value) {
  813. return;
  814. }
  815. // 动态设置div高度为屏幕可见高度(排除浏览器头部)
  816. if (fullScreen_mindMap.value) {
  817. // 存储原始高度,进入全屏时恢复
  818. original_obj.value.height = fullScreen_mindMap.value.style.height;
  819. original_obj.value.width = fullScreen_mindMap.value.style.width;
  820. original_obj.value.position = fullScreen_mindMap.value.style.position;
  821. original_obj.value.zIndex = fullScreen_mindMap.value.style.zIndex;
  822. // 设置div高度为视口高度,避免覆盖浏览器头部
  823. fullScreen_mindMap.value.style.height = window.innerHeight - 90 + 'px';
  824. fullScreen_mindMap.value.style.width = window.innerWidth - 10 + 'px';
  825. fullScreen_mindMap.value.style.position = 'absolute';
  826. fullScreen_mindMap.value.style.top = '0';
  827. fullScreen_mindMap.value.style.left = '0';
  828. fullScreen_mindMap.value.style.zIndex = '9995';
  829. isFullScreen.value = true;
  830. zoomReset();
  831. }
  832. };
  833. // 退出全屏模式的函数
  834. const exitFullScreen = () => {
  835. // 恢复原始高度
  836. if (fullScreen_mindMap.value) {
  837. fullScreen_mindMap.value.style.height = original_obj.value.height;
  838. fullScreen_mindMap.value.style.width = original_obj.value.width;
  839. fullScreen_mindMap.value.style.position = original_obj.value.position;
  840. fullScreen_mindMap.value.style.zIndex = original_obj.value.zIndex;
  841. isFullScreen.value = false;
  842. zoomReset();
  843. }
  844. };
  845. /**
  846. * 下载
  847. * */
  848. const downloadMap = () => {
  849. mind_map.value.downloadPNG();
  850. };
  851. /**
  852. * 打开欢迎页面
  853. * */
  854. const toShowWelcome = () => {
  855. toNewChat();
  856. show_welcome.value = true;
  857. chat_display_list.value = [];
  858. chat_log_list.value = [];
  859. chat_new_list.value = [];
  860. is_show_right_page.value = false;
  861. };
  862. const toNewChat = () => {
  863. if (is_init_model.value) {
  864. pushInitAnswer();
  865. }
  866. is_init_model.value = false;
  867. chat_display_list.value = [];
  868. if (!show_welcome.value) {
  869. return;
  870. }
  871. show_welcome.value = false;
  872. pushInitAnswer();
  873. };
  874. const pushInitAnswer = () => {
  875. chat_log_list.value.push({
  876. answer: 'Hi,我是Smart~ <br> 很高兴遇见你!你可以随时把网址或文件发给我,我来帮你看看',
  877. client_custom_chat_status: '2',
  878. client_custom_chat_type: '0',
  879. client_custom_item_type: 'a',
  880. client_custom_item_think: false, // 是否有思考
  881. client_custom_item_think_content: '', //思考内容
  882. client_custom_item_internet: false, //是否联网
  883. client_custom_item_internet_list: [], //联网内容
  884. client_custom_chat_message_id: '' //会话的message_id
  885. });
  886. };
  887. /**
  888. * 智能体中区分是首次进去展示动态表单,还是点击按钮进去展示动态表单
  889. * */
  890. const is_init_model = ref(false);
  891. const initModel = (val) => {
  892. is_init_model.value = val;
  893. };
  894. const target_action_config = ref({});
  895. /**
  896. * 初始化sse相关方法
  897. * */
  898. const { startStream, stopStream } = useFetchEventSource();
  899. /**
  900. * 输入已经确认要提交了
  901. *
  902. * 1. 创建会话 get-chat-id
  903. * 2. 文件上传 如果有文件,则需要文件上传,没有文件,则不需要文件上传
  904. * 3. 连接socket,socket 发送文本与文件数据内容
  905. * 4. 接收 socket 返回的数据项
  906. */
  907. const handleInputSubmit = async (options) => {
  908. show_welcome.value = false;
  909. if (!chat_display_list.value.length) {
  910. pushInitAnswer();
  911. }
  912. if (!props.agent_id) {
  913. toSubmitNewChat(options);
  914. return;
  915. }
  916. let ask_text = options.text; // 文本
  917. const ask_files = options.files; // 文件列表
  918. const action_config = options.config; //是否深度思考,是否联网,是否选择知识库
  919. target_action_config.value = {};
  920. // 用户选定的文件数量
  921. const file_total = ask_files.length;
  922. const is_text_required = is_chat_text_required.value;
  923. const is_file_required = is_chat_file_required.value;
  924. // 检查对话文字是否必须
  925. if (is_text_required) {
  926. if (!ask_text) {
  927. const msg = i18n.t('conversation.please_input_text');
  928. handleErrorMessage(msg);
  929. return;
  930. }
  931. } else {
  932. ask_text = default_text_value.value;
  933. }
  934. // 检查对话文件是否必须
  935. if (is_file_required) {
  936. if (file_total === 0) {
  937. const msg = i18n.t('conversation.please_upload_file');
  938. handleErrorMessage(msg);
  939. return;
  940. }
  941. }
  942. // --------------------------------------------
  943. // --------------------------------------------
  944. // --------------------------------------------
  945. // --------------------------------------------
  946. // --------------------------------------------消息发送前的准备工作
  947. // 保存附加参数数据,由于下方有一个 inputClearFn2() 方法的执行
  948. // 会将所有数据清空,故附件参数需要在清理之前保存
  949. const other_options = getInputTopOptions();
  950. /**
  951. * 当新消息发送时
  952. * 意味着新一轮对话的开始
  953. * 此时应该将之前一轮对话的信息进行清空
  954. *
  955. * 整理客户端提问的数据
  956. * 组装Question数据
  957. */
  958. const client_question = formatDialogQuestionByClient(ask_text, ask_files);
  959. // 保存用户的提问信息
  960. // 当消息正式发出后,将提问数据放入列表中
  961. chat_new_question.value = client_question;
  962. // 消息即将发送,记录用户的提问数据
  963. chat_new_list.value.push(chat_new_question.value);
  964. // 客户端答案数据(loading状态),客户端模拟的答案数据,依据此数据展示加载中的动画
  965. const answerLoadingMsg = formatDialogAnswerForLoading(props.CHAT_AGENT_INFOMATION);
  966. chat_new_list.value.push(answerLoadingMsg);
  967. /**
  968. * 会话已确认发送
  969. * 隐藏欢迎内容,显示对话内容
  970. */
  971. show_welcome.value = false;
  972. // 清空文本数据和文件数据,避免下一次会话冲突
  973. // 清空表单里的数据
  974. inputClearFn1();
  975. inputClearFn2();
  976. // 清空可能存在的推荐项目
  977. chat_suggestion.value = [];
  978. // 清空已保存的提问数据(提问数据已经推入数组,此处无需继续保存)
  979. chat_new_question.value = null;
  980. // 文件上传、文本输入区域禁用
  981. input_disabled.value = true;
  982. // 对话区域滚动到底部
  983. scroll2ListBottom();
  984. // --------------------------------------------
  985. // --------------------------------------------
  986. // --------------------------------------------
  987. // --------------------------------------------
  988. // --------------------------------------------
  989. const agent_id = props.agent_id;
  990. // 对话分为三个步骤
  991. // 1. 创建会话,如果已经创建,则不需要再次创建
  992. // 2. 上传文件,如果不存在文件,则无需文件上传
  993. // 3. 连接websocket
  994. /**
  995. * 创建会话 get chat id
  996. * 判断对话是否已经创建
  997. * 如果对话已经创建,则使用已经创建的对话
  998. * 否则,创建新对话
  999. */
  1000. if (chat_round_id.value === null) {
  1001. const session_id = props.session_id;
  1002. if (session_id) {
  1003. // 已有会话ID,使用此会话ID
  1004. chat_round_id.value = session_id;
  1005. } else {
  1006. // 无会话ID,则是首页会话
  1007. if (!agent_id) {
  1008. return;
  1009. }
  1010. // 未创建chat,开始创建
  1011. try {
  1012. const chatResult = await getDialogChatId(agent_id);
  1013. if (chatResult.code / 1 === 200) {
  1014. // 成功
  1015. const res_data = chatResult.data;
  1016. chat_round_id.value = res_data.chat_id;
  1017. } else {
  1018. // 失败
  1019. // 处理异常:直接在界面上显示
  1020. handleChatRequestionException({
  1021. message: `code: ${chatResult.code} message: ${chatResult.msg}`
  1022. });
  1023. return;
  1024. }
  1025. } catch (err) {
  1026. console.log('err-nfsdnakiuwirea7', err);
  1027. // 处理错误
  1028. handleChatRequestionException({
  1029. message: err.message
  1030. });
  1031. return;
  1032. }
  1033. }
  1034. } else {
  1035. // 已创建chat,使用已创建的会话ID,进入下一步流程
  1036. }
  1037. /**
  1038. * 查看动态表单中是否有文件需要上传,如果有,则需要先上传文件
  1039. * */
  1040. if (other_options && other_options.files) {
  1041. const files_obj = other_options.files;
  1042. const chat_id = chat_round_id.value;
  1043. const menu_id = current_menu_id.value;
  1044. for (const key in files_obj) {
  1045. const upload_files = files_obj[key].map((item) => item.file);
  1046. const uploadResult = await uploadFileByAgentInfo(agent_id, chat_id, upload_files, menu_id);
  1047. if (uploadResult && uploadResult.length) {
  1048. files_obj[key] = files_obj[key].map((file) => {
  1049. const uploadedFile = uploadResult.find((uploaded) => uploaded.name === file.name);
  1050. // 如果找到了匹配的文件,则回填 id
  1051. if (uploadedFile) {
  1052. file.upload_file_id = uploadedFile.id;
  1053. file.url = '';
  1054. }
  1055. return file;
  1056. });
  1057. // 判断如果对应的文件是单文件上传,则显示为对象
  1058. if (files_obj[key].some((item) => item.limit_1)) {
  1059. files_obj[key] = files_obj[key][0];
  1060. }
  1061. }
  1062. }
  1063. other_options.options = {
  1064. ...other_options.options,
  1065. ...files_obj
  1066. };
  1067. }
  1068. // 文件上传后的信息
  1069. let files_response = null;
  1070. /**
  1071. * 如果存在文件数据,则进行文件上传
  1072. */
  1073. if (file_total !== 0) {
  1074. // 进行文件上传,获取文件信息
  1075. const chat_id = chat_round_id.value;
  1076. if (agent_id) {
  1077. try {
  1078. const menu_id = current_menu_id.value;
  1079. const uploadResult = await uploadFileByAgentInfo(agent_id, chat_id, ask_files, menu_id);
  1080. if (uploadResult.length) {
  1081. files_response = uploadResult.map((item) => {
  1082. return {
  1083. ...item,
  1084. url: '',
  1085. type: getFileType(item),
  1086. transfer_method: 'local_file',
  1087. upload_file_id: item.id
  1088. };
  1089. });
  1090. }
  1091. // if (uploadResult.code / 1 === 200) {
  1092. // // 文件上传成功
  1093. // files_response = uploadResult.data;
  1094. // } else {
  1095. // // 文件上传异常
  1096. // handleChatRequestionException({
  1097. // message: `code: ${uploadResult.code} \n\n message: ${uploadResult.msg}`
  1098. // });
  1099. // return;
  1100. // }
  1101. } catch (err) {
  1102. console.log('err-fhskafhakfjslkfjl', err);
  1103. handleChatRequestionException({
  1104. message: err.message
  1105. });
  1106. return;
  1107. }
  1108. } else {
  1109. }
  1110. }
  1111. // TODO: 重新组装sse参数,调用sse接口问答
  1112. const query_params = {
  1113. agent_id,
  1114. mode: current_agent_mode.value, //智能体类型
  1115. query: ask_text,
  1116. files: files_response || [],
  1117. inputs: other_options ? other_options.options : [],
  1118. session_id: props.session_id || ''
  1119. };
  1120. startStream(query_params, sseOnMessage);
  1121. return;
  1122. };
  1123. // 首页对话
  1124. const toSubmitNewChat = async (options) => {
  1125. let ask_text = options.text; // 文本
  1126. const ask_files = options.files; // 文件列表
  1127. const action_config = options.config; //是否深度思考,是否联网,是否选择知识库
  1128. target_action_config.value = options.config;
  1129. let parent_id = '';
  1130. if (chat_display_list.value.length) {
  1131. const lastItem = chat_display_list.value[chat_display_list.value.length - 1];
  1132. if (lastItem.parent_id) {
  1133. parent_id = lastItem.parent_id;
  1134. }
  1135. }
  1136. if (options.parent_id) {
  1137. parent_id = options.parent_id;
  1138. }
  1139. mindMap_txt.value = options.text;
  1140. // 用户选定的文件数量
  1141. const file_total = ask_files.length;
  1142. if (!ask_text) {
  1143. const msg = i18n.t('conversation.please_input_text');
  1144. handleErrorMessage(msg);
  1145. return;
  1146. }
  1147. const other_options = getInputTopOptions();
  1148. const knowledgeIdList = target_action_config.value?.knowledgeId || [];
  1149. const client_question = formatDialogQuestionByClient(ask_text, ask_files, knowledgeIdList);
  1150. // 保存用户的提问信息
  1151. // 当消息正式发出后,将提问数据放入列表中
  1152. chat_new_question.value = client_question;
  1153. // 消息即将发送,记录用户的提问数据
  1154. chat_new_list.value.push(chat_new_question.value);
  1155. // 客户端答案数据(loading状态),客户端模拟的答案数据,依据此数据展示加载中的动画
  1156. const answerLoadingMsg = formatDialogAnswerForLoading(props.CHAT_AGENT_INFOMATION);
  1157. chat_new_list.value.push(answerLoadingMsg);
  1158. inputClearFn1();
  1159. inputClearFn2();
  1160. // 清空可能存在的推荐项目
  1161. chat_suggestion.value = [];
  1162. // 清空已保存的提问数据(提问数据已经推入数组,此处无需继续保存)
  1163. chat_new_question.value = null;
  1164. // 文件上传、文本输入区域禁用
  1165. input_disabled.value = true;
  1166. // 对话区域滚动到底部
  1167. scroll2ListBottom();
  1168. // 文件上传后的信息
  1169. let files_response = null;
  1170. // 判断是否有要上传的文件,如果有上传文件,否则组合参数进行对话
  1171. if (file_total !== 0) {
  1172. try {
  1173. const uploadResult = await uploadNewChatInfo(1, ask_files);
  1174. files_response = uploadResult.map((item) => {
  1175. return {
  1176. ...item,
  1177. url: '',
  1178. type: getFileType(item),
  1179. transfer_method: 'local_file',
  1180. upload_file_id: item.id
  1181. };
  1182. });
  1183. } catch (e) {
  1184. handleChatRequestionException({
  1185. message: e.message
  1186. });
  1187. }
  1188. }
  1189. // 组合参数
  1190. const query_params = {
  1191. query: ask_text,
  1192. files: files_response,
  1193. session_id: props.session_id || '',
  1194. knowledgeId: action_config.knowledgeId,
  1195. chatMode: action_config.chatMode,
  1196. isDeep: action_config.isDeep,
  1197. parentId: parent_id
  1198. };
  1199. startStream(query_params, sseOnMessage);
  1200. return;
  1201. };
  1202. /**
  1203. * 网页数据
  1204. * */
  1205. const search_list = ref([]);
  1206. const toSearchPage = (item) => {
  1207. search_list.value = [...item.client_custom_item_internet_list];
  1208. minMap_messageId.value = item.client_custom_chat_message_id;
  1209. if (!is_show_right_page.value) {
  1210. is_show_right_page.value = true;
  1211. // if (!mindMap_content.value) {
  1212. // mindMap_err.value = '暂无内容';
  1213. // }
  1214. toShowMindMap(item);
  1215. }
  1216. if (!show_web_page.value) {
  1217. show_web_page.value = true;
  1218. }
  1219. };
  1220. const toShowMindMap = async (item) => {
  1221. const res = await getHistoryMapData(item.client_custom_chat_message_id);
  1222. if (res.code / 1 === 200) {
  1223. if (res.data.mindmap) {
  1224. mindMap_content.value = res.data.mindmap;
  1225. mindMap_err.value = '';
  1226. } else {
  1227. (mindMap_content.value = ''), (mindMap_err.value = '暂无内容');
  1228. }
  1229. } else {
  1230. (mindMap_content.value = ''), (mindMap_err.value = '暂无内容');
  1231. }
  1232. is_show_right_page.value = true;
  1233. minMap_messageId.value = item.client_custom_chat_message_id;
  1234. setTimeout(() => {
  1235. show_mind_map.value = true;
  1236. }, 200);
  1237. };
  1238. /**
  1239. * sseOnMessage处理函数
  1240. * */
  1241. const sseOnMessage = (ev) => {
  1242. // return;
  1243. // sse流返回的数据
  1244. const res_data = JSON.parse(ev.data);
  1245. if (res_data.session_id) {
  1246. emit('updateSessionId', res_data.session_id);
  1247. }
  1248. if (res_data.message_id) {
  1249. minMap_messageId.value = res_data.message_id;
  1250. }
  1251. //sse流式结束
  1252. if (res_data.event === 'message_end') {
  1253. stopStream();
  1254. if (props.agent_id) {
  1255. // return;
  1256. } else {
  1257. is_show_right_page.value = true;
  1258. loading_mind_map.value = true;
  1259. getMindMapData({ messageId: minMap_messageId.value, query: mindMap_txt.value })
  1260. .then((res) => {
  1261. if (res.code / 1 === 200) {
  1262. mindMap_str.value = res.data.mindmap;
  1263. mindMap_content.value = res.data.mindmap;
  1264. mindMap_err.value = '';
  1265. if (!res.data.mindmap) {
  1266. mindMap_err.value = '文本内容太少,不足以生成思维导图,请重新尝试';
  1267. }
  1268. } else {
  1269. mindMap_err.value = '文本内容太少,不足以生成思维导图,请重新尝试';
  1270. }
  1271. })
  1272. .catch((e) => {
  1273. mindMap_err.value = '文本内容太少,不足以生成思维导图,请重新尝试';
  1274. })
  1275. .finally(() => {
  1276. loading_mind_map.value = false;
  1277. setTimeout(() => {
  1278. show_mind_map.value = true;
  1279. }, 200);
  1280. });
  1281. }
  1282. }
  1283. if (res_data.event === 'node_finished' && res_data.data.title && res_data.data.title === 'SearXNG 搜索') {
  1284. if (res_data.data.outputs?.json) {
  1285. search_list.value = res_data.data.outputs.json;
  1286. if (search_list.value.length) {
  1287. show_web_page.value = true;
  1288. }
  1289. }
  1290. }
  1291. if (res_data.event === 'error' || res_data.message === 'error') {
  1292. //sse错误,显示错误信息
  1293. handleChatRequestionException({
  1294. message: res_data.error
  1295. });
  1296. return;
  1297. }
  1298. // TODO: 将返回数据填充到页面
  1299. // 获取当前对话中最后一个对话数据
  1300. // 最后一个对话数据为当前正在进行的对话
  1301. const last_msg = chat_new_list.value[chat_new_list.value.length - 1];
  1302. /**
  1303. * 要混入的回答参数
  1304. * client_custom_item_think: false, // 是否有思考
  1305. * client_custom_item_think_content: '', //思考内容
  1306. * client_custom_item_internet: false, //是否联网
  1307. * client_custom_item_internet_list: [], //联网内容
  1308. * client_custom_chat_message_id: '' //会话的message_id
  1309. *
  1310. */
  1311. if (Object.keys(target_action_config.value)) {
  1312. res_data.isDeep = target_action_config.value.isDeep / 1 === 2 ? true : false;
  1313. res_data.isInternet = target_action_config.value.chatMode / 1 === 2 ? true : false;
  1314. }
  1315. /**
  1316. * 整理返回的数据
  1317. *
  1318. */
  1319. const currentChat = formatDialogAnswerFromSseReceiveMessage(res_data, last_msg, props.CHAT_AGENT_INFOMATION);
  1320. const chat_status = currentChat.status;
  1321. // 处理对话数据
  1322. const handleChatData = () => {
  1323. const chat_data = currentChat.data; // 对话相关的数据项
  1324. // 原来的最后一项弹出
  1325. chat_new_list.value.pop();
  1326. // 添加新的最后一项
  1327. chat_new_list.value.push(chat_data);
  1328. // 滚动到底部
  1329. scroll2ListBottom();
  1330. };
  1331. if (chat_status === 0) {
  1332. // 未开始: 此状态不存在,无需处理
  1333. }
  1334. if (chat_status === 1) {
  1335. // 此轮对话仍然在进行中
  1336. handleChatData();
  1337. show_stop_sse.value = true;
  1338. }
  1339. if (chat_status === 2) {
  1340. // 此轮对话已结束
  1341. handleChatData();
  1342. // 对话已结束:检查是否存在推荐数据
  1343. const chat_suggest = currentChat.suggest; // 对话相关的推荐数据
  1344. if (chat_suggest) {
  1345. chat_suggestion.value = chat_suggest;
  1346. }
  1347. // 对话已结束:文件上传、文本输入区域放开使用
  1348. input_disabled.value = false;
  1349. show_stop_sse.value = false;
  1350. }
  1351. if (chat_status === 3) {
  1352. // 此轮对话出错了
  1353. // TODO: 错误处理
  1354. // 此轮对话已结束
  1355. handleChatData();
  1356. // 对话已结束:文件上传、文本输入区域放开使用
  1357. input_disabled.value = false;
  1358. show_stop_sse.value = false;
  1359. }
  1360. };
  1361. // 停止响应
  1362. const stopResponse = () => {
  1363. stopStream();
  1364. show_stop_sse.value = false;
  1365. input_disabled.value = false;
  1366. // 停止后修改最后一项的状态为已结束
  1367. const lastItem = chat_new_list.value.pop();
  1368. lastItem.client_custom_chat_status = 2;
  1369. chat_new_list.value.push(lastItem);
  1370. scroll2ListBottom();
  1371. };
  1372. watch(
  1373. () => props.agent_id,
  1374. () => {
  1375. if (props.agent_id) {
  1376. show_welcome.value = false;
  1377. }
  1378. }
  1379. );
  1380. watch(
  1381. () => props.session_id,
  1382. () => {
  1383. if (props.session_id) {
  1384. /**
  1385. * 当页面中有记录,则不调用历史记录
  1386. * 防止在新会话中每次问答后更新session_id去调用历史记录,
  1387. * */
  1388. if (chat_display_list.value.length) {
  1389. return;
  1390. }
  1391. // 有历史记录:获取历史记录内容进行展示
  1392. getHistoryLogs(props.session_id)
  1393. .then(async (res) => {
  1394. if (res.code / 1 === 200) {
  1395. const res_data = res.data;
  1396. // 成功
  1397. const session_list = res_data.message;
  1398. // for (let key in session_list) {
  1399. // if (session_list[key].role == 'assistant') {
  1400. // const res = await getHistoryMapData(session_list[key].id);
  1401. // if (res.code / 1 === 200) {
  1402. // session_list[key].mindMap = res.data.mindmap;
  1403. // } else {
  1404. // session_list[key].mindMap = '';
  1405. // }
  1406. // }
  1407. // }
  1408. // 将列表数据进行转化
  1409. const list = formatDialogHistory(session_list, props.CHAT_AGENT_INFOMATION);
  1410. // 如果历史记录不为空
  1411. // 则需要展示对话内容
  1412. // 此时欢迎内容就不需要展示
  1413. if (list.length !== 0) {
  1414. show_welcome.value = false;
  1415. // 滚动到底部
  1416. scroll2ListBottom();
  1417. }
  1418. chat_log_list.value = list;
  1419. } else {
  1420. // 异常
  1421. // TODO:处理异常
  1422. show_welcome.value = false;
  1423. // handleChatRequestionException({
  1424. // message: res.msg
  1425. // });
  1426. // 使用消息提示,而非界面错误提示
  1427. handleErrorMessage('历史会话异常:' + res.msg);
  1428. }
  1429. })
  1430. .catch((err) => {
  1431. console.log('err-mfsjakfjsdkalfjs', err);
  1432. show_welcome.value = false;
  1433. // handleChatRequestionException({
  1434. // message: err.message
  1435. // });
  1436. handleErrorMessage('历史会话错误:' + err.message);
  1437. });
  1438. } else {
  1439. // 无历史记录:等待创建会话内容(用户开始输入、提交完成),列表区域显示欢迎界面
  1440. }
  1441. }
  1442. );
  1443. const getHistoryMapData = async (message_id) => {
  1444. const res = await getMapById(message_id);
  1445. return res;
  1446. };
  1447. onMounted(() => {
  1448. const agent_id = props.agent_id;
  1449. const session_id = props.session_id;
  1450. /**
  1451. * 如果选择智能体,则不显示欢迎页面
  1452. *
  1453. */
  1454. if (props.chat_history) {
  1455. show_welcome.value = false;
  1456. console.log(11111111111111111111);
  1457. } else {
  1458. if (!agent_id && !session_id) {
  1459. show_welcome.value = true;
  1460. } else {
  1461. show_welcome.value = false;
  1462. }
  1463. }
  1464. // 组件挂载
  1465. if (session_id) {
  1466. // 有历史记录:获取历史记录内容进行展示
  1467. getHistoryLogs(session_id)
  1468. .then(async (res) => {
  1469. if (res.code / 1 === 200) {
  1470. const res_data = res.data;
  1471. // 成功
  1472. const session_list = res_data.message;
  1473. // for (let key in session_list) {
  1474. // if (session_list[key].role == 'assistant') {
  1475. // const res = await getHistoryMapData(session_list[key].id);
  1476. // if (res.code / 1 === 200) {
  1477. // session_list[key].mindMap = res.data.mindMap;
  1478. // } else {
  1479. // session_list[key].mindMap = '';
  1480. // }
  1481. // }
  1482. // }
  1483. // 将列表数据进行转化
  1484. const list = formatDialogHistory(session_list, props.CHAT_AGENT_INFOMATION);
  1485. // 如果历史记录不为空
  1486. // 则需要展示对话内容
  1487. // 此时欢迎内容就不需要展示
  1488. if (list.length !== 0) {
  1489. show_welcome.value = false;
  1490. // 滚动到底部
  1491. scroll2ListBottom();
  1492. }
  1493. chat_log_list.value = list;
  1494. } else {
  1495. // 异常
  1496. // TODO:处理异常
  1497. show_welcome.value = false;
  1498. // handleChatRequestionException({
  1499. // message: res.msg
  1500. // });
  1501. // 使用消息提示,而非界面错误提示
  1502. handleErrorMessage('历史会话异常:' + res.msg);
  1503. }
  1504. })
  1505. .catch((err) => {
  1506. console.log('err-mfsjakfjsdkalfjs', err);
  1507. show_welcome.value = false;
  1508. // handleChatRequestionException({
  1509. // message: err.message
  1510. // });
  1511. handleErrorMessage('历史会话错误:' + err.message);
  1512. });
  1513. } else {
  1514. // 无历史记录:等待创建会话内容(用户开始输入、提交完成),列表区域显示欢迎界面
  1515. }
  1516. });
  1517. // ---------------------------------------------------------
  1518. onUnmounted(() => {
  1519. // 组件卸载,关闭组件内的websocket连接
  1520. // closeWSConnect();
  1521. });
  1522. defineExpose({
  1523. toShowWelcome,
  1524. toNewChat
  1525. });
  1526. </script>
  1527. <style lang="css" scoped>
  1528. .conversation-chat-container {
  1529. position: relative;
  1530. display: flex;
  1531. justify-content: center;
  1532. margin-left: auto;
  1533. margin-right: auto;
  1534. height: calc(100% - 52px);
  1535. padding-top: 52px;
  1536. }
  1537. .conversation-chat-left {
  1538. width: 25%;
  1539. flex-shrink: 0;
  1540. }
  1541. .conversation-chat-right {
  1542. width: 25%;
  1543. flex-shrink: 0;
  1544. }
  1545. .conversation-chat-main {
  1546. flex-grow: 1;
  1547. width: 100%;
  1548. height: 100%;
  1549. /* max-width: 960px; */
  1550. margin-left: auto;
  1551. margin-right: auto;
  1552. display: flex;
  1553. /* flex-direction: column; */
  1554. flex-direction: row;
  1555. /* gap: 5%; */
  1556. /* background-color: #aaa; */
  1557. }
  1558. .conversation-chat-main-left {
  1559. /* flex-grow: 1; */
  1560. /* flex-basis: 10%; */
  1561. width: 10%;
  1562. flex-direction: column;
  1563. overflow: hidden;
  1564. height: 100%;
  1565. }
  1566. .conversation-chat-main-content {
  1567. /* flex-grow: 5; */
  1568. /* flex-basis: 50%; */
  1569. flex-direction: column;
  1570. overflow: hidden;
  1571. margin: 0 auto;
  1572. height: 100%;
  1573. }
  1574. .conversation-chat-main-right {
  1575. /* flex-grow: 4; */
  1576. /* flex-basis: 40%; */
  1577. flex-direction: column;
  1578. overflow: hidden;
  1579. height: 100%;
  1580. text-align: right;
  1581. }
  1582. .conversation-chat-expand {
  1583. text-align: right;
  1584. height: 100%;
  1585. width: 100%;
  1586. }
  1587. .conversation-chat-mind-map {
  1588. position: relative;
  1589. /* width: 100%; */
  1590. height: 330px;
  1591. padding: 5px;
  1592. border-radius: 12px;
  1593. background-color: var(--color-bg-1);
  1594. }
  1595. .conversation-chat-mind-map-spin {
  1596. position: absolute;
  1597. left: 0;
  1598. top: 0;
  1599. width: 100%;
  1600. height: 330px;
  1601. padding: 5px;
  1602. border-radius: 12px;
  1603. background-color: var(--color-bg-1);
  1604. display: flex;
  1605. align-items: center;
  1606. justify-content: center;
  1607. }
  1608. .mind_map_error {
  1609. position: absolute;
  1610. left: 0;
  1611. top: 0;
  1612. width: 100%;
  1613. height: 330px;
  1614. display: flex;
  1615. align-items: center;
  1616. justify-content: center;
  1617. background-color: var(--color-bg-1);
  1618. }
  1619. .exit-button {
  1620. position: absolute;
  1621. right: 10px;
  1622. top: 10px;
  1623. border: none;
  1624. cursor: pointer;
  1625. z-index: 9999;
  1626. }
  1627. .conversation-chat-mind-map-tools {
  1628. text-align: right;
  1629. }
  1630. .conversation-chat-web-page {
  1631. margin-top: 20px;
  1632. height: calc(100% - 380px);
  1633. padding: 10px;
  1634. border-radius: 12px;
  1635. background-color: var(--color-bg-1);
  1636. }
  1637. .conversation-chat-content {
  1638. flex-grow: 1;
  1639. height: calc(100% - 60px);
  1640. display: flex;
  1641. flex-direction: column;
  1642. overflow: hidden;
  1643. padding-top: 8px;
  1644. }
  1645. .conversation-chat-main-container {
  1646. height: 80%;
  1647. /* flex-grow: 1; */
  1648. }
  1649. .conversation-chat-area {
  1650. flex-grow: 1;
  1651. overflow: auto;
  1652. /* max-width: 960px; */
  1653. }
  1654. .history-list-loading {
  1655. display: flex;
  1656. justify-content: space-between;
  1657. align-items: center;
  1658. min-height: 300px;
  1659. width: 100%;
  1660. }
  1661. .history-list-loading-container {
  1662. display: flex;
  1663. justify-content: center;
  1664. align-items: center;
  1665. }
  1666. .conversation-input-attached {
  1667. flex-shrink: 0;
  1668. width: 100%;
  1669. margin-left: auto;
  1670. margin-right: auto;
  1671. /* margin-top: 5px; */
  1672. margin-bottom: -10px;
  1673. padding: 5px;
  1674. border-radius: 10px;
  1675. display: flex;
  1676. }
  1677. .conversation-chat-input {
  1678. flex-shrink: 0;
  1679. width: 100%;
  1680. margin-left: auto;
  1681. margin-right: auto;
  1682. margin-top: 5px;
  1683. margin-bottom: 5px;
  1684. padding: 5px;
  1685. border-radius: 10px;
  1686. /* display: flex; */
  1687. }
  1688. /* .scroll-left {
  1689. width: 15%;
  1690. flex-shrink: 0;
  1691. }
  1692. .scroll-right {
  1693. width: 15%;
  1694. flex-shrink: 0;
  1695. } */
  1696. </style>