wujingjing
2025-02-20 68df4582c1edaf1952e6c21d769981e348fb3d04
src/components/chat/assistant/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,350 @@
<template>
   <div class="flex px-4 py-6 rounded-lg relative">
      <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ msg?.createTime }}</div>
      <img
         class="rounded-full size-12 flex-0"
         :class="{ 'mr-4': msg.role === RoleEnum.assistant }"
         :src="roleImageMap[msg.role]"
         alt=""
         srcset=""
      />
      <div class="flex-auto flex">
         <div class="inline-flex flex-col" :class="{ 'w-full': msg.role === RoleEnum.assistant }">
            <div class="w-full">
               <div class="rounded-[6px] p-4 leading-relaxed bg-white">
                  <!-- #region ====================== æ¶ˆæ¯å†…容 ======================-->
                  <!-- <template v-if="item.content?.values"> -->
                  <!-- #region ====================== å›žç­”组件 ======================-->
                  <template v-if="msg.content.type === AnswerType.Report">
                     <template v-if="msg?.stepGroup?.length > 0">
                        <div v-for="(num, index) in msg?.stepGroup?.length" :key="index">
                           <!-- æ„å›¾åˆ†æžåªå±•示第一个,后续的 stepGroup éƒ½æ˜¯ç©ºçš„,用于循环出组件 -->
                           <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                           <div class="flex flex-col" v-if="msg?.stepGroup?.[index]?.value?.length > 0 && index === 0">
                              <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                              <div class="flex items-center">
                                 <span class="mr-2">意图分析:</span>
                                 <div
                                    @click="toggleStepList(msg?.stepGroup?.[index])"
                                    class="cursor-pointer border border-gray-300 border-solid w-fit px-2 flex items-center space-x-2 rounded-lg hover:bg-gray-100 active:bg-gray-200"
                                 >
                                    <span>
                                       {{ toggleStepLabel(msg?.stepGroup?.[index]) }}
                                    </span>
                                    <span
                                       class="ywifont"
                                       :class="{
                                          'ywicon-unfold': !msg?.stepGroup?.[index].isShow,
                                          'ywicon-fold': msg?.stepGroup?.[index].isShow,
                                       }"
                                    ></span>
                                 </div>
                              </div>
                              <!-- #endregion -->
                              <!-- #region ====================== è¿‡ç¨‹è¾“出 ======================-->
                              <el-steps v-show="msg?.stepGroup?.[index].isShow" class="mt-3" direction="vertical">
                                 <el-step
                                    :key="`template-${stepIndex}`"
                                    v-for="(subItem, stepIndex) in msg?.stepGroup?.[index].value"
                                    :title="subItem.title"
                                    :status="stepEnumMap[subItem.status]"
                                 >
                                    <template #icon v-if="stepIndex + 1 === msg?.stepGroup?.[index].value.length && isTalking && isLast&&(subItem.finishLoading===false || subItem.finishLoading===undefined)">
                                       <span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
                                    </template>
                                    <template #title>
                                       <span class="">
                                          <span v-html="subItem.title.replace(/\n/g, '<br>')"></span>
                                          <span v-if="subItem.ms" class="text-green-600">{{ `(${subItem.ms})` }}</span></span
                                       >
                                    </template>
                                    <template #description v-if="subItem?.subStep?.length > 0">
                                       <div class="my-1 flex flex-col gap-1 text-[14px]">
                                          <div
                                             :key="`${msg.historyId}-${stepIndex + 1}-${multiChatIndex + 1}`"
                                             v-for="(multiChatItem, multiChatIndex) in subItem.subStep"
                                          >
                                             <component
                                                :order="`${stepIndex + 1}-${multiChatIndex + 1}`"
                                                :item="multiChatItem"
                                                :is="multiChatTypeMapCom[multiChatItem.type]"
                                                @change="multiChatChange"
                                                :disabled="!(stepIndex + 1 === msg?.stepGroup?.[index].value.length && isTalking && isLast)"
                                             />
                                          </div>
                                       </div>
                                    </template>
                                 </el-step>
                              </el-steps>
                              <!-- #endregion -->
                           </div>
                           <!-- #endregion -->
                           <!-- result æ—¶  recordSetTable å·²ç»åŠ è½½ï¼Œsummary æ—¶åˆåŠ è½½äº†ä¸€æ¬¡ï¼Œå¯¼è‡´æ¯”ä¾‹åˆ— push äº†ä¸¤æ¬¡
                              ä¸ºäº†è§£å†³è¿™ä¸ªé—®é¢˜ï¼Œç­‰åˆ° msg.historyId å­˜åœ¨æ—¶ï¼Œå†æ¸²æŸ“ recordSetTable
                              -->
                           <component
                              v-if="msg.content?.values?.[index]  && msg.content?.errCode !== ErrorCode.Message"
                              :reportIndex="index"
                              :conclusion="msg.content.values[index].conclusion"
                              :is="answerTypeMapCom[msg.content.values[index].content.type]"
                              :data="msg.content.values[index].content.values"
                              :originData="msg.content.values[index]"
                              :historyId="msg.historyId"
                              :isTalking="isTalking && isLast"
                           />
                        </div>
                     </template>
                     <p v-else-if="msg.content?.errCode !== ErrorCode.Message" class="text-info">暂无内容,请重试</p>
                  </template>
                  <component
                     v-else
                     :historyId="msg.historyId"
                     :conclusion="msg.conclusion"
                     :is="answerTypeMapCom[msg.content.type]"
                     :data="msg.content.values"
                     :originData="msg"
                     :isTalking="isTalking && isLast"
                  />
                  <!-- #endregion -->
                  <!-- </template> -->
                  <!-- #region ====================== æŠ¥é”™ä¿¡æ¯ ======================-->
                  <div v-if="msg.content?.errCode === ErrorCode.Message" class="flex-column w-full">
                     <p class="text-danger">
                        {{ msg.content.errMsg }}
                     </p>
                     <div class="mt-3 flex" v-if="showFixQuestion(msg)">
                        <div class="text-gray-600 flex-0 mb-auto py-3">
                           {{ '猜你想问:' }}
                        </div>
                        <div class="flex-auto space-x-2 space-y-1 inline-flex flex-wrap items-center">
                           <div
                              v-for="fixItem in msg.content.origin?.sample_question"
                              :key="fixItem"
                              class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg first-of-type:ml-2 first-of-type:mt-1"
                              @click="fixQuestionClick(fixItem, msg.content.origin)"
                           >
                              {{ fixItem }}
                           </div>
                        </div>
                     </div>
                  </div>
                  <!-- #endregion -->
                  <!-- #endregion -->
                  <!-- #region ====================== é™„加内容 ======================-->
                  <!-- #region ====================== åœæ­¢ ======================-->
                  <span v-if="msg.isStopMsg && msg?.role === RoleEnum.assistant" class="text-gray-400 text-[12px]">(已停止)</span>
                  <!-- parseContent è¿”回为 null -->
                  <p v-if="!msg.content && !isTalking && !msg.isStopMsg && msg.content?.errCode !== ErrorCode.Message" class="text-red-500">暂无数据</p>
                  <!-- #endregion -->
                  <!-- #endregion -->
               </div>
               <!-- #region ====================== ai æ¶ˆæ¯æ“ä½œ ======================-->
               <div v-if="msg.content?.values && !isSharePage" class="absolute flex items-center right-0 mr-4 mt-2 space-x-2">
                  <div
                     class="flex items-center justify-center size-[15px]"
                     v-if="msg.content?.type === AnswerType.Text || msg.content?.type === AnswerType.Knowledge"
                  >
                     <i class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]" @click="copyClick(msg)" />
                  </div>
                  <template v-if="msg.content.errCode !== ErrorCode.Message">
                     <el-tooltip effect="dark" content="点赞" placement="top">
                        <div class="flex items-center justify-center size-[15px]">
                           <i
                              :class="{ 'text-[#0284ff]': msg.state === AnswerState.Like }"
                              class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
                              @click="likeClick(msg)"
                           />
                        </div>
                     </el-tooltip>
                     <el-tooltip effect="dark" content="点踩" placement="top">
                        <div class="flex items-center justify-center size-[15px]">
                           <i
                              :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }"
                              class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                              @click="unLikeClick(msg)"
                           />
                        </div>
                     </el-tooltip>
                  </template>
                  <el-tooltip effect="dark" content="分享" placement="top">
                     <div class="flex items-center justify-center size-[15px]">
                        <i
                           class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                           @click="shareClick(msg)"
                        />
                     </div>
                  </el-tooltip>
                  <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,
                                    msg,
                                    msgList.filter((v) => v.role === RoleEnum.assistant).findIndex((v) => v.historyId === msg.historyId)
                                 )
                           "
                        />
                        <FeedbackPanel
                           v-show="feedbackIsShow && currentFeedbackMapItem === msg"
                           ref="feedbackPanelRef"
                           v-model:isShow="feedbackIsShow"
                           v-model:content="feedbackContent"
                           :chatItem="currentFeedbackMapItem"
                           :position="feedbackPosition"
                        />
                     </div>
                  </el-tooltip>
               </div>
               <!-- #endregion -->
               <div class="absolute flex items-center left-18 mt-2 space-x-2 pb-7">
                  <div
                     v-if="isTalking && isLast"
                     class="text-blue-400 cursor-pointer z-20 py-2 px-2 border border-solid border-blue-400 hover:text-blue-500 hover:border-blue-500 rounded-lg hover:bg-[#ebeffa]"
                     @click="stopGenClick"
                  >
                     åœæ­¢è¾“出
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>
</template>
<script setup lang="ts" name="AssistantMsg">
import type { PropType } from 'vue';
import { useAssistantContentOpt } from '../hooks/useAssistantContentOpt';
import type { ChatContent } from '../model/types';
import {
   AnswerState,
   AnswerType,
   MultiChatType,
   RoleEnum,
   answerTypeMapCom,
   roleImageMap,
   stepEnumMap,
   type ChatMessage,
} from '../model/types';
import FeedbackPanel from '../components/FeedbackPanel.vue';
import { multiChatTypeMapCom } from '/@/components/chat/chatComponents/multiChat';
import { isSharePage } from '/@/stores/chatRoom';
import { ErrorCode } from '/@/utils/request';
import { question_stream_reply } from '/@/api/ai/chat';
const props = defineProps({
   /** @description å½“前消息 */
   msg: {
      type: Object as PropType<ChatMessage>,
   },
   msgList: {
      type: Array as PropType<Array<ChatMessage>>,
   },
   isLast: {
      type: Boolean,
      default: false,
   },
   isTalking: {
      type: Boolean,
      default: false,
   },
});
const emit = defineEmits({
   sendChatMessage: (content: ChatContent) => true,
   shareMsg: (msg: ChatMessage) => true,
   stopGenClick: () => true,
});
const sendChatMessage = (content: ChatContent) => {
   emit('sendChatMessage', content);
};
const stopGenClick = () => {
   emit('stopGenClick');
};
//#region ====================== æ­¥éª¤ step ======================
const toggleStepLabel = (item: any) => (item.isShow ? '收起' : '展开');
const toggleStepList = (item: any) => {
   item.isShow = !item.isShow;
};
//#endregion
const {
   copyClick,
   likeClick,
   unLikeClick,
   feedbackPosition,
   feedbackIsShow,
   feedbackContent,
   feedbackPanelRef,
   currentFeedbackMapItem,
   feedbackClick,
   fixQuestionClick,
   showFixQuestion,
} = useAssistantContentOpt({
   sendChatMessage,
});
//#region ====================== åˆ†äº« ======================
const shareClick = async (item: ChatMessage) => {
   emit('shareMsg', item);
};
//#endregion
const multiChatChange = async (replyId: string, val: any) => {
   const res = await question_stream_reply({
      start_time: val.start_time,
      end_time: val.end_time,
      reply_id: replyId,
   });
};
</script>
<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);
      }
   }
}
:deep(.el-step__icon.is-text) {
   --radius-size: 24px;
   width: var(--radius-size);
   height: var((--radius-size));
}
:deep(.el-step__icon-inner) {
   font-size: 16px !important;
}
:deep(.el-step__description) {
   min-height: 20px;
}
:deep(.el-step:last-of-type .el-step__description) {
   // display: none;
}
</style>