From f9831b4c716eaa858353a5a1a5ba340f3b9c92ec Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期一, 04 十一月 2024 17:41:13 +0800
Subject: [PATCH] 步骤 loading 动画

---
 src/components/chat/Chat.vue |  637 ++++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 378 insertions(+), 259 deletions(-)

diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue
index 6eb98d6..25cb9cf 100644
--- a/src/components/chat/Chat.vue
+++ b/src/components/chat/Chat.vue
@@ -1,14 +1,19 @@
 <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"
+						:key="`${item.historyId}_${item.role}`"
 					>
+						<div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ item?.createTime }}</div>
 						<img
 							class="rounded-full size-12 flex-0"
 							:class="{ 'mr-4': item.role === RoleEnum.assistant, 'ml-4': item.role === RoleEnum.user }"
@@ -18,44 +23,112 @@
 						/>
 						<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' }"
 									>
-										<div v-if="item.content.errCode === ErrorCode.Message" class="flex-column w-full">
-											<p class="text-red-500">
-												{{ item.content.msg }}
-											</p>
-											<div
-												class="mt-5 flex items-center"
-												v-if="showFixQuestion"
-											>
-												<div class="text-gray-600 flex-0">
-													{{ computedMessageList.at(-1).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="item in computedMessageList.at(-1).content.origin.err_json.fix_question?.values"
-														:key="item"
-														class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg"
-														@click="fixQuestionClick(item)"
-													>
-														{{ item.title }}
+											</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">
+														<span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
+													</template>
+													<template #title>
+														<span class="text-sm">{{ subItem.title }}</span>
+													</template>
+												</el-step>
+											</el-steps>
+										</div>
+
+										<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>
-										</div>
-										<component v-else :is="answerTypeMapCom[item.content.type]" :data="item.content.values" :originData="item" />
+											<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 }}
+														</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.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]">
+											<i
+												class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
+												@click="copyUserClick(item)"
+											/>
+										</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]"
+												@click="setCommonQuestionClick(item)"
+											/>
+										</div>
+									</div>
+									<div
+										v-if="item.role === RoleEnum.assistant && item.content?.values"
+										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"
 										>
 											<i
-												class="p-2 ywicon icon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]"
+												class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]"
 												@click="copyClick(item)"
 											/>
 										</div>
@@ -63,14 +136,14 @@
 											<div class="flex items-center justify-center size-[15px]">
 												<i
 													:class="{ 'text-[#0284ff]': item.state === AnswerState.Like }"
-													class="p-2 ywicon icon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
+													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 ywicon icon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
+													class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
 													@click="unLikeClick(item)"
 												/>
 											</div>
@@ -78,7 +151,7 @@
 
 										<div class="flex items-center justify-center size-[15px] relative">
 											<i
-												class="p-2 ywicon icon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
+												class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
 												@click="
 													($event) =>
 														feedbackClick(
@@ -101,12 +174,10 @@
 										</div>
 									</div>
 								</div>
-
-								<Loding v-else class="w-fit" :process="process" />
 							</div>
 						</div>
 					</div>
-					<div v-if="showAskMore" class="ml-4 mt-5 text-sm">
+					<div v-if="showAskMore" 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
@@ -122,40 +193,51 @@
 				</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">
 				<PlayBar
 					v-model:voicePageIsShow="voicePageIsShow"
 					:isTalking="isTalking"
 					:isHome="false"
 					v-model="messageContent.values"
 					@sendClick="sendClick"
+					@showUpChatClick="showUpChatClick"
+					@showDownChatClick="showDownChatClick"
 					:style="{ width: chatWidth }"
 				></PlayBar>
 			</div>
 		</div>
-
 		<CustomDrawer v-model:isShow="drawerIsShow" @updateChatInput="updateChatInput" />
 	</div>
 </template>
 
 <script setup lang="ts">
-import { ElMessage } from 'element-plus';
 import _ from 'lodash';
+import moment from 'moment';
 import { v4 as uuidv4 } from 'uuid';
-import { computed, nextTick, onActivated, onMounted, ref, watch } from 'vue';
-import useClipboard from 'vue-clipboard3';
+import { computed, onMounted, ref } from 'vue';
 import FeedbackPanel from './components/FeedbackPanel.vue';
 import Loding from './components/Loding.vue';
-import type { ChatContent } from './model/types';
-import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types';
-import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, SetHistoryAnswerState, getQuestionProcess } from '/@/api/ai/chat';
+import { useAssistantContentOpt } from './hooks/useAssistantContentOpt';
+import { useQueryProcess } from './hooks/useQueryProcess';
+import { convertProcessItem, useScrollLoad } from './hooks/useScrollLoad';
+import { useScrollToBottom } from './hooks/useScrollToBottom';
+import type { ChatContent, StepItem } from './model/types';
+import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage, stepEnumMap } from './model/types';
+import { QuestionAi, extCallQuery, questionStreamByPost } from '/@/api/ai/chat';
 import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
 import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
