yangyin
2024-10-14 32e433e28a3b779874f2317b80c4cef81957ff43
src/components/chat/Chat.vue
@@ -1,13 +1,17 @@
<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" :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" 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}`"
               >
                  <img
                     class="rounded-full size-12 flex-0"
@@ -156,7 +160,6 @@
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus';
import _ from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { computed, onMounted, ref } from 'vue';
@@ -164,10 +167,11 @@
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 { GetHistoryAnswer, 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';
@@ -324,14 +328,9 @@
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) return;
   if (!content?.values || isTalking.value || chatListLoading.value) return;
   const isNewChat = messageList.value.length === 0;
   if (isNewChat) {
      if (activeSampleId.value) {
@@ -355,6 +354,9 @@
      // 出现回复,置空出现等待动画
      messageList.value.push(assistantItem);
      // 滚动至当前发送消息
      scrollToBottom();
      if (isCallExtParams) {
         const extRes = await extCallQuery(isCallExtParams);
         questionRes = extRes;
@@ -362,7 +364,7 @@
      } else {
         resMsgContent = await questionAi(content.values);
      }
      nextUserMsgEndIndex.value++;
      if (isNewChat) {
         const firstResCb = getRoomConfig(currentRouteId, 'firstResCb');
         firstResCb?.(resMsgContent);
@@ -374,6 +376,10 @@
      assistantItem.historyId = questionRes.history_id;
      assistantItem.sectionAId = finalCalcSectionAId;
      appendLastMessageContent(resMsgContent);
      setTimeout(() => {
         // 收到回复,继续滚
         scrollToBottom();
      }, 300);
   } catch (error: any) {
      // appendLastMessageContent({
      //    type: AnswerType.Text,
@@ -392,60 +398,27 @@
      messageList.value.at(-1).content = content;
   }
};
const { loadRangeData, onChatListScroll, moreIsLoading, nextUserMsgEndIndex } = useScrollLoad({
   container: chatListDom,
   historyGroupId: currentRouteId,
   messageList,
   parseAnswerContent: parseContent,
});
// 一次性加载最近条数限制
const LOAD_CHAT_LIMIT = 10;
const chatListLoading = ref(false);
// 所有用户提问历史记录
let userMsgHistory = [];
// 下次加载用户提问索引位置
let nextUserMsgEndIndex = 0;
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 = currentUserMsgList.map((item) => {
      return {
         historyId: item.history_id,
         role: RoleEnum.user,
         content: {
            type: AnswerType.Text,
            values: item.question,
         },
      } as ChatMessage;
   });
   const sectionAIdMap = new Map();
   messageList.value = [];
   // 加载初始数据
   chatListLoading.value = true;
   // 获取结果插入到用户提问之后
   currentUserMsgList.map((item) => {
      sectionAIdMap.set(item.history_id, item.section_a_id);
      getAnswerById(item.history_id).then((aiRobot) => {
         // 用户提问索引
         const userMsgIndex = messageList.value.findIndex((subItem) => subItem?.historyId === item.history_id);
         const userMsg = messageList.value[userMsgIndex];
         // values 取回答之后最终的 question
         userMsg.content.values = aiRobot.answer?.question ?? userMsg.content.values;
         messageList.value.splice(
            userMsgIndex + 1,
            0,
            aiRobot.answer === null
               ? null
               : {
                     historyId: aiRobot.answer?.history_id,
                     role: RoleEnum.assistant,
                     content: parseContent(aiRobot.answer),
                     state: aiRobot.answer_state,
                     sectionAId: sectionAIdMap.get(aiRobot.answer.history_id),
                 }
         );
      });
   await loadRangeData().finally(() => {
      chatListLoading.value = false;
   });
   if (messageList.value.length === 0) {
      messageContent.value = {
         type: AnswerType.Text,
@@ -453,14 +426,17 @@
      };
      sendChatMessage();
   } else {
      setTimeout(() => {
         // 初始状态滚一下
         scrollToBottom();
         setTimeout(() => {
            chatListDom.value.addEventListener('scroll', onChatListScroll);
         }, 300);
      }, 300);
   }
});
const { forbidScroll } = useScrollToBottom({
   chatListDom: chatListDom,
   displayMessageList: computedMessageList,
});
//#region ====================== 关联查询 ======================
const relativeQueryClick = async (val) => {
   sendChatMessage(
@@ -495,7 +471,6 @@
   showFixQuestion,
   showAskMore,
} = useAssistantContentOpt({
   forbidScroll,
   sendChatMessage,
   displayMessageList: computedMessageList,
});
@@ -509,10 +484,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>