wujingjing
2024-11-07 b006d0fefbcd38f9fe3d95a46519b2e5e6720d5c
src/components/chat/Chat.vue
@@ -10,7 +10,7 @@
               <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"
                  v-for="(item, msgIndex) of computedMessageList"
                  :key="`${item.historyId}_${item.role}`"
               >
                  <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ item?.createTime }}</div>
@@ -49,12 +49,16 @@
                                       :title="subItem.title"
                                       :status="stepEnumMap[subItem.status]"
                                    >
                                       <!-- <template #icon>
                                       <span v-if="subItem.status === StepEnum.Loading" class="ywifont ywicon-loading animate-spin"></span>
                                       <span v-else class="ywifont ywicon-loading1 animate-spin"></span>
                                    </template> -->
                                       <template
                                          #icon
                                          v-if="index + 1 === item.stepList.length && isTalking && msgIndex === computedMessageList.length - 1"
                                       >
                                          <span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
                                       </template>
                                       <template #title>
                                          <span class="text-sm">{{ subItem.title }}</span>
                                          <span class="text-sm"
                                             >{{ subItem.title }}<span v-if="subItem.ms" class="text-green-600">{{ `(${subItem.ms})` }}</span></span
                                          >
                                       </template>
                                    </el-step>
                                 </el-steps>
@@ -82,37 +86,12 @@
                                    </div>
                                 </div>
                                 <template v-else>
                                    <el-popover placement="bottom-start" trigger="hover" :popper-style="{ minWidth: '70px' }" :width="70">
                                       <template #default>
                                          <div class="action" v-if="item.role === RoleEnum.user">
                                             <div class="flex items-center justify-center size-[20px]">
                                                <i
                                                   class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px]"
                                                   @click="copyUserClick(item)"
                                                />
                                             </div>
                                             <div class="flex items-center justify-center size-[20px]">
                                                <i
                                                   class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px]"
                                                   @click="setCommonQuestionClick(item)"
                                                />
                                             </div>
                                          </div>
                                       </template>
                                       <template #reference>
                                          <component
                                             :is="answerTypeMapCom[item.content.type]"
                                             v-if="item.role === RoleEnum.user"
                                             :data="item.content.values"
                                             :originData="item"
                                          />
                                       </template>
                                    </el-popover>
                                    <component
                                       :conclusion="item.conclusion"
                                       :is="answerTypeMapCom[item.content.type]"
                                       v-if="item.role !== RoleEnum.user"
                                       :data="item.content.values"
                                       :originData="item"
                                       :isTalking="isTalking && msgIndex === computedMessageList.length - 1"
                                    />
                                    <div
                                       v-if="item.role === RoleEnum.assistant && item.content.origin?.ext_call_list"
@@ -135,6 +114,23 @@
                           </div>
                           <!-- 操作 -->
                           <div
                              v-if="item.role === RoleEnum.user && item.content?.values"
                              class="absolute flex items-center right-0 mr-4 space-x-2"
                           >
                              <div class="flex items-center justify-center size-[20px]">
                                 <i
                                    class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                                    @click="copyUserClick(item)"
                                 />
                              </div>
                              <div class="flex items-center justify-center size-[20px]">
                                 <i
                                    class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                                    @click="setCommonQuestionClick(item)"
                                 />
                              </div>
                           </div>
                           <div
                              v-if="item.role === RoleEnum.assistant && item.content?.values"
                              class="absolute flex items-center right-0 mr-4 mt-2 space-x-2"
                           >
