yangyin
2024-10-31 46a5e875efefb071a654bdf14da0e662347a986b
src/components/chat/Chat.vue
@@ -1,14 +1,19 @@
<template>
   <div class="flex h-full">
      <div class="flex flex-col h-full flex-auto">
         <div class="h-full flex flex-col items-center overflow-y-auto">
            <div ref="chatListDom" class="h-full" v-loading="chatListLoading" :style="{ width: chatWidth }">
         <div ref="chatListDom" class="relative h-full flex flex-col items-center overflow-y-auto">
            <span
               class="more-loading absolute text-blue-400 left-[50%] translate-x-[-50%] cursor-pointer w-10"
               v-loading="moreIsLoading"
            ></span>
            <div class="h-full relative" v-loading="chatListLoading" :style="{ width: chatWidth }">
               <div
                  class="group flex px-4 py-6 hover:bg-slate-100 rounded-lg relative"
                  :class="{ 'flex-row-reverse': item.role === RoleEnum.user }"
                  v-for="(item, index) of computedMessageList"
                  :key="index"
                  :key="`${item.historyId}_${item.role}`"
               >
                  <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ item?.createTime }}</div>
                  <img
                     class="rounded-full size-12 flex-0"
                     :class="{ 'mr-4': item.role === RoleEnum.assistant, 'ml-4': item.role === RoleEnum.user }"
@@ -123,7 +128,7 @@
                     </div>
                  </div>
               </div>
               <div v-if="showAskMore" class="ml-4 mt-5 text-sm">
               <div v-if="showAskMore" class="ml-4 mt-5 text-sm pb-10">
                  <div class="text-gray-600 mb-5">你可以继续问我:</div>
                  <div class="space-y-2 inline-flex flex-col">
                     <div
@@ -139,7 +144,7 @@
            </div>
         </div>
         <div class="sticky bottom-0 w-full p-6 pb-8 bg-[rgb(247,248,250)] flex justify-center">
         <div class="sticky bottom-0 w-full p-6 bg-[rgb(247,248,250)] flex justify-center">
            <PlayBar
               v-model:voicePageIsShow="voicePageIsShow"
               :isTalking="isTalking"
