From b5e920831f3e2ea94c809b0dd99f08c16d9feceb Mon Sep 17 00:00:00 2001 From: yangyin <1850366751@qq.com> Date: 星期五, 08 十一月 2024 14:20:00 +0800 Subject: [PATCH] 修改删除常用语时面板被关闭的问题 --- src/components/chat/Chat.vue | 652 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 482 insertions(+), 170 deletions(-) diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue index 1a85964..5b89f09 100644 --- a/src/components/chat/Chat.vue +++ b/src/components/chat/Chat.vue @@ -1,14 +1,28 @@ <template> <div class="flex h-full"> <div class="flex flex-col h-full flex-auto"> - <div class="h-full flex flex-col items-center overflow-y-auto"> - <div ref="chatListDom" class="h-full" :style="{ width: chatWidth }"> + <div ref="chatListDom" class="relative h-full flex flex-col items-center overflow-y-auto"> + <span + class="more-loading absolute text-blue-400 left-[50%] translate-x-[-50%] cursor-pointer w-10" + v-loading="moreIsLoading" + ></span> + <div class="h-full relative" v-loading="chatListLoading" :style="{ width: chatWidth }"> <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" - :key="index" + class="flex px-4 py-6 rounded-lg relative" + :class="{ 'flex-row-reverse': item.role === RoleEnum.user, 'px-10': isShareCheck }" + v-for="(item, msgIndex) of computedMessageList" + :key="`${item.historyId}_${item.role}`" > + <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ item?.createTime }}</div> + <!-- :class="{ 'top-[30px]': item.role === RoleEnum.user, 'top-[30px]': item.role === RoleEnum.assistant }" --> + + <el-checkbox + v-if="isShareCheck" + class="absolute left-0 top-[28px]" + size="large" + v-model="item.isChecked" + @change="(isChecked) => shareCheckChange(isChecked as boolean, item)" + ></el-checkbox> <img class="rounded-full size-12 flex-0" :class="{ 'mr-4': item.role === RoleEnum.assistant, 'ml-4': item.role === RoleEnum.user }" @@ -18,55 +32,133 @@ /> <div class="flex-auto flex" :class="{ 'justify-end': item.role === RoleEnum.user }"> <div class="inline-flex flex-col" :class="{ 'w-full': item.role === RoleEnum.assistant }"> - <div class="w-full" v-if="item.content?.values"> + <div class="w-full"> <div class="text-sm rounded-[6px] p-4 leading-relaxed" :style="{ backgroundColor: item.role === RoleEnum.user ? 'rgb(197 224 255)' : 'white' }" + :class="{ group: item.role === RoleEnum.user }" > - <div v-if="item.content.errCode === ErrorCode.Message" class="flex-column w-full"> - <p class="text-red-500"> - {{ item.content.errMsg }} - </p> - <div class="mt-5 flex items-center" v-if="showFixQuestion(item)"> - <div class="text-gray-600 flex-0"> - {{ item.content.origin.err_json.fix_question.title + '锛�' }} + <div class="flex flex-col" v-if="item?.stepList?.length > 0"> + <div class="flex items-center mb-3"> + <span class="mr-2">鎰忓浘鍒嗘瀽锛�</span> + <div + @click="toggleStepList(item)" + 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(item) }} + </span> + <span class="ywifont" :class="{ 'ywicon-unfold': !item.stepIsShow, 'ywicon-fold': item.stepIsShow }"></span> </div> - <div class="ml-1 space-x-2 inline-flex flex-wrap"> - <div - v-for="fixItem in item.content.origin.err_json.fix_question?.values" - :key="fixItem" - class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg" - @click="fixQuestionClick(fixItem, item.content.origin)" + </div> + + <!-- 杩囩▼杈撳嚭 --> + <el-steps v-show="item.stepIsShow" direction="vertical" :active="activeStep"> + <el-step + v-for="(subItem, index) in item.stepList" + :title="subItem.title" + :status="stepEnumMap[subItem.status]" + > + <template + #icon + v-if="index + 1 === item.stepList.length && isTalking && msgIndex === computedMessageList.length - 1" > - {{ fixItem.title }} + <span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span> + </template> + <template #title> + <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 + v-if="item.role === RoleEnum.user && item.content?.values && chatDisplayMode === 'default' && !isShareCheck" + class="absolute flex items-center bottom-0 group invisible" + > + <div class="bg-[#fff] flex items-center 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(item)" + /> </div> - </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(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-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]" + @click="shareClick(item)" + /> + </div> + </el-tooltip> </div> </div> - <template v-else> - <component :is="answerTypeMapCom[item.content.type]" :data="item.content.values" :originData="item" /> - - <div - v-if="item.role === RoleEnum.assistant && item.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 item.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 }} + <template v-if="item.content?.values"> + <div v-if="item.content.errCode === ErrorCode.Message" class="flex-column w-full"> + <p class="text-red-500"> + {{ item.content.errMsg }} + </p> + <div class="mt-5 flex items-center" v-if="showFixQuestion(item)"> + <div class="text-gray-600 flex-0"> + {{ item.content.origin.err_json.fix_question.title + '锛�' }} + </div> + <div class="ml-1 space-x-2 inline-flex flex-wrap"> + <div + v-for="fixItem in item.content.origin.err_json.fix_question?.values" + :key="fixItem" + class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg" + @click="fixQuestionClick(fixItem, item.content.origin)" + > + {{ fixItem.title }} + </div> </div> </div> </div> + <template v-else> + <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" + > + <div class="flex-0 mb-auto -mr-4">鍏宠仈鍔熻兘锛�</div> + <div class="space-x-5 flex flex-wrap"> + <div + v-for="callItem in item.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> </template> </div> - <!-- 鎿嶄綔 --> - <div v-if="item.role === RoleEnum.assistant" class="absolute flex items-center right-0 mr-4 mt-2 space-x-2"> + <div + v-if="item.role === RoleEnum.assistant && item.content?.values && chatDisplayMode === 'default' && !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="item.content?.type === AnswerType.Text || item.content?.type === AnswerType.Knowledge" @@ -77,53 +169,65 @@ /> </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> + <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> + <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)" + class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]" + @click="shareClick(item)" /> </div> - </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> + <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> - - <Loding v-if="isTalking && index === messageList.length - 1" class="w-fit" :process="process" /> </div> </div> </div> - <div v-if="showAskMore" class="ml-4 mt-5 text-sm"> + <div v-if="showAskMore && !isShareCheck" class="ml-4 mt-5 text-sm pb-10"> <div class="text-gray-600 mb-5">浣犲彲浠ョ户缁棶鎴戯細</div> <div class="space-y-2 inline-flex flex-col"> <div @@ -139,18 +243,24 @@ </div> </div> - <div class="sticky bottom-0 w-full p-6 pb-8 bg-[rgb(247,248,250)] flex justify-center"> + <div + class="sticky bottom-0 w-full p-6 bg-[rgb(247,248,250)] flex justify-center" + v-if="chatDisplayMode === 'default' && !isShareCheck" + > <PlayBar v-model:voicePageIsShow="voicePageIsShow" :isTalking="isTalking" :isHome="false" v-model="messageContent.values" @sendClick="sendClick" + @showUpChatClick="showUpChatClick" + @showDownChatClick="showDownChatClick" :style="{ width: chatWidth }" + :setCommonQuestionInfo="setCommonQuestionInfo" ></PlayBar> </div> + <div class="sticky bottom-0 w-full p-6 bg-[rgb(247,248,250)] flex justify-center" v-if="isShareCheck"></div> </div> - <CustomDrawer v-model:isShow="drawerIsShow" @updateChatInput="updateChatInput" /> </div> </template> @@ -158,22 +268,34 @@ <script setup lang="ts"> import { ElMessage } from 'element-plus'; import _ from 'lodash'; -import { v4 as uuidv4 } from 'uuid'; +import moment from 'moment'; import { computed, onMounted, ref } from 'vue'; +import useClipboard from 'vue-clipboard3'; 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 } from './model/types'; -import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types'; -import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, extCallQuery } from '/@/api/ai/chat'; +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 { Logger } from '/@/model/logger/Logger'; import router from '/@/router'; -import { activeChatRoom, activeLLMId, activeSampleId, activeSectionAId, getRoomConfig, roomConfig } from '/@/stores/chatRoom'; +import { + activeChatRoom, + activeGroupType, + activeLLMId, + activeRoomId, + activeSampleId, + activeSectionAId, + chatDisplayMode, + getRoomConfig, + roomConfig, +} from '/@/stores/chatRoom'; import { ErrorCode } from '/@/utils/request'; - +import { toMyFixed } from '/@/utils/util'; const chatWidth = '75%'; const voicePageIsShow = ref(false); let isTalking = ref(false); @@ -183,13 +305,14 @@ }); const currentRoute = router.currentRoute; const currentRouteId = currentRoute.value.query.id as string; +activeRoomId.value = currentRouteId; const chatListDom = ref<HTMLDivElement>(); const messageList = ref<ChatMessage[]>([]); 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, @@ -220,7 +343,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: @@ -248,16 +374,46 @@ content.origin = res; return content; }; - const { clearQueryProcess, process, processId, queryProcess } = useQueryProcess(); -const DEFAULT_SECTION_A_ID = 'knowledge_base'; +//#region ====================== 姝ラ step ====================== +const activeStep = ref(-1); +const stepList = ref<StepItem[]>([ + { + title: '鎰忓浘鍒嗘瀽涓�...', + status: 0, + }, + { + title: '鎰忓浘鍒嗘瀽瀹屾垚', + status: 1, + }, + { + title: '鎬濊�冨浣曟墽琛�:鎸囨爣鏄庣粏鏌ヨ', + status: 1, + }, + { + title: '鎸囨爣鏄庣粏鏌ヨ瀹屾垚', + status: 1, + }, +]); + +const resetStep = () => { + activeStep.value = -1; + stepList.value = []; +}; + +const toggleStepLabel = (item: ChatMessage) => (item.stepIsShow ? '鏀惰捣' : '灞曞紑'); +const toggleStepList = (item: ChatMessage) => { + item.stepIsShow = !item.stepIsShow; +}; + +//#endregion + +const DEFAULT_SECTION_A_ID = 'knowledge_base'; let questionRes = null; let finalCalcSectionAId = null; const questionAi = async (text) => { - - processId.value = uuidv4(); let judgeParams = null; if (!preQuestion.value) { // const aiContent = computedMessageList.value.filter((item) => item.role === RoleEnum.assistant); @@ -267,7 +423,7 @@ // prev_question: lastQuestion, // } // : {}; - // 姝e父鍥炵瓟鏆傛椂涓嶉噰鐢� + // 姝e父鍥炵瓟鏆傛椂涓嶉噰鐢� judgeParams = {}; } else { judgeParams = { @@ -279,20 +435,27 @@ currentSectionAId = activeSectionAId.value; activeSectionAId.value = ''; } else { - const lastSectionAItem = _.findLast(computedMessageList.value as any, (item) => item.role === RoleEnum.assistant && !!item.sectionAId); + const lastSectionAItem = _.findLast( + computedMessageList.value as any, + (item) => item.role === RoleEnum.assistant && !!item.sectionAId + ); currentSectionAId = lastSectionAItem?.sectionAId ?? DEFAULT_SECTION_A_ID; } finalCalcSectionAId = currentSectionAId; - + const params = { - process_id: processId.value, + // process_id: processId.value, question: text, // FIXME: 鏆傛椂杩欐牱 - section_a_id: currentSectionAId, + // section_a_id: currentSectionAId, history_group_id: currentRouteId, raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false, ...judgeParams, } as any; + + if (activeGroupType.value) { + params.group_type = activeGroupType.value; + } if (currentSampleId) { params.sample_id = currentSampleId; @@ -302,13 +465,56 @@ // if (currentLLMId) { // params.llm_id = currentLLMId; // } - clearQueryProcess(); - queryProcess(); - const res = await QuestionAi(params).finally(() => { - clearQueryProcess(); + // clearQueryProcess(); + // queryProcess(); + resetStep(); + let res = null; + 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); + stepList.push(stepItem); + scrollToBottom(); + }) + .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; }; @@ -322,14 +528,8 @@ let currentLLMId = null; -const getAnswerById = async (historyId: string) => { - return await GetHistoryAnswer({ - history_id: historyId, - }); -}; - const sendChatMessage = async (content: ChatContent = messageContent.value, cb?: any, isCallExtParams?: any) => { - if (!content?.values || isTalking.value) return; + if (!content?.values || isTalking.value || chatListLoading.value) return; const isNewChat = messageList.value.length === 0; if (isNewChat) { if (activeSampleId.value) { @@ -339,15 +539,20 @@ if (activeLLMId.value) { currentLLMId = activeLLMId.value; } - - } let resMsgContent: ChatContent = null; try { isTalking.value = true; - const userItem: ChatMessage = { role: RoleEnum.user, content } as any; - const assistantItem: ChatMessage = { role: RoleEnum.assistant, content: null, state: AnswerState.Null } as any; + const userItem: ChatMessage = { role: RoleEnum.user, content, isChecked: false } as any; + const assistantItem: ChatMessage = { + role: RoleEnum.assistant, + content: null, + state: AnswerState.Null, + stepList: [], + stepIsShow: true, + isChecked: false, + } as any; // 鍙戦�佸綋鍓� messageList.value.push(userItem); // 娓呯┖杈撳叆妗� @@ -355,14 +560,17 @@ // 鍑虹幇鍥炲锛岀疆绌哄嚭鐜扮瓑寰呭姩鐢� messageList.value.push(assistantItem); + // 婊氬姩鑷冲綋鍓嶅彂閫佹秷鎭� + scrollToBottom(); + if (isCallExtParams) { const extRes = await extCallQuery(isCallExtParams); questionRes = extRes; - resMsgContent = parseContent(extRes); + resMsgContent = parseContent(extRes, true); } else { resMsgContent = await questionAi(content.values); } - + nextUserMsgEndIndex.value++; if (isNewChat) { const firstResCb = getRoomConfig(currentRouteId, 'firstResCb'); firstResCb?.(resMsgContent); @@ -374,13 +582,15 @@ assistantItem.historyId = questionRes.history_id; assistantItem.sectionAId = finalCalcSectionAId; appendLastMessageContent(resMsgContent); + setTimeout(() => { + // 鏀跺埌鍥炲锛岀户缁粴 + scrollToBottom(); + }, 300); } catch (error: any) { // appendLastMessageContent({ // type: AnswerType.Text, // values: '鍙戠敓閿欒锛�', // }); - } finally { - isTalking.value = false; } }; @@ -388,56 +598,33 @@ sendChatMessage(messageContent.value, cb); }; const appendLastMessageContent = (content: ChatContent) => { + const currentTime = moment().format('MM鏈圖D鏃� HH:mm:ss'); if (messageList.value.at(-1)) { messageList.value.at(-1).content = content; + messageList.value.at(-1).createTime = currentTime; } }; +const { loadRangeData, onChatListScroll, moreIsLoading, nextUserMsgEndIndex } = useScrollLoad({ + container: chatListDom, + historyGroupId: currentRouteId, + messageList, + parseAnswerContent: parseContent, +}); + +const chatListLoading = ref(false); + +const { scrollToBottom } = useScrollToBottom({ + chatListDom: chatListDom, +}); onMounted(async () => { - const res = await QueryHistoryDetail({ - history_group_id: currentRouteId, + messageList.value = []; + // 鍔犺浇鍒濆鏁版嵁 + chatListLoading.value = true; + + await loadRangeData().finally(() => { + chatListLoading.value = false; }); - - messageList.value = (res.details ?? []).map((item) => { - return { - historyId: item.history_id, - role: RoleEnum.user, - content: { - type: AnswerType.Text, - values: item.question, - }, - } as ChatMessage; - }); - const sectionAIdMap = new Map(); - - const resList = await Promise.all( - (res.details ?? []).map((item) => { - sectionAIdMap.set(item.history_id, item.section_a_id); - return getAnswerById(item.history_id); - }) - ); - let i = 0; - resList.map((item, index) => { - const insertIndex = index + 1 + i; - const userMsg = messageList.value[insertIndex - 1]; - userMsg.content.values = item?.answer?.question ??userMsg.content.values; - messageList.value.splice( - insertIndex, - 0, - item.answer === null - ? null - : { - historyId: item.answer?.history_id, - role: RoleEnum.assistant, - content: parseContent(item.answer), - state: item.answer_state, - sectionAId: sectionAIdMap.get(item.answer.history_id), - - } - ); - i++; - }); - if (messageList.value.length === 0) { messageContent.value = { type: AnswerType.Text, @@ -445,14 +632,17 @@ }; sendChatMessage(); + } else { + setTimeout(() => { + // 鍒濆鐘舵�佹粴涓�涓� + scrollToBottom(); + + setTimeout(() => { + chatListDom.value.addEventListener('scroll', onChatListScroll); + }, 300); + }, 300); } }); - -const { forbidScroll } = useScrollToBottom({ - chatListDom: chatListDom, - displayMessageList: computedMessageList, -}); - //#region ====================== 鍏宠仈鏌ヨ ====================== const relativeQueryClick = async (val) => { sendChatMessage( @@ -470,7 +660,37 @@ ); }; //#endregion - +//#region ====================== 鍏夋爣杈撳叆涓婁笅绠ご鏄剧ず鍘嗗彶娑堟伅 ====================== +const currentIndex = ref(null); +const history_data = computed(() => { + return computedMessageList.value.filter((item) => item.role === RoleEnum.user); +}); +//鏄剧ず涓婁竴鏉℃秷鎭� +const showUpChatClick = () => { + if (computedMessageList.value.length === 0) return; + 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; + } + messageContent.value.values = history_data.value[currentIndex.value].content.values; +}; +//鏄剧ず涓嬩竴鏉℃秷鎭� +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 { + currentIndex.value = (currentIndex.value + 1) % history_data.value.length; + } + messageContent.value.values = history_data.value[currentIndex.value].content.values; +}; +//#endregion const { copyClick, likeClick, @@ -487,7 +707,6 @@ showFixQuestion, showAskMore, } = useAssistantContentOpt({ - forbidScroll, sendChatMessage, displayMessageList: computedMessageList, }); @@ -499,12 +718,105 @@ messageContent.value.values = content; }; //#endregion +//#region ====================== 鐢ㄦ埛璇㈤棶鐨勯棶棰樿缃负甯哥敤璇� ====================== +const setCommonQuestionInfo = ref({}); +const { toClipboard } = useClipboard(); + +//鐢ㄦ埛澶嶅埗闂 +const copyUserClick = (item) => { + const text = item.content.values; + ElMessage.success('澶嶅埗鎴愬姛'); + toClipboard(text); +}; +//鐢ㄦ埛闂璁剧疆涓哄父鐢ㄨ +const setCommonQuestionClick = (item) => { + setCommonQuestionInfo.value = item; +}; +//#endregion + +//#region ====================== 鍒嗕韩 ====================== + +const resetShare = () => { + computedMessageList.value.forEach((item) => { + item.isChecked = false; + }); + isShareCheck.value = false; +}; + +const isShareCheck = ref(false); +const shareClick = async (item: ChatMessage) => { + item.isChecked = true; + shareCheckChange(true, item); + // 鐩墠鍙垎浜竴涓紝涓嶈繘鍏ュ閫夋ā寮忥紝鍒嗕韩澶氫釜 + // isShareCheck.value = true; + + const url = await generateShareUrl(); + ElMessage.success('宸插鍒跺垎浜摼鎺�'); + toClipboard(url); +}; + +const shareCheckChange = (isChecked: boolean, item: ChatMessage) => { + const toFindRole = item.role === RoleEnum.user ? RoleEnum.assistant : RoleEnum.user; + + const foundMapItem = computedMessageList.value.find( + (msgItem) => msgItem.historyId === item.historyId && msgItem.role === toFindRole + ); + if (!foundMapItem) return; + foundMapItem.isChecked = isChecked; +}; + +const generateShareUrl = async () => { + const shareList = computedMessageList.value.filter((item) => item.isChecked && item.role === RoleEnum.user && !!item.historyId); + if (shareList.length === 0) { + // ElMessage.warning('璇烽�夋嫨瑕佸垎浜殑鍐呭'); + return; + } + const historyIdStr = shareList.map((item) => item.historyId).join(','); + const res = await shareChatHistoryByPost({ + history_ids: historyIdStr, + share_days: 1, + }).finally(() => { + resetShare(); + }); + + if (!res.values) return; + const shareId = Object.values(res.values)[0]; + if (!shareId) return; + + return `${shareId}`; +}; + +//#endregion </script> -<style scoped> +<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) { + height: 20px; +} </style> -- Gitblit v1.9.3