wujingjing
2025-04-09 dd58c1d3a27ba48a5df050aab7c586bb9b988914
src/components/chat/Chat.vue
@@ -48,23 +48,25 @@
<script setup lang="ts">
import type { CancelTokenSource } from 'axios';
import axios from 'axios';
import { orderBy } from 'lodash-es';
import { ElMessage } from 'element-plus';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { computed, nextTick, onActivated, onMounted, ref } from 'vue';
import { loadAmisSource } from '../amis/load';
import ChatContainer from './components/ChatContainer.vue';
import ShareLinkDlg from './components/shareLink/index.vue';
import type { SendMsg } from './hooks/types';
import { useLoadData } from './hooks/useLoadData';
import { useScrollLoad } from './hooks/useScrollLoad';
import { useSyncMsg } from './hooks/useSyncMsg';
import MessageList from './messageList/index.vue';
import type { ChatContent } from './model/types';
import { AnswerState, AnswerType, RoleEnum, type ChatMessage } from './model/types';
import { getShareChatJsonByPost, questionStreamByPost } from '/@/api/ai/chat';
import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
import { Logger } from '/@/model/logger/Logger';
import { triggerRef } from 'vue';
import { ElLoadingService, ElMessage } from 'element-plus';
import ChatContainer from './components/ChatContainer.vue';
import ShareLinkDlg from './components/shareLink/index.vue';
import router from '/@/router';
import MessageList from './messageList/index.vue';
import {
   activeChatRoom,
   activeGroupType,
@@ -74,18 +76,28 @@
   isSharePage,
   roomConfig,
} from '/@/stores/chatRoom';
import { ParentRegister } from '/@/stores/global';
import emitter from '/@/utils/mitt';
import { deepClone } from '/@/utils/other';
import { useCompRef } from '/@/utils/types';
import { toMyFixed } from '/@/utils/util';
import { useLoadData } from './hooks/useLoadData';
import { useSyncMsg } from './hooks/useSyncMsg';
import { getCurrentPosition } from '/@/utils/brower';
import { toFormData, toMyFixed } from '/@/utils/util';
const containerRef = useCompRef(ChatContainer);
const chatListDom = computed(() => containerRef.value?.chatListDom);
const scrollToBottom = () => {
   containerRef.value?.scrollToBottom();
};
const { loadReplyData, parseContent, parseExtraContent, convertProcessItem, convertProcessToStep, formatShowTimeYear, getStepGroupList } = useLoadData();
const {
   loadReplyData,
   parseContent,
   parseExtraContent,
   convertProcessItem,
   convertProcessToStep,
   convertAttach,
   formatShowTimeYear,
   getStepGroupList,
} = useLoadData();
const voicePageIsShow = ref(false);
let isTalking = ref(false);
const chatWidth = computed(() => containerRef.value?.chatWidth);
@@ -115,6 +127,8 @@
   });
};
const enableCallback = ref(false);
let streamOutputIsStart = false;
let position: Position = null;
const questionAi = async (text) => {
@@ -133,7 +147,15 @@
      raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false,
      ...judgeParams,
   } as any;
   const tableList = attachList.value.filter((item) => item.type === 'table').map((item) => item.model);
   if (tableList?.length > 0) {
      params.tables = JSON.stringify(tableList);
   }
   const metricList = attachList.value.filter((item) => item.type === 'metric').map((item) => item.model);
   if (metricList?.length > 0) {
      params.metrics = JSON.stringify(metricList);
   }
   // if (!position) {
   //    const loadingInstance = ElLoadingService({
   //       text: '获取位置中...',
@@ -158,7 +180,12 @@
      params.sample_id = currentSampleId;
      currentSampleId = '';
   }
   const formDataParams = toFormData(params);
   const fileList = attachList.value.filter((item) => item.type === 'file').map((item) => item.model);
   for (const item of fileList) {
      formDataParams.append('files', item.file);
   }
   // clearAttach();
   let lastTimestamp = new Date().getTime();
   questionRes = {};
   let lastIsResult = false;
