| | |
| | | <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> |
| | |
| | | </div> |
| | | </div> |
| | | <template v-else> |
| | | <component :is="answerTypeMapCom[item.content.type]" :data="item.content.values" :originData="item" /> |
| | | <component |
| | | :conclusion="item.conclusion" |
| | | :is="answerTypeMapCom[item.content.type]" |
| | | :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" |
| | | class="flex font-bold items-center mt-6" |
| | |
| | | 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]"> |
| | | <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> |
| | | <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]" |
| | |
| | | /> |
| | | </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> |
| | |
| | | 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); |
| | |
| | | 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, |
| | |
| | | case AnswerType.Summary: |
| | | content = { |
| | | type: AnswerType.Summary, |
| | | values: res.summary, |
| | | values: res.summary?.map((item) => { |
| | | item.reportIsShow = reportIsShow; |
| | | return item; |
| | | }), |
| | | }; |
| | | break; |
| | | case AnswerType.Url: |
| | |
| | | // queryProcess(); |
| | | resetStep(); |
| | | let res = null; |
| | | await questionStreamByPost(params, (chunkRes) => { |
| | | Logger.info('chunk response:\n\n' + JSON.stringify(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; |
| | | }; |
| | | |
| | |
| | | if (isCallExtParams) { |
| | | const extRes = await extCallQuery(isCallExtParams); |
| | | questionRes = extRes; |
| | | resMsgContent = parseContent(extRes); |
| | | resMsgContent = parseContent(extRes, true); |
| | | } else { |
| | | resMsgContent = await questionAi(content.values); |
| | | } |
| | |
| | | // type: AnswerType.Text, |
| | | // values: '发生错误!', |
| | | // }); |
| | | } finally { |
| | | isTalking.value = false; |
| | | } |
| | | }; |
| | | |
| | |
| | | //#endregion |
| | | //#region ====================== 用户询问的问题设置为常用语 ====================== |
| | | const setCommonQuestionInfo = ref({}); |
| | | const { toClipboard } = useClipboard(); |
| | | |
| | | //用户复制问题 |
| | | const copyUserClick = () => {}; |
| | | const copyUserClick = (item) => { |
| | | const text = item.content.values; |
| | | ElMessage.success('复制成功'); |
| | | toClipboard(text); |
| | | }; |
| | | //用户问题设置为常用语 |
| | | const setCommonQuestionClick = (item) => { |
| | | setCommonQuestionInfo.value = item; |