<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 ====================== 报错信息 ======================-->
|
<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 -->
|
<!-- #region ====================== 回答组件 ======================-->
|
<template v-else>
|
<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">
|
<!-- #region ====================== 意图分析 ======================-->
|
<div class="flex flex-col" v-if="msg?.stepGroup?.[index]?.value?.length > 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">
|
<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]">
|
<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]"
|
: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.historyId"
|
: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 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"
|
/>
|
</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"
|
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>
|
</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';
|
|
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,
|
});
|
|
const sendChatMessage = (content: ChatContent) => {
|
emit('sendChatMessage', content);
|
};
|
|
//#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
|
</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>
|