-import { useClickOther } from '/@/hooks/useClickOther';
 import router from '/@/router';
-import { activeChatRoom, activeLLMId, activeSampleId, activeSectionAId, getRoomConfig, roomConfig } from '/@/stores/chatRoom';
+import {
+	activeChatRoom,
+	activeGroupType,
+	activeLLMId,
+	activeRoomId,
+	activeSampleId,
+	activeSectionAId,
+	getRoomConfig,
+	roomConfig,
+} from '/@/stores/chatRoom';
 import { ErrorCode } from '/@/utils/request';
-
 const chatWidth = '75%';
 const voicePageIsShow = ref(false);
 let isTalking = ref(false);
@@ -165,24 +247,11 @@
 });
 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 && v.role !== RoleEnum.system);
-});
-
-const showAskMore = computed(() => {
-	if (!computedMessageList.value || computedMessageList.value.length === 0) return false;
-	const last = computedMessageList.value.at(-1);
-	const isShow = last?.role === RoleEnum.assistant && last?.content?.values && last.content?.askMoreList?.length > 0;
-	return isShow;
-});
-
-const showFixQuestion = computed(() => {
-	if (!computedMessageList.value || computedMessageList.value.length === 0) return false;
-	const last = computedMessageList.value.at(-1);
-	const isShow = last?.role === RoleEnum.assistant && last?.content?.values && last.content?.origin?.err_json?.fix_question;
-	return isShow;
+	return messageList.value.filter((v) => !!v);
 });
 const parseContent = (res) => {
 	if (!res) return null;
@@ -239,71 +308,116 @@
 	}
 	content.askMoreList = _.orderBy(res.context_history, [(item) => Number(item.radio)], ['desc']);
 	content.errCode = res?.err_code;
-	content.msg = res?.json_msg;
+	content.errMsg = res?.json_msg;
 	content.origin = res;
 	return content;
 };
-//#region ====================== 鏌ヨ杩涘害 ======================
-let processId = '';
-const QUERY_PROCESS_INTERVAL = 1000;
-const process = ref('');
-let processTimer = null;
-let finishProcess = true;
+const { clearQueryProcess, process, processId, queryProcess } = useQueryProcess();
 