@@ -178,7 +205,7 @@
         return isEmpty;
      };
      questionStreamByPost(
         params,
         formDataParams,
         (chunkRes) => {
            Logger.info('chunk response:\n\n' + JSON.stringify(chunkRes));
            if (chunkRes.mode === 'result') {
@@ -207,6 +234,26 @@
               triggerRefresh();
               return;
               // chunkRes.value = '准备数据分析';
            }
            if (chunkRes.mode === 'main_frame') {
               const jsonObj = JSON.parse(chunkRes.value);
               if (!enableCallback.value) {
                  return;
               }
               ParentRegister.notify?.({
                  type: 'main_frame',
                  value: jsonObj,
               });
               return;
            }
            if (chunkRes.mode === 'create_work_order') {
               const lastMsg = computedMessageList.value.at(-1);
               lastMsg.modeContent = chunkRes;
               triggerRefresh();
               return;
            }
            if (chunkRes.mode === 'summary') {
@@ -258,10 +305,11 @@
            if (chunkRes.mode === 'conclusion') {
               const lastReport = computedMessageList.value.at(-1)?.content?.values?.at(-1);
               if (lastReport) {
                  lastReport.conclusion = chunkRes.value;
                  chunkRes.value = '分析结束';
               }
               chunkRes.value = '分析结束';
            }
            const getLastGroup = () => {
               const lastGroup = computedMessageList.value.at(-1).stepGroup[0];
@@ -323,7 +371,7 @@
               const ms = toMyFixed(currentTimeStamp - lastTimestamp, 2) + ' ms';
               stepList.at(-1).ms = ms;
            }
            }
            if (!streamOutputIsStart) {
               lastTimestamp = currentTimeStamp;
@@ -333,7 +381,9 @@
               stepList.push(stepItem);
            } else {
               const lastItem = stepList.at(-1);
               lastItem.title += chunkRes.value ?? '';
               if (lastItem) {
                  lastItem.title += chunkRes.value ?? '';
               }
            }
            if (chunkRes.mode === 'begin_stream') {
@@ -368,23 +418,40 @@
   const content = parseContent(questionRes, true);
   return content;
};
const clearMessageContent = () =>
   (messageContent.value = {
const playBarRef = useCompRef(PlayBar);
const attachList = computed(() => playBarRef.value?.attachList ?? []);
const clearMessageContent = () => {
   messageContent.value = {
      type: AnswerType.Text,
      values: '',
   });
   };
};
let currentSampleId = '';
let currentLLMId = null;
const stopGenClick = () => {
const resetTalking = () => {
   lastAxiosSource?.cancel();
   isTalking.value = false;
   chatListLoading.value = false;
   streamOutputIsStart = false;
};
const stopGenClick = () => {
   resetTalking();
   if (isFrontQuestion) {
      ParentRegister.notify?.({
         type: 'msg_stop',
      });
   }
   computedMessageList.value.at(-1).isStopMsg = true;
};
const finishFrontQuestion = () => {
   resetTalking();
   ParentRegister.updateChildCallObj('sendMsg', null);
};
const checkCanSend = (content: ChatContent = messageContent.value) => {
@@ -400,11 +467,12 @@
const addChatItem = (content: ChatContent) => {
   isTalking.value = true;
   const userItem: ChatMessage = { role: RoleEnum.user, content, isChecked: false } as any;
   const userItem: ChatMessage = { role: RoleEnum.user, content, isChecked: false, attachList: deepClone(attachList.value) } as any;
   const assistantItem: ChatMessage = {
      role: RoleEnum.assistant,
      content: {
         type: AnswerType.Report,
         values: [],
      },
      state: AnswerState.Null,
      stepGroup: [
@@ -413,6 +481,7 @@
            isShow: true,
         },
      ],
      isStopMsg: false,
      isChecked: false,
   } as any;
@@ -424,10 +493,64 @@
   return [userItem, assistantItem];
};
/**
 * 清除附件
 */
const clearAttach = () => {
   playBarRef.value?.clearAttach();
};
const updateUserInfo = (userItem: ChatMessage, other: { historyId: string; question: string }) => {
   userItem.historyId = other.historyId;
   const current = moment().format('YYYY-MM-DD HH:mm:ss');
   userItem.createTime = current;
   userItem.content.values = other.question ?? userItem.content.values;
};
const updateAssistantInfo = (
   assistantItem: ChatMessage,
   resMsgContent: ChatContent,
   other: { historyId: string; question: string }
) => {
   const current = moment().format('YYYY-MM-DD HH:mm:ss');
   assistantItem.historyId = other.historyId;
   const currentTime = formatShowTimeYear(current);
   assistantItem.createTime = currentTime;
   assistantItem.content = resMsgContent;
};
const updateInfo = (
   userItem: ChatMessage,
   assistantItem: ChatMessage,
   resMsgContent: ChatContent,
   other: {
      historyId: string;
      question: string;
   }
) => {
   updateUserInfo(userItem, other);
   updateAssistantInfo(assistantItem, resMsgContent, other);
};
const handleAfterQuestion = (
   userItem: ChatMessage,
   assistantItem: ChatMessage,
   resMsgContent: ChatContent,
   other: { historyId: string; question: string }
) => {
   updateLoadIndex();
   updateInfo(userItem, assistantItem, resMsgContent, other);
   setTimeout(() => {
      // 收到回复,继续滚
      scrollToBottom();
   }, 300);
};
const sendChatMessage = async (content: ChatContent = messageContent.value) => {
   if (!checkCanSend(content)) {
      return;
   }
   isFrontQuestion = false;
   const isNewChat = messageList.value.length === 0;
   if (isNewChat) {
      if (activeSampleId.value) {
@@ -443,22 +566,96 @@
   try {
      const [userItem, assistantItem] = addChatItem(content);
      resMsgContent = await questionAi(content.values);
      updateLoadIndex();
      userItem.historyId = questionRes?.history_id;
      const current = moment().format('YYYY-MM-DD HH:mm:ss');
      userItem.createTime = current;
      userItem.content.values = questionRes?.question ?? userItem.content.values;
      assistantItem.historyId = questionRes?.history_id;
      const currentTime = formatShowTimeYear(current);
      assistantItem.createTime = currentTime;
      assistantItem.content = resMsgContent;
      setTimeout(() => {
         // 收到回复,继续滚
         scrollToBottom();
      }, 300);
      handleAfterQuestion(userItem, assistantItem, resMsgContent, {
         historyId: questionRes?.history_id,
         question: questionRes?.question,
      });
   } catch (error: any) {}
};
let isFrontQuestion = false;
const sendFrontChatMessage = async (content: ChatContent = messageContent.value): Promise<any> => {
   isFrontQuestion = true;
   const [userItem, assistantItem] = addChatItem(content);
   const promise = new Promise((resolve, reject) => {
      const receiveMsg = (msg: SendMsg) => {
         console.log('receiveMsg', msg);
         if (!assistantItem.content?.values) {
            assistantItem.content.values = [];
         }
         switch (msg.type) {
            case 'text':
               // 开始增加新的 stepGroup,后续的 stepGroup 并没有实际作用,只是为了做迭代用,迭代出组件,屎山代码实在太难改了!!!
               assistantItem.stepGroup.push({
                  value: [],
                  isShow: true,
               });
               assistantItem.content.values.push({
                  content: {
                     type: 'knowledge',
                     values: [
                        {
                           answer: msg.value,
                        },
                     ],
                  },
               });
               break;
            case 'select':
            case 'confirm':
            case 'input':
               // 开始增加新的 stepGroup,后续的 stepGroup 并没有实际作用,只是为了做迭代用,迭代出组件,屎山代码实在太难改了!!!
               assistantItem.stepGroup.push({
                  value: [],
                  isShow: true,
               });
               assistantItem.content.values.push({
                  content: {
                     type: 'content_cb',
                     values: msg,
                  },
               });
               break;
            case 'info':
               if (msg.value === 'finish') {
                  finishFrontQuestion();
                  return resolve({ userItem, assistantItem, resMsgContent: assistantItem.content });
               }
               break;
            default:
               break;
         }
         scrollToBottom();
         triggerRefresh();
      };
      ParentRegister.updateChildCallObj('sendMsg', receiveMsg);
   });
   return promise;
};
const questionSelf = (content: string) => {
   const myContent = { type: AnswerType.Text, values: content };
   if (!checkCanSend(myContent)) {
      return false;
   }
   sendFrontChatMessage(myContent).then(({ userItem, assistantItem, resMsgContent }) => {
      handleAfterQuestion(userItem, assistantItem, resMsgContent, {
         historyId: uuidv4(),
         question: content,
      });
   });
   return true;
};
const backQuestion = (content: { question: string; data: any }) => {
   sendChatMessage({ type: AnswerType.Text, values: content.question });
};
ParentRegister.updateChildCallObj('frontQuestion', questionSelf);
ParentRegister.updateChildCallObj('backQuestion', backQuestion);
const sendClick = () => {
   sendChatMessage(messageContent.value);
@@ -526,6 +723,7 @@
         type: AnswerType.Text,
         values: msgValue.question,
      },
      attachList: convertAttach(msgValue),
      isChecked: false,
   };
@@ -583,7 +781,6 @@
   messageContent.value.values = content;
};
//#endregion
const playBarRef = useCompRef(PlayBar);
//用户问题设置为常用语
const setCommonPhraseClick = (item) => {
   playBarRef.value.addPhrase(item);