@@ -148,45 +144,50 @@
                                 />
                              </div>
                              <template v-if="item.content.errCode !== ErrorCode.Message">
                                 <div class="flex items-center justify-center size-[15px]">
                                    <i
                                       :class="{ 'text-[#0284ff]': item.state === AnswerState.Like }"
                                       class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
                                       @click="likeClick(item)"
                                    />
                                 </div>
                                 <div class="flex items-center justify-center size-[15px]">
                                    <i
                                       :class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }"
                                       class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                       @click="unLikeClick(item)"
                                    />
                                 </div>
                                 <el-tooltip effect="dark" content="点赞" placement="top">
                                    <div class="flex items-center justify-center size-[15px]">
                                       <i
                                          :class="{ 'text-[#0284ff]': item.state === AnswerState.Like }"
                                          class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
                                          @click="likeClick(item)"
                                       />
                                    </div>
                                 </el-tooltip>
                                 <el-tooltip effect="dark" content="点踩" placement="top">
                                    <div class="flex items-center justify-center size-[15px]">
                                       <i
                                          :class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }"
                                          class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                          @click="unLikeClick(item)"
                                       />
                                    </div>
                                 </el-tooltip>
                              </template>
                              <div class="flex items-center justify-center size-[15px] relative">
                                 <i
                                    class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                    @click="
                                       ($event) =>
                                          feedbackClick(
                                             $event,
                                             item,
                                             computedMessageList
                                                .filter((v) => v.role === RoleEnum.assistant)
                                                .findIndex((v) => v.historyId === item.historyId)
                                          )
                                    "
                                 />
                                 <FeedbackPanel
                                    v-show="feedbackIsShow && currentFeedbackMapItem === item"
                                    ref="feedbackPanelRef"
                                    v-model:isShow="feedbackIsShow"
                                    v-model:content="feedbackContent"
                                    :chatItem="currentFeedbackMapItem"
                                    :position="feedbackPosition"
                                 />
                              </div>
                              <el-tooltip effect="dark" content="反馈" placement="top">
                                 <div class="flex items-center justify-center size-[15px] relative">
                                    <i
                                       class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                       @click="
                                          ($event) =>
                                             feedbackClick(
                                                $event,
                                                item,
                                                computedMessageList
                                                   .filter((v) => v.role === RoleEnum.assistant)
                                                   .findIndex((v) => v.historyId === item.historyId)
                                             )
                                       "
                                    />
                                    <FeedbackPanel
                                       v-show="feedbackIsShow && currentFeedbackMapItem === item"
                                       ref="feedbackPanelRef"
                                       v-model:isShow="feedbackIsShow"
                                       v-model:content="feedbackContent"
                                       :chatItem="currentFeedbackMapItem"
                                       :position="feedbackPosition"
                                    />
                                 </div>
                              </el-tooltip>
                           </div>
                        </div>
                     </div>
@@ -218,6 +219,7 @@
               @showUpChatClick="showUpChatClick"
               @showDownChatClick="showDownChatClick"
               :style="{ width: chatWidth }"
               :setCommonQuestionInfo="setCommonQuestionInfo"
            ></PlayBar>
         </div>
      </div>
