<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 mr-4"
|
:src="roleImageMap[msg.role]"
|
alt=""
|
srcset=""
|
/>
|
<div class="flex-auto flex">
|
<div class="inline-flex flex-col w-full" >
|
<div class="w-full">
|
<div class="rounded-[6px] p-4 leading-relaxed bg-white">
|
<!-- #region ====================== 意图分析 ======================-->
|
<div class="flex flex-col" v-if="msg?.stepList?.length > 0">
|
<!-- #region ====================== 意图分析 ======================-->
|
<div class="flex items-center">
|
<span class="mr-2">意图分析:</span>
|
<div
|
@click="toggleStepList(msg)"
|
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) }}
|
</span>
|
<span class="ywifont" :class="{ 'ywicon-unfold': !msg.stepIsShow, 'ywicon-fold': msg.stepIsShow }"></span>
|
</div>
|
</div>
|
<!-- #endregion -->
|
|
<!-- #region ====================== 过程输出 ======================-->
|
<el-steps v-show="msg.stepIsShow" class="mt-3" direction="vertical" :active="activeStep">
|
<el-step v-for="(subItem, index) in msg.stepList" :title="subItem.title" :status="stepEnumMap[subItem.status]">
|
<template
|
#icon
|
v-if="index + 1 === msg.stepList.length && isTalking && msgIndex === computedMessageList.length - 1"
|
>
|
<span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
|
</template>
|
<template #title>
|
<span class="">
|
{{ subItem.title }}
|
|
<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]">
|
<component
|
:key="`${msg.historyId}-${index + 1}-${multiChatIndex + 1}`"
|
v-for="(multiChatItem, multiChatIndex) in subItem.subStep"
|
:order="`${index + 1}-${multiChatIndex + 1}`"
|
:item="multiChatItem"
|
:is="multiChatTypeMapCom[multiChatItem.type]"
|
:disabled="!(index + 1 === msg.stepList.length && isTalking && msgIndex === computedMessageList.length - 1)"
|
/>
|
</div>
|
</template>
|
</el-step>
|
</el-steps>
|
<!-- #endregion -->
|
</div>
|
<!-- #endregion -->
|
|
<!-- #region ====================== 用户操作按钮 ======================-->
|
<div
|
v-if="msg.role === RoleEnum.user && msg.content?.values && !isSharePage && !isShareCheck"
|
class="absolute flex items-center bottom-0 group invisible"
|
>
|
<div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible">
|
<el-tooltip effect="dark" content="复制" placement="top">
|
<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(msg)"
|
/>
|
</div>
|
</el-tooltip>
|
<el-tooltip effect="dark" content="设为常用语" placement="top">
|
<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(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-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
|
@click="shareClick(msg)"
|
/>
|
</div>
|
</el-tooltip>
|
</div>
|
</div>
|
|
<!-- #endregion -->
|
<!-- #region ====================== 消息内容 ======================-->
|
<template v-if="msg.content?.values">
|
<!-- #region ====================== 报错信息 ======================-->
|
<div v-if="msg.content.errCode === ErrorCode.Message" class="flex-column w-full">
|
<p class="text-red-500">
|
{{ 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 -->
|
<!-- #region ====================== 回答组件 ======================-->
|
<template v-else>
|
<component
|
:conclusion="msg.conclusion"
|
:is="answerTypeMapCom[msg.content.type]"
|
:data="msg.content.values"
|
:originData="msg"
|
:isTalking="isTalking && msgIndex === computedMessageList.length - 1"
|
/>
|
<div
|
v-if="msg.role === RoleEnum.assistant && msg.content.origin?.ext_call_list"
|
class="flex font-bold items-center mt-6"
|
>
|
<div class="flex-0 mb-auto -mr-4">关联功能:</div>
|
<div class="space-x-5 flex flex-wrap">
|
<div
|
v-for="callItem in msg.content.origin?.ext_call_list"
|
:key="callItem.call_ext_id"
|
@click="relativeQueryClick(callItem)"
|
class="cursor-pointer hover:underline first-of-type:ml-5"
|
>
|
{{ callItem.question }}
|
</div>
|
</div>
|
</div>
|
</template>
|
<!-- #endregion -->
|
</template>
|
|
<!-- #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" class="text-red-500">暂无数据</p>
|
<!-- #endregion -->
|
<!-- #endregion -->
|
</div>
|
<!-- #region ====================== ai 消息操作 ======================-->
|
<div
|
v-if="msg.role === RoleEnum.assistant && msg.content?.values && !isSharePage && !isShareCheck"
|
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,
|
computedMessageList
|
.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>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts" name="AssistantMsg">
|
|
import type { CancelTokenSource } from 'axios';
|
import axios from 'axios';
|
import { findLast, orderBy } from 'lodash-es';
|
import moment from 'moment';
|
import QRCode from 'qrcodejs2-fixes';
|
import { computed, nextTick, onActivated, onMounted, ref } from 'vue';
|
import useClipboard from 'vue-clipboard3';
|
import { loadAmisSource } from '../amis/load';
|
import FeedbackPanel from './components/FeedbackPanel.vue';
|
import { useAssistantContentOpt } from './hooks/useAssistantContentOpt';
|
import { convertProcessItem, useScrollLoad } from './hooks/useScrollLoad';
|
import { useScrollToBottom } from './hooks/useScrollToBottom';
|
import type { ChatContent, StepItem } from './model/types';
|
import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, stepEnumMap, type ChatMessage } from './model/types';
|
import { extCallQuery, questionStreamByPost, shareChatHistoryByPost } from '/@/api/ai/chat';
|
import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
|
import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
|
import { SHARE_URL } from '/@/constants';
|
import { Logger } from '/@/model/logger/Logger';
|
import router from '/@/router';
|
import {
|
activeChatRoom,
|
activeGroupType,
|
activeLLMId,
|
activeRoomId,
|
activeSampleId,
|
activeSectionAId,
|
getRoomConfig,
|
isSharePage,
|
roomConfig,
|
} from '/@/stores/chatRoom';
|
import UserMsg from './user/index.vue';
|
import { multiChatTypeMapCom } from '/@/components/chat/chatComponents/multiChat';
|
import emitter from '/@/utils/mitt';
|
import { ErrorCode } from '/@/utils/request';
|
import { toMyFixed } from '/@/utils/util';
|
import { ElMessage } from 'element-plus';
|
const props = defineProps({
|
/** @description 当前消息 */
|
msg: {
|
type: Object,
|
},
|
})
|
|
|
</script>
|
<style scoped lang="scss"></style>
|