-const queryProcessApi = async () => {
-	const res = await getQuestionProcess({
-		process_id: processId,
-	}).catch((err) => {
-		process.value = err;
-	});
+//#region ====================== 姝ラ step ======================
+const activeStep = ref(-1);
+const stepList = ref<StepItem[]>([
+	{
+		title: '鎰忓浘鍒嗘瀽涓�...',
+		status: 0,
+	},
+	{
+		title: '鎰忓浘鍒嗘瀽瀹屾垚',
+		status: 1,
+	},
+	{
+		title: '鎬濊�冨浣曟墽琛�:鎸囨爣鏄庣粏鏌ヨ',
+		status: 1,
+	},
+	{
+		title: '鎸囨爣鏄庣粏鏌ヨ瀹屾垚',
+		status: 1,
+	},
+]);
 
-	process.value = res.process;
-	finishProcess = true;
+const resetStep = () => {
+	activeStep.value = -1;
+	stepList.value = [];
 };
 
-const queryProcess = () => {
-	processTimer = setInterval(() => {
-		if (!finishProcess) return;
-		finishProcess = false;
-		queryProcessApi();
-	}, QUERY_PROCESS_INTERVAL);
-};
-
-const clearQueryProcess = () => {
-	process.value = '';
-	clearInterval(processTimer);
+const toggleStepLabel = (item: ChatMessage) => (item.stepIsShow ? '鏀惰捣' : '灞曞紑');
+const toggleStepList = (item: ChatMessage) => {
+	item.stepIsShow = !item.stepIsShow;
 };
 
 //#endregion
 
-let isNextChat = false;
+const DEFAULT_SECTION_A_ID = 'knowledge_base';
 let questionRes = null;
-const questionAi = async (text) => {
-	if (!currentSectionId) {
-		ElMessage.warning('鍙戦�佸け璐ワ紝鏈‘瀹氬簲鐢ㄥ満鏅紒');
+
+let finalCalcSectionAId = null;
+const questionAi = async (text, assistantMsg: ChatMessage) => {
+	let judgeParams = null;
+	if (!preQuestion.value) {
+		// const aiContent = computedMessageList.value.filter((item) => item.role === RoleEnum.assistant);
+		// const lastQuestion = aiContent[aiContent.length - 2]?.content?.origin?.question;
+		// judgeParams = lastQuestion
+		// 	? {
+		// 			prev_question: lastQuestion,
+		// 	  }
+		// 	: {};
+		// 姝e父鍥炵瓟鏆傛椂涓嶉噰鐢�
+		judgeParams = {};
+	} else {
+		judgeParams = {
+			prev_question: preQuestion.value,
+		};
 	}
-	processId = uuidv4();
+	let currentSectionAId = '';
+	if (activeSectionAId.value) {
+		currentSectionAId = activeSectionAId.value;
+		activeSectionAId.value = '';
+	} else {
+		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,
+		// process_id: processId.value,
 		question: text,
 		// FIXME: 鏆傛椂杩欐牱
-		section_a_id: currentSectionId,
+		// section_a_id: currentSectionAId,
 		history_group_id: currentRouteId,
 		raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false,
-		next_chat: isNextChat,
+		...judgeParams,
 	} as any;
+
+	if (activeGroupType.value) {
+		params.group_type = activeGroupType.value;
+	}
 
 	if (currentSampleId) {
 		params.sample_id = currentSampleId;
+		currentSampleId = '';
 	}
 
-	if (currentLLMId) {
-		params.llm_id = currentLLMId;
-	}
-	clearQueryProcess();
-	queryProcess();
-	const res = await QuestionAi(params).finally(() => {
-		clearQueryProcess();
+	// if (currentLLMId) {
+	// 	params.llm_id = currentLLMId;
+	// }
+	// clearQueryProcess();
+	// queryProcess();
+	resetStep();
+	let res = null;
+	await questionStreamByPost(params, (chunkRes) => {
+		if (chunkRes.mode === 'result') {
+			res = chunkRes.value;
+		} else {
+			const stepItem = convertProcessItem(chunkRes);
+			computedMessageList.value.at(-1).stepList.push(stepItem);
+			scrollToBottom();
+		}
+	}).finally(() => {
+		computedMessageList.value.at(-1).stepIsShow = false;
+		resetStep();
 	});
 	questionRes = res;
 	const content = parseContent(res);
@@ -316,23 +430,12 @@
 		values: '',
 	});
 
-const scrollToBottom = () => {
-	if (!chatListDom.value) return;
-	chatListDom.value.lastElementChild?.scrollIntoView();
-};
-let currentSectionId = null;
-let currentSampleId = null;
+let currentSampleId = '';
 
 let currentLLMId = null;
 
-const getAnswerById = async (historyId: string) => {
-	return await GetHistoryAnswer({
-		history_id: historyId,
-	});
-};
-
-const sendChatMessage = async (content: ChatContent = messageContent.value, cb?: any) => {
-	if (!content?.values) return;
+const sendChatMessage = async (content: ChatContent = messageContent.value, cb?: any, isCallExtParams?: any) => {
+	if (!content?.values || isTalking.value || chatListLoading.value) return;
 	const isNewChat = messageList.value.length === 0;
 	if (isNewChat) {
 		if (activeSampleId.value) {
@@ -342,17 +445,19 @@
 		if (activeLLMId.value) {
 			currentLLMId = activeLLMId.value;
 		}
-
-		if (activeSectionAId.value) {
-			currentSectionId = activeSectionAId.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 assistantItem: ChatMessage = {
+			role: RoleEnum.assistant,
+			content: null,
+			state: AnswerState.Null,
+			stepList: [],
+			stepIsShow: true,
+		} as any;
 		// 鍙戦�佸綋鍓�
 		messageList.value.push(userItem);
 		// 娓呯┖杈撳叆妗�
@@ -360,8 +465,17 @@
 
 		// 鍑虹幇鍥炲锛岀疆绌哄嚭鐜扮瓑寰呭姩鐢�
 		messageList.value.push(assistantItem);
+		// 婊氬姩鑷冲綋鍓嶅彂閫佹秷鎭�
+		scrollToBottom();
 
-		resMsgContent = await questionAi(content.values);
+		if (isCallExtParams) {
+			const extRes = await extCallQuery(isCallExtParams);
+			questionRes = extRes;
+			resMsgContent = parseContent(extRes);
+		} else {
+			resMsgContent = await questionAi(content.values, assistantItem);
+		}
+		nextUserMsgEndIndex.value++;
 		if (isNewChat) {
 			const firstResCb = getRoomConfig(currentRouteId, 'firstResCb');
 			firstResCb?.(resMsgContent);
@@ -369,8 +483,14 @@
 			cb?.(resMsgContent);
 		}
 		userItem.historyId = questionRes.history_id;
+		userItem.content.values = questionRes?.question ?? userItem.content.values;
 		assistantItem.historyId = questionRes.history_id;
+		assistantItem.sectionAId = finalCalcSectionAId;
 		appendLastMessageContent(resMsgContent);
+		setTimeout(() => {
+			// 鏀跺埌鍥炲锛岀户缁粴
+			scrollToBottom();
+		}, 300);
 	} catch (error: any) {
 		// appendLastMessageContent({
 		// 	type: AnswerType.Text,
@@ -385,66 +505,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 askMoreClick = (item) => {
-	if (!item.question) return;
-	sendChatMessage({ type: AnswerType.Text, values: item.question });
-};
+const chatListLoading = ref(false);
 
-const fixQuestionClick = (item) => {
-	if (!item.question) return;
-	isNextChat = true;
-	try {
-		sendChatMessage({
-			type: AnswerType.Text,
-			values: item.question,
-		});
-	} finally {
-		isNextChat = false;
-	}
-};
+const { scrollToBottom } = useScrollToBottom({
+	chatListDom: chatListDom,
+});
 
 onMounted(async () => {
-	const res = await QueryHistoryDetail({
-		history_group_id: currentRouteId,
-	});
+	messageList.value = [];
+	// 鍔犺浇鍒濆鏁版嵁
+	chatListLoading.value = true;
 
-	messageList.value = (res.details ?? []).map((item) => {
-		return {
-			historyId: item.history_id,
-			role: RoleEnum.user,
-			content: {
-				type: AnswerType.Text,
-				values: item.question,
-			},
-		} as ChatMessage;
+	await loadRangeData().finally(() => {
+		chatListLoading.value = false;
 	});
-	currentSectionId = res?.details?.[0]?.section_a_id;
-	currentSampleId = res?.details?.[0]?.sample_id;
-	const resList = await Promise.all((res.details ?? []).map((item) => getAnswerById(item.history_id)));
-	let i = 0;
-
-	resList.map((item, index) => {
-		const insertIndex = index + 1 + i;
-		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,
-				  }
-		);
-		i++;
-	});
-
 	if (messageList.value.length === 0) {
 		messageContent.value = {
 			type: AnswerType.Text,
@@ -452,99 +539,79 @@
 		};
 
 		sendChatMessage();
-	}
-});
-let forbidScroll = false;
-watch(
-	messageList,
-	() => {
-		if (forbidScroll) return;
-		nextTick(() => scrollToBottom());
-	},
-	{
-		deep: true,
-	}
-);
-
-onActivated(() => {
-	if (forbidScroll) return;
-	nextTick(() => scrollToBottom());
-});
-
-//#region ====================== 鑱婂ぉ鍐呭鎿嶄綔 ======================
-
-const { toClipboard } = useClipboard();
-
-const copyClick = (item) => {
-	const type = item.content.type;
-	let text = '';
-	if (type === AnswerType.Knowledge) {
-		text = item.content.values?.map((item) => item.answer).join('\n\n') ?? '';
 	} else {
-		text = item.content.values;
+		setTimeout(() => {
+			// 鍒濆鐘舵�佹粴涓�涓�
+			scrollToBottom();
+
+			setTimeout(() => {
+				chatListDom.value.addEventListener('scroll', onChatListScroll);
+			}, 300);
+		}, 300);
 	}
-	ElMessage.success('澶嶅埗鎴愬姛');
-	toClipboard(text);
-};
-
-const likeClick = async (item) => {
-	const toSetState = item.state === AnswerState.Like ? AnswerState.Null : AnswerState.Like;
-	const res = await SetHistoryAnswerState({
-		history_id: item.historyId,
-		answer_state: toSetState,
-	});
-	item.state = toSetState;
-	forbidScroll = true;
-	nextTick(() => {
-		forbidScroll = false;
-	});
-};
-
-const unLikeClick = async (item) => {
-	const toSetState = item.state === AnswerState.Unlike ? AnswerState.Null : AnswerState.Unlike;
-	const res = await SetHistoryAnswerState({
-		history_id: item.historyId,
-		answer_state: toSetState,
-	});
-	item.state = toSetState;
-
-	forbidScroll = true;
-	nextTick(() => {
-		forbidScroll = false;
-	});
-};
-const feedbackPosition = ref({
-	x: 0,
-	y: 0,
 });
-
-const feedbackIsShow = ref(false);
-const feedbackContent = ref('');
-const feedbackPanelRef = ref<HTMLDivElement>(null);
-const currentFeedbackMapItem = ref(null);
-const curFeedbackIndex = ref(0);
-const feedbackClick = async (e, item, index) => {
-	currentFeedbackMapItem.value = item;
-	curFeedbackIndex.value = index;
-	const offsetX = -4;
-	const offsetY = -8;
-	feedbackIsShow.value = true;
-	nextTick(() => {
-		feedbackPosition.value = {
-			x: -feedbackPanelRef.value[index].$el.clientWidth + offsetX,
-			y: -feedbackPanelRef.value[index].$el.clientHeight + offsetY,
-		};
-	});
+//#region ====================== 鍏宠仈鏌ヨ ======================
+const relativeQueryClick = async (val) => {
+	sendChatMessage(
+		{
+			type: AnswerType.Text,
+			values: val.question,
+		},
+		undefined,
+		{
+			history_group_id: currentRouteId,
+			question: val.question,
+			call_ext_id: val.call_ext_id,
+			call_ext_args: val.agrs ? JSON.stringify(val.agrs) : null,
+		}
+	);
 };
-useClickOther(
-	computed(() => feedbackPanelRef.value[curFeedbackIndex.value]),
-	feedbackIsShow,
-	() => {
-		feedbackIsShow.value = false;
-		feedbackContent.value = '';
-	}
-);
 //#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 === null) {
+		currentIndex.value = history_data.value.length - 1;
+	} 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 === 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,
+	unLikeClick,
+	feedbackPosition,
+	feedbackIsShow,
+	feedbackContent,
+	feedbackPanelRef,
+	currentFeedbackMapItem,
+	feedbackClick,
+	askMoreClick,
+	fixQuestionClick,
+	preQuestion,
+	showFixQuestion,
+	showAskMore,
+} = useAssistantContentOpt({
+	sendChatMessage,
+	displayMessageList: computedMessageList,
+});
 
 //#region ====================== 渚ц竟鏍廳rawer ======================
 const drawerIsShow = ref(false);
@@ -553,12 +620,64 @@
 	messageContent.value.values = content;
 };
 //#endregion
+//#region ====================== 鐢ㄦ埛璇㈤棶鐨勯棶棰樿缃负甯哥敤璇� ======================
+//鐢ㄦ埛澶嶅埗闂
+const copyUserClick = () => {};
+//鐢ㄦ埛闂璁剧疆涓哄父鐢ㄨ
+const setCommonQuestionClick = () => {};
+//#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;
+}
+.action {
+	left: 0;
+	padding: 4px;
+	gap: 4px;
+	border-radius: 4px;
+	display: flex;
+	align-items: center;
+	flex-direction: row;
+	position: absolute;
+	top: 0;
+}
+.action {
+	left: 0;
+	padding: 4px;
+	gap: 4px;
+	border-radius: 4px;
+	display: flex;
+	align-items: center;
+	flex-direction: row;
+	position: absolute;
+	top: 0;
+}
 </style>

--
Gitblit v1.9.3