@@ -163,14 +168,24 @@
import Loding from './components/Loding.vue';
import { useAssistantContentOpt } from './hooks/useAssistantContentOpt';
import { useQueryProcess } from './hooks/useQueryProcess';
import { useScrollLoad } from './hooks/useScrollLoad';
import { useScrollToBottom } from './hooks/useScrollToBottom';
import type { ChatContent } from './model/types';
import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types';
import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, extCallQuery } from '/@/api/ai/chat';
import { QuestionAi, extCallQuery } from '/@/api/ai/chat';
import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
import router from '/@/router';
import { activeChatRoom, activeLLMId, activeSampleId, activeSectionAId, getRoomConfig, roomConfig } from '/@/stores/chatRoom';
import {
   activeChatRoom,
   activeGroupType,
   activeLLMId,
   activeRoomId,
   activeSampleId,
   activeSectionAId,
   getRoomConfig,
   roomConfig,
} from '/@/stores/chatRoom';
import { ErrorCode } from '/@/utils/request';
const chatWidth = '75%';
@@ -182,12 +197,12 @@
});
const currentRoute = router.currentRoute;
const currentRouteId = currentRoute.value.query.id as string;
activeRoomId.value = currentRouteId;
const chatListDom = ref<HTMLDivElement>();
const messageList = ref<ChatMessage[]>([]);
const computedMessageList = computed(() => {
   return messageList.value.filter((v) => !!v);
});
const parseContent = (res) => {
   if (!res) return null;
   let content: ChatContent = {
@@ -247,10 +262,8 @@
   content.origin = res;
   return content;
};
const { clearQueryProcess, process, processId, queryProcess } = useQueryProcess();
const DEFAULT_SECTION_A_ID = 'knowledge_base';
let questionRes = null;
let finalCalcSectionAId = null;
@@ -289,11 +302,15 @@
      process_id: processId.value,
      question: text,
      // FIXME: 暂时这样
      section_a_id: currentSectionAId,
      // section_a_id: currentSectionAId,
      history_group_id: currentRouteId,
      raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false,
      ...judgeParams,
   } as any;
   if (activeGroupType.value) {
      params.group_type = activeGroupType.value;
   }
   if (currentSampleId) {
      params.sample_id = currentSampleId;
@@ -323,14 +340,8 @@
let currentLLMId = null;
const getAnswerById = async (historyId: string) => {
   return await GetHistoryAnswer({
      history_id: historyId,
   });
};
const sendChatMessage = async (content: ChatContent = messageContent.value, cb?: any, isCallExtParams?: any) => {
   if (!content?.values || isTalking.value ||chatListLoading.value) return;
   if (!content?.values || isTalking.value || chatListLoading.value) return;
   const isNewChat = messageList.value.length === 0;
   if (isNewChat) {
      if (activeSampleId.value) {
@@ -364,7 +375,7 @@
      } else {
         resMsgContent = await questionAi(content.values);
      }
      nextUserMsgEndIndex.value++;
      if (isNewChat) {
         const firstResCb = getRoomConfig(currentRouteId, 'firstResCb');
         firstResCb?.(resMsgContent);
@@ -376,8 +387,10 @@
      assistantItem.historyId = questionRes.history_id;
      assistantItem.sectionAId = finalCalcSectionAId;
      appendLastMessageContent(resMsgContent);
      // 收到恢复,继续滚
      scrollToBottom();
      setTimeout(() => {
         // 收到回复,继续滚
         scrollToBottom();
      }, 300);
   } catch (error: any) {
      // appendLastMessageContent({
      //    type: AnswerType.Text,
@@ -396,69 +409,28 @@
      messageList.value.at(-1).content = content;
   }
};
const { loadRangeData, onChatListScroll, moreIsLoading, nextUserMsgEndIndex } = useScrollLoad({
   container: chatListDom,
   historyGroupId: currentRouteId,
   messageList,
   parseAnswerContent: parseContent,
});
// 一次性加载最近条数限制
const LOAD_CHAT_LIMIT = 10;
// 所有用户提问历史记录
let userMsgHistory = [];
// 下次加载用户提问索引位置
let nextUserMsgEndIndex = 0;
const chatListLoading = ref(false);
const { scrollToBottom } = useScrollToBottom({
   chatListDom: chatListDom,
});
onMounted(async () => {
   const res = await QueryHistoryDetail({
      history_group_id: currentRouteId,
   });
   userMsgHistory = res.details ?? [];
   nextUserMsgEndIndex = userMsgHistory.length;
   // 截取倒数 LOAD_CHAT_LIMIT 条用户消息
   const currentUserMsgList = userMsgHistory.slice(nextUserMsgEndIndex - LOAD_CHAT_LIMIT, nextUserMsgEndIndex);
   messageList.value = [];
   // 加载初始数据
   chatListLoading.value = true;
   const tmpMessageList = currentUserMsgList.map((item) => {
      return {
         historyId: item.history_id,
         role: RoleEnum.user,
         content: {
            type: AnswerType.Text,
            values: item.question,
         },
      } as ChatMessage;
   });
   const sectionAIdMap = new Map();
   try {
      chatListLoading.value = true;
      const resList = await Promise.all(
         (currentUserMsgList ?? []).map((item) => {
            sectionAIdMap.set(item.history_id, item.section_a_id);
            return getAnswerById(item.history_id);
         })
      );
      let i = 0;
      resList.map((item, index) => {
         const insertIndex = index + 1 + i;
         const userMsg = tmpMessageList[insertIndex - 1];
         userMsg.content.values = item?.answer?.question ?? userMsg.content.values;
         tmpMessageList.splice(
            insertIndex,
            0,
            item.answer === null
               ? null
               : {
                     historyId: item.answer?.history_id,
                     role: RoleEnum.assistant,
                     content: parseContent(item.answer),
                     state: item.answer_state,
                     sectionAId: sectionAIdMap.get(item.answer.history_id),
                 }
         );
         i++;
      });
   } finally {
   await loadRangeData().finally(() => {
      chatListLoading.value = false;
   }
   messageList.value = tmpMessageList;
   if (tmpMessageList.length === 0) {
   });
   if (messageList.value.length === 0) {
      messageContent.value = {
         type: AnswerType.Text,
         values: activeChatRoom.value.title,
@@ -469,14 +441,13 @@
      setTimeout(() => {
         // 初始状态滚一下
         scrollToBottom();
         setTimeout(() => {
            chatListDom.value.addEventListener('scroll', onChatListScroll);
         }, 300);
      }, 300);
   }
});
const { scrollToBottom } = useScrollToBottom({
   chatListDom: chatListDom,
});
//#region ====================== 关联查询 ======================
const relativeQueryClick = async (val) => {
   sendChatMessage(
@@ -524,10 +495,21 @@
//#endregion
</script>
<style scoped>
<style scoped lang="scss">
pre {
   font-family: -apple-system, 'Noto Sans', 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial, 'Liberation Sans', 'PingFang SC',
      'Hiragino Sans GB', 'Noto Sans CJK SC', 'Source Han Sans SC', 'Source Han Sans CN', 'Microsoft YaHei', 'Wenquanyi Micro Hei',
      'WenQuanYi Zen Hei', 'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;
}
.more-loading {
   :deep(.el-loading-spinner) {
      --loading-size: 35px;
      margin-top: 0;
      .circular {
         width: var(--loading-size);
         height: var(--loading-size);
      }
   }
}
</style>