@@ -228,19 +230,18 @@
<script setup lang="ts">
import _ from 'lodash';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { computed, onMounted, ref } from 'vue';
import FeedbackPanel from './components/FeedbackPanel.vue';
import Loding from './components/Loding.vue';
import { useAssistantContentOpt } from './hooks/useAssistantContentOpt';
import { useQueryProcess } from './hooks/useQueryProcess';
import { convertProcessItem, useScrollLoad } from './hooks/useScrollLoad';
import { useScrollToBottom } from './hooks/useScrollToBottom';
import type { ChatContent, StepItem } from './model/types';
import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage, stepEnumMap } from './model/types';
import { QuestionAi, extCallQuery, questionStreamByPost } from '/@/api/ai/chat';
import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, stepEnumMap, type ChatMessage } from './model/types';
import { extCallQuery, 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 router from '/@/router';
import {
   activeChatRoom,
@@ -253,6 +254,9 @@
   roomConfig,
} from '/@/stores/chatRoom';
import { ErrorCode } from '/@/utils/request';
import { ElMessage } from 'element-plus';
import useClipboard from 'vue-clipboard3';
import { toMyFixed, toPercent } from '/@/utils/util';
const chatWidth = '75%';
const voicePageIsShow = ref(false);
let isTalking = ref(false);
@@ -268,7 +272,7 @@
const computedMessageList = computed(() => {
   return messageList.value.filter((v) => !!v);
});
const parseContent = (res) => {
const parseContent = (res, reportIsShow = false) => {
   if (!res) return null;
   let content: ChatContent = {
      type: AnswerType.Text,
@@ -299,7 +303,10 @@
      case AnswerType.Summary:
         content = {
            type: AnswerType.Summary,
            values: res.summary,
            values: res.summary?.map((item) => {
               item.reportIsShow = reportIsShow;
               return item;
            }),
         };
         break;
      case AnswerType.Url:
@@ -366,8 +373,7 @@
let questionRes = null;
let finalCalcSectionAId = null;
const questionAi = async (text, assistantMsg: ChatMessage) => {
   // processId.value = uuidv4();
const questionAi = async (text) => {
   let judgeParams = null;
   if (!preQuestion.value) {
      // const aiContent = computedMessageList.value.filter((item) => item.role === RoleEnum.assistant);
@@ -423,20 +429,52 @@
   // queryProcess();
   resetStep();
   let res = null;
   await questionStreamByPost(params, (chunkRes) => {
      if (chunkRes.mode === 'result') {
         res = chunkRes.value;
      } else {
   let lastTimestamp = new Date().getTime();
   const resultP = new Promise(async (resolve, reject) => {
      await questionStreamByPost(params, (chunkRes) => {
         Logger.info('chunk response:\n\n' + JSON.stringify(chunkRes));
         if (chunkRes.mode === 'result') {
            res = chunkRes.value;
            resolve(res);
            chunkRes.value = '准备数据分析';
         }
         if (chunkRes.mode === 'conclusion') {
            computedMessageList.value.at(-1).conclusion = chunkRes.value;
            chunkRes.value = '分析结束';
         }
         const stepList = computedMessageList.value.at(-1).stepList;
         const currentTimeStamp = new Date().getTime();
         const ms = toMyFixed(currentTimeStamp - lastTimestamp, 2) + ' ms';
         if (chunkRes.mode === 'finish') {
            stepList.at(-1).ms = ms;
            isTalking.value = false;
            return;
         }
         if (stepList?.length >= 1) {
            stepList.at(-1).ms = ms;
         }
         lastTimestamp = currentTimeStamp;
         const stepItem = convertProcessItem(chunkRes);
         computedMessageList.value.at(-1).stepList.push(stepItem);
         stepList.push(stepItem);
         scrollToBottom();
      }
   }).finally(() => {
      computedMessageList.value.at(-1).stepIsShow = false;
      resetStep();
      })
         .catch((err) => {
            throw err;
         })
         .finally(() => {
            isTalking.value = false;
            computedMessageList.value.at(-1).stepIsShow = false;
            resetStep();
         });
   });
   questionRes = res;
   const content = parseContent(res);
   questionRes = await resultP;
   const content = parseContent(res, true);
   return content;
};
@@ -487,9 +525,9 @@
      if (isCallExtParams) {
         const extRes = await extCallQuery(isCallExtParams);
         questionRes = extRes;
         resMsgContent = parseContent(extRes);
         resMsgContent = parseContent(extRes, true);
      } else {
         resMsgContent = await questionAi(content.values, assistantItem);
         resMsgContent = await questionAi(content.values);
      }
      nextUserMsgEndIndex.value++;
      if (isNewChat) {
@@ -512,8 +550,6 @@
      //    type: AnswerType.Text,
      //    values: '发生错误!',
      // });
   } finally {
      isTalking.value = false;
   }
};
@@ -591,8 +627,9 @@
//显示上一条消息
const showUpChatClick = () => {
   if (computedMessageList.value.length === 0) return;
   if (currentIndex.value === null) {
      currentIndex.value = history_data.value.length - 1;
   if (currentIndex.value == 0) {
      messageContent.value.values = history_data.value[currentIndex.value].content.values;
      return;
   } else {
      currentIndex.value = (currentIndex.value + history_data.value.length - 1) % history_data.value.length;
   }
@@ -601,6 +638,10 @@
//显示下一条消息
const showDownChatClick = () => {
   if (computedMessageList.value.length === 0) return;
   if (currentIndex.value == history_data.value.length - 1) {
      messageContent.value.values = history_data.value[currentIndex.value].content.values;
      return;
   }
   if (currentIndex.value === null) {
      currentIndex.value = 0;
   } else {
@@ -637,10 +678,19 @@
};
//#endregion
//#region ====================== 用户询问的问题设置为常用语 ======================
const setCommonQuestionInfo = ref({});
const { toClipboard } = useClipboard();
//用户复制问题
const copyUserClick = () => {};
const copyUserClick = (item) => {
   const text = item.content.values;
   ElMessage.success('复制成功');
   toClipboard(text);
};
//用户问题设置为常用语
const setCommonQuestionClick = () => {};
const setCommonQuestionClick = (item) => {
   setCommonQuestionInfo.value = item;
};
//#endregion
</script>