From 1d0eec6da86e3e9bcbf002d1a22142586fe023a3 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期二, 31 十二月 2024 11:34:15 +0800
Subject: [PATCH] 拆分组件 assistantMsg.vue

---
 src/components/chat/Chat.vue |  919 ++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 584 insertions(+), 335 deletions(-)

diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue
index 90d3c0f..ff4ec3f 100644
--- a/src/components/chat/Chat.vue
+++ b/src/components/chat/Chat.vue
@@ -1,86 +1,33 @@
 <template>
-	<div class="flex flex-col h-full">
-		<div class="h-full flex flex-col items-center overflow-y-auto">
-			<div ref="chatListDom" class="h-full" :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"
-				>
-					<img
-						class="rounded-full size-12 flex-0"
-						:class="{ 'mr-4': item.role === RoleEnum.assistant, 'ml-4': item.role === RoleEnum.user }"
-						:src="roleImageMap[item.role]"
-						alt=""
-						srcset=""
+	<ChatContainer
+		:loading="chatListLoading"
+		:more-is-loading="moreIsLoading"
+		:is-share-page="isSharePage"
+		:chat-width="chatWidth"
+		ref="containerRef"
+	>
+		<!-- 娑堟伅鍒楄〃 -->
+		<template #message-list>
+			<template v-if="computedMessageList?.length > 0">
+				<div v-for="(item, msgIndex) of computedMessageList" :key="`${item.historyId}_${item.role}`">
+					<UserMsg
+						:msg="item"
+						@shareClick="shareClick"
+						@setCommonQuestion="setCommonQuestionClick"
+						v-if="item.role === RoleEnum.user"
+					></UserMsg>
+
+					<AssistantMsg
+						v-else
+						:msg="item"
+						:msgList="computedMessageList"
+						:isLast="msgIndex === computedMessageList.length - 1"
+						@sendChatMessage="sendChatMessage"
+						@shareMsg="shareClick"
+						:isTalking="isTalking"
 					/>
-					<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="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="text-red-500 w-full">{{ item.content.msg }}</div>
-									<component v-else :is="answerTypeMapCom[item.content.type]" :data="item.content.values" :originData="item" />
-								</div>
-
-								<div v-if="item.role === RoleEnum.assistant" 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]" @click="copyClick(item)" />
-									</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 ywicon icon-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]"
-												@click="unLikeClick(item)"
-											/>
-										</div>
-									</template>
-
-									<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]"
-											@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>
-								</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 pb-10">
 					<div class="text-gray-600 mb-5">浣犲彲浠ョ户缁棶鎴戯細</div>
 					<div class="space-y-2 inline-flex flex-col">
 						<div
@@ -93,65 +40,114 @@
 						</div>
 					</div>
 				</div>
-			</div>
-		</div>
+			</template>
+			<el-empty v-else-if="isSharePage && !chatListLoading" :image-size="200">
+				<template #description>
+					<span class="text-[15px]">鍒嗕韩鐨勫璇濅笉瀛樺湪鎴栧凡澶辨晥</span>
+				</template>
+			</el-empty>
+		</template>
 
-		<div class="sticky bottom-0 w-full p-6 pb-8 bg-[rgb(247,248,250)] flex justify-center">
+		<!-- 杈撳叆鍖哄煙 -->
+		<template #input-area>
 			<PlayBar
+				v-model:voicePageIsShow="voicePageIsShow"
 				:isTalking="isTalking"
+				:isHome="false"
 				v-model="messageContent.values"
-				@sendClick="sendChatMessage"
+				@sendClick="sendClick"
+				@showUpChatClick="showUpChatClick"
+				@stopGenClick="stopGenClick"
+				@showDownChatClick="showDownChatClick"
 				:style="{ width: chatWidth }"
-			></PlayBar>
-		</div>
-	</div>
+				:setCommonQuestionInfo="setCommonQuestionInfo"
+			/>
+		</template>
+
+		<!-- 鎶藉眽 -->
+		<template #drawer>
+			<CustomDrawer v-model:isShow="drawerIsShow" @updateChatInput="updateChatInput" />
+			<ShareLinkDlg ref="shareLinkDlgRef" />
+		</template>
+	</ChatContainer>
 </template>
 
 <script setup lang="ts">
-import { ElMessage } from 'element-plus';
-import { computed, nextTick, onActivated, onMounted, ref, watch } from 'vue';
-import useClipboard from 'vue-clipboard3';
-import Loding from './components/Loding.vue';
+import type { CancelTokenSource } from 'axios';
+import axios from 'axios';
+import { orderBy } from 'lodash-es';
+import moment from 'moment';
+import { computed, onActivated, onMounted, ref } from 'vue';
+import { loadAmisSource } from '../amis/load';
+import { convertProcessItem, convertProcessToStep, formatShowTimeYear, useScrollLoad } from './hooks/useScrollLoad';
 import type { ChatContent } from './model/types';
-import { AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage, AnswerState } from './model/types';
-import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, SetHistoryAnswerState, getQuestionProcess } from '/@/api/ai/chat';
+import { AnswerState, AnswerType, RoleEnum, type ChatMessage } from './model/types';
+import { getShareChatJsonByPost, questionStreamByPost } 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 { ElMessage } from 'element-plus';
+import AssistantMsg from './assistant/index.vue';
+import ChatContainer from './components/ChatContainer.vue';
+import ShareLinkDlg from './components/shareLink/index.vue';
+import UserMsg from './user/index.vue';
 import router from '/@/router';
-import { activeChatRoom, activeLLMId, activeRoomId, activeSampleId, activeSectionAId, roomConfig } from '/@/stores/chatRoom';
-import { v4 as uuidv4 } from 'uuid';
-import _ from 'lodash';
-import { ErrorCode } from '/@/utils/request';
-import FeedbackPanel from './components/FeedbackPanel.vue';
-import { useClickOther } from '/@/hooks/useClickOther';
-
+import {
+	activeChatRoom,
+	activeGroupType,
+	activeLLMId,
+	activeRoomId,
+	activeSampleId,
+	isSharePage,
+	roomConfig,
+} from '/@/stores/chatRoom';
+import emitter from '/@/utils/mitt';
+import { useCompRef } from '/@/utils/types';
+import { toMyFixed } from '/@/utils/util';
+const containerRef = useCompRef(ChatContainer);
+const chatListDom = computed(() => containerRef.value?.chatListDom);
 const chatWidth = '75%';
-
+const voicePageIsShow = ref(false);
 let isTalking = ref(false);
+
 let messageContent = ref<ChatContent>({
 	type: AnswerType.Text,
 	values: '',
 });
 const currentRoute = router.currentRoute;
 const currentRouteId = currentRoute.value.query.id as string;
-const chatListDom = ref<HTMLDivElement>();
+activeRoomId.value = currentRouteId;
 const messageList = ref<ChatMessage[]>([]);
 const computedMessageList = computed(() => {
-	return messageList.value.filter((v) => v && v.role !== RoleEnum.system);
+	return messageList.value.filter((v) => !!v);
 });
 
-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 parseExtraContent = (res) => {
+	if (!res) return {};
+	const askMoreList = orderBy(res.context_history, [(item) => Number(item.radio)], ['desc']);
+	const errCode = res?.err_code;
+	const errMsg = res?.json_msg;
+	const origin = res;
 
-const parseContent = (res) => {
+	return {
+		askMoreList,
+		errCode,
+		errMsg,
+		origin,
+	};
+};
+
+const parseContent = (res, reportIsShow = false, extraContent?) => {
 	if (!res) return null;
 	let content: ChatContent = {
 		type: AnswerType.Text,
 		values: '瑙f瀽澶辫触锛�',
 	};
+	if (res.type) {
+		res.answer_type = res.type;
+	}
+	const curExtraContent = parseExtraContent(res);
 
 	switch (res.answer_type) {
 		case AnswerType.RecordSet:
@@ -166,6 +162,12 @@
 				values: res.values ?? res.answer,
 			};
 			break;
+		case AnswerType.Script:
+			content = {
+				type: AnswerType.Script,
+				values: res,
+			};
+			break;
 
 		case AnswerType.Knowledge:
 			content = {
@@ -174,10 +176,22 @@
 			};
 
 			break;
+		case AnswerType.Report:
+			content = {
+				type: AnswerType.Report,
+				values: (res?.reports ?? []).map((item) => ({
+					content: parseContent(item, reportIsShow, { origin: item, conclusion: item.conclusion ?? [] }),
+				})),
+			};
+			break;
+
 		case AnswerType.Summary:
 			content = {
 				type: AnswerType.Summary,
-				values: res.summary,
+				values: res.summary?.map((item) => {
+					item.reportIsShow = reportIsShow;
+					return item;
+				}),
 			};
 			break;
 		case AnswerType.Url:
@@ -199,74 +213,230 @@
 			};
 			break;
 	}
-	content.askMoreList = _.orderBy(res.context_history, [(item) => Number(item.radio)], ['desc']);
-	content.errCode = res?.err_code;
-	content.msg = res?.json_msg;
-	content.origin = res;
+	if (!extraContent) {
+		content = {
+			...content,
+			...curExtraContent,
+		};
+	} else {
+		content = {
+			...content,
+			...extraContent,
+		};
+	}
+
 	return content;
 };
-//#region ====================== 鏌ヨ杩涘害 ======================
-let processId = '';
-const QUERY_PROCESS_INTERVAL = 1000;
-const process = ref('');
-let processTimer = null;
-let finishProcess = true;
-
-const queryProcessApi = async () => {
-	const res = await getQuestionProcess({
-		process_id: processId,
-	}).catch((err) => {
-		process.value = err;
-	});
-
-	process.value = res.process;
-	finishProcess = true;
-};
-
-const queryProcess = () => {
-	processTimer = setInterval(() => {
-		if (!finishProcess) return;
-		finishProcess = false;
-		queryProcessApi();
-	}, QUERY_PROCESS_INTERVAL);
-};
-
-const clearQueryProcess = () => {
-	process.value = '';
-	clearInterval(processTimer);
-};
-
-//#endregion
 
 let questionRes = null;
+let position = null;
+const preQuestion = ref(null);
+
+let lastAxiosSource: CancelTokenSource = null;
 const questionAi = async (text) => {
-	if (!currentSectionId) {
-		ElMessage.warning('鍙戦�佸け璐ワ紝鏈‘瀹氬簲鐢ㄥ満鏅紒');
+	let judgeParams = null;
+	if (!preQuestion.value) {
+		judgeParams = {};
+	} else {
+		judgeParams = {
+			prev_question: preQuestion.value,
+		};
 	}
-	processId = uuidv4();
+
 	const params = {
-		process_id: processId,
 		question: text,
-		// FIXME: 鏆傛椂杩欐牱
-		section_a_id: currentSectionId,
 		history_group_id: currentRouteId,
 		raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false,
+		...judgeParams,
 	} as any;
+
+	if (position) {
+		const longitude = position.coords.longitude;
+		const latitude = position.coords.latitude;
+		params.cur_pos = [longitude, latitude].join(',');
+	}
+
+	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();
+	let lastTimestamp = new Date().getTime();
+	questionRes = {};
+	let lastIsResult = false;
+	const resultP = new Promise((resolve, reject) => {
+		const currentSource = axios.CancelToken.source();
+		lastAxiosSource = currentSource;
+
+		const getResReport = () => {
+			const resReport = {
+				answer_type: AnswerType.Report,
+				reports: [],
+			};
+			return resReport;
+		};
+		const checkReportEmpty = () => {
+			const isEmpty = !questionRes?.reports || questionRes?.reports?.length === 0;
+
+			return isEmpty;
+		};
+		questionStreamByPost(
+			params,
+			(chunkRes) => {
+				Logger.info('chunk response锛歕n\n' + JSON.stringify(chunkRes));
+
+				if (chunkRes.mode === 'result') {
+					lastIsResult = true;
+					const res = chunkRes.value;
+
+					if (checkReportEmpty()) {
+						const resReport = getResReport();
+						resReport.reports.push(res);
+						questionRes = resReport;
+						resolve(resReport);
+					} else {
+						const lastMsg = computedMessageList.value.at(-1);
+
+						// 宸茬粡瑙f瀽杩囦竴娆� reports
+						lastMsg.content.values.push({
+							content: parseContent(res, true, {
+								origin: res,
+							}),
+						});
+					}
+					return;
+					// chunkRes.value = '鍑嗗鏁版嵁鍒嗘瀽';
+				}
+
+				if (chunkRes.mode === 'summary') {
+					const lastMsg = computedMessageList.value.at(-1);
+					const extraContent = parseExtraContent(chunkRes.value);
+					const isReportEmpty = checkReportEmpty();
+					// 娌℃湁缁忚繃 result 鎶ュ憡杩樻病鍒濆鍖�
+					if (isReportEmpty) {
+						const resReport = getResReport();
+						questionRes = resReport;
+					}
+					// 姝ゅ璇濆凡缁忓姞鍏ュ埌瀵硅瘽鍒楄〃
+					if (lastMsg.content?.values && extraContent) {
+						for (const key in extraContent) {
+							if (Object.prototype.hasOwnProperty.call(extraContent, key)) {
+								const value = extraContent[key];
+								if (!lastMsg.content[key] || (Array.isArray(lastMsg.content[key]) && lastMsg.content[key].length === 0)) {
+									lastMsg.content[key] = value;
+								}
+							}
+						}
+
+						lastMsg.historyId = chunkRes.value.history_id;
+						const userMsg = computedMessageList.value.at(-2);
+						userMsg.historyId = chunkRes.value.history_id;
+						userMsg.content.values = chunkRes.value.question;
+					}
+
+					if (Object.keys(questionRes).length === 0) {
+						questionRes = chunkRes.value;
+					}
+
+					// 姝ゅ璇濊繕鏈姞鍏ュ埌瀵硅瘽鍒楄〃
+					if (!lastMsg.content?.values && questionRes) {
+						questionRes = {
+							...questionRes,
+							...chunkRes.value,
+						};
+					}
+
+					if (isReportEmpty) {
+						resolve(questionRes);
+					}
+					// computedMessageList.value[computedMessageList.value.length - 1] = finalMsg;
+					scrollToBottom();
+					// chunkRes.value = '浣犲彲浠ョ户缁棶鎴�';
+					return;
+				}
+
+				if (chunkRes.mode === 'conclusion') {
+					const lastReport = computedMessageList.value.at(-1)?.content?.values?.at(-1);
+					if (lastReport) {
+						lastReport.conclusion = chunkRes.value;
+						chunkRes.value = '鍒嗘瀽缁撴潫';
+					}
+				}
+
+				if (chunkRes.mode === 'question') {
+					const lastGroup = computedMessageList.value.at(-1).stepGroup.at(-1);
+					const stepList = lastGroup?.value ?? [];
+					const lastStepItem = stepList.at(-1);
+					if (!lastStepItem.subStep) {
+						lastStepItem.subStep = [];
+					}
+					lastStepItem.subStep.push({
+						type: chunkRes.value.type,
+						data: chunkRes.value,
+					});
+					scrollToBottom();
+					return;
+				}
+				// 鏆傛椂涓嶈�冭檻澶氫釜 report鎯呭喌
+
+				// if (lastIsResult && chunkRes.mode !== 'finish') {
+				// 	// 寮�濮嬪鍔犳柊鐨� stepGroup
+				// 	computedMessageList.value.at(-1).stepGroup.push({
+				// 		value: [],
+				// 		isShow: true,
+				// 	});
+				// 	lastIsResult = false;
+				// }
+				const lastGroup = computedMessageList.value.at(-1).stepGroup.at(-1);
+				const stepList = lastGroup?.value ?? [];
+				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;
+				} else {
+					const stepGroup = computedMessageList.value.at(-1).stepGroup;
+					if (stepGroup.length > 1) {
+						const lastStepList = stepGroup.at(-2).value;
+						lastStepList.at(-1).ms = ms;
+					}
+				}
+				lastTimestamp = currentTimeStamp;
+				const stepItem = convertProcessItem(chunkRes);
+
+				stepList.push(stepItem);
+				// 寮哄埗瑙﹀彂鏇存柊
+
+				scrollToBottom();
+			},
+			{
+				cancelToken: currentSource.token,
+			}
+		)
+			.catch((err) => {
+				throw err;
+			})
+			.finally(() => {
+				isTalking.value = false;
+				// 鏀惰捣鎵�鏈� stepGroup
+				computedMessageList.value.at(-1).stepGroup.forEach((item) => {
+					item.isShow = false;
+				});
+			});
 	});
-	questionRes = res;
-	const content = parseContent(res);
+
+	await resultP;
+	const content = parseContent(questionRes, true);
 	return content;
 };
 
@@ -276,24 +446,61 @@
 		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 stopGenClick = () => {
+	lastAxiosSource?.cancel();
+	isTalking.value = false;
+	chatListLoading.value = false;
+
+	computedMessageList.value.at(-1).isStopMsg = true;
+};
+
+const checkCanSend = (content: ChatContent = messageContent.value) => {
+	if (!content?.values) {
+		return false;
+	}
+	if (isTalking.value || chatListLoading.value) {
+		ElMessage.warning('ai 姝e湪鍥炲涓紝璇风◢鍚庡皾璇曟彁闂�');
+		return false;
+	}
+	return true;
+};
+
+const addChatItem = (content: ChatContent) => {
+	isTalking.value = true;
+	const userItem: ChatMessage = { role: RoleEnum.user, content, isChecked: false } as any;
+	const assistantItem: ChatMessage = {
+		role: RoleEnum.assistant,
+		content: {
+			type: AnswerType.Report,
+		},
+		state: AnswerState.Null,
+		stepGroup: [
+			{
+				value: [],
+				isShow: true,
+			},
+		],
+		isStopMsg: false,
+		isChecked: false,
+	} as any;
+	messageList.value.push(userItem);
+	clearMessageContent();
+
+	messageList.value.push(assistantItem);
+	scrollToBottom();
+	return [userItem, assistantItem];
 };
 
 const sendChatMessage = async (content: ChatContent = messageContent.value) => {
-	if (!content?.values) return;
-	if (messageList.value.length === 0) {
+	if (!checkCanSend(content)) {
+		return;
+	}
+	const isNewChat = messageList.value.length === 0;
+	if (isNewChat) {
 		if (activeSampleId.value) {
 			currentSampleId = activeSampleId.value;
 		}
@@ -301,192 +508,234 @@
 		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;
-		// 鍙戦�佸綋鍓�
-		messageList.value.push(userItem);
-		// 娓呯┖杈撳叆妗�
-		clearMessageContent();
+		const [userItem, assistantItem] = addChatItem(content);
 
-		// 鍑虹幇鍥炲锛岀疆绌哄嚭鐜扮瓑寰呭姩鐢�
-		messageList.value.push(assistantItem);
-
-		let resMsgContent: ChatContent = null;
 		resMsgContent = await questionAi(content.values);
-		userItem.historyId = questionRes.history_id;
-		assistantItem.historyId = questionRes.history_id;
-		appendLastMessageContent(resMsgContent);
-	} catch (error: any) {
-		// appendLastMessageContent({
-		// 	type: AnswerType.Text,
-		// 	values: '鍙戠敓閿欒锛�',
-		// });
-	} finally {
-		isTalking.value = false;
-	}
-};
-const appendLastMessageContent = (content: ChatContent) => {
-	if (messageList.value.at(-1)) {
-		messageList.value.at(-1).content = content;
-	}
+
+		updateLoadIndex();
+
+		userItem.historyId = questionRes?.history_id;
+		userItem.content.values = questionRes?.question ?? userItem.content.values;
+		assistantItem.historyId = questionRes?.history_id;
+		const currentTime = formatShowTimeYear(moment().format('YYYY-MM-DD HH:mm:ss'));
+		assistantItem.createTime = currentTime;
+		assistantItem.content = resMsgContent;
+		setTimeout(() => {
+			// 鏀跺埌鍥炲锛岀户缁粴
+			scrollToBottom();
+		}, 300);
+	} catch (error: any) {}
 };
 
+const sendClick = () => {
+	sendChatMessage(messageContent.value);
+};
+
+const { loadRangeData, onChatListScroll, moreIsLoading, updateLoadIndex } = useScrollLoad({
+	container: chatListDom,
+	historyGroupId: currentRouteId,
+	messageList,
+	parseAnswerContent: parseContent,
+});
+
+const chatListLoading = ref(true);
+
+onActivated(() => {
+	emitter.emit('updateHeaderTitle', activeChatRoom.value?.title ?? '');
+});
+
+const initNewChat = () => {
+	messageContent.value = {
+		type: AnswerType.Text,
+		values: activeChatRoom.value?.title,
+	};
+	sendChatMessage();
+};
+const scrollToBottom = () => {
+	containerRef.value?.scrollToBottom();
+};
+
+const initHistoryChat = () => {
+	// 鍒濆鐘舵�佹粴涓�涓�
+	scrollToBottom();
+
+	setTimeout(() => {
+		chatListDom.value.addEventListener('scroll', onChatListScroll);
+	}, 300);
+};
+
+/**
+ * 鍔犺浇鍒嗕韩鏁版嵁
+ */
+const loadShareData = async () => {
+	const res = await getShareChatJsonByPost({
+		share_id: router.currentRoute.value.query.id as string,
+	});
+
+	const msgValue = res?.values;
+	if (!msgValue) {
+		messageList.value = [];
+		return;
+	}
+	const userMsg: ChatMessage = {
+		historyId: msgValue.history_id,
+		role: RoleEnum.user,
+		content: {
+			type: AnswerType.Text,
+			values: msgValue.question,
+		},
+		isChecked: false,
+	};
+
+	const assistantMsg: ChatMessage = {
+		historyId: msgValue.history_id,
+		role: RoleEnum.assistant,
+		content: parseContent(msgValue),
+		stepGroup: (msgValue?.reports ?? []).map((item) => ({
+			value: convertProcessToStep(item?.exec_process),
+			isShow: false,
+		})),
+		isStopMsg: false,
+
+		conclusion: msgValue.conclusion ?? [],
+		isChecked: false,
+	};
+	messageList.value = [userMsg, assistantMsg];
+};
+
+onMounted(async () => {
+	messageList.value = [];
+	chatListLoading.value = true;
+	if (isSharePage.value) {
+		await loadShareData().finally(() => {
+			chatListLoading.value = false;
+		});
+	} else {
+		await loadRangeData().finally(() => {
+			chatListLoading.value = false;
+		});
+	}
+	setTimeout(() => {
+		emitter.emit('updateHeaderTitle', activeChatRoom.value?.title ?? '');
+	}, 300);
+
+	if (messageList.value.length === 0) {
+		initNewChat();
+	} else {
+		if (!isSharePage.value) {
+			setTimeout(() => {
+				initHistoryChat();
+			}, 300);
+		}
+	}
+	loadAmisSource();
+});
+
+//#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 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;
+	const result = isShow && !isSharePage.value;
+	return result;
+});
 const askMoreClick = (item) => {
 	if (!item.question) return;
 	sendChatMessage({ type: AnswerType.Text, values: item.question });
 };
 
-onMounted(async () => {
-	const res = await QueryHistoryDetail({
-		history_group_id: currentRouteId,
-	});
+//#region ====================== 渚ц竟鏍廳rawer ======================
+const drawerIsShow = ref(false);
 
-	messageList.value = (res.details ?? []).map((item) => {
-		return {
-			historyId: item.history_id,
-			role: RoleEnum.user,
-			content: {
-				type: AnswerType.Text,
-				values: item.question,
-			},
-		} as ChatMessage;
-	});
-	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,
-			values: activeChatRoom.value.title,
-		};
-
-		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;
-	}
-	ElMessage.success('澶嶅埗鎴愬姛');
-	toClipboard(text);
+const updateChatInput = (content) => {
+	messageContent.value.values = content;
 };
+//#endregion
+//#region ====================== 鐢ㄦ埛璇㈤棶鐨勯棶棰樿缃负甯哥敤璇� ======================
+const setCommonQuestionInfo = ref({});
 
-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 setCommonQuestionClick = (item) => {
+	setCommonQuestionInfo.value = item;
 };
+//#endregion
 
-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;
+//#region ====================== 鍒嗕韩 ======================
 
-	forbidScroll = true;
-	nextTick(() => {
-		forbidScroll = false;
-	});
+const shareLinkDlgRef = useCompRef(ShareLinkDlg);
+
+const shareClick = async (item: ChatMessage) => {
+	shareLinkDlgRef.value.openShare(item);
 };
-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,
-		};
-	});
-};
-useClickOther(
-	computed(() => feedbackPanelRef.value[curFeedbackIndex.value]),
-	feedbackIsShow,
-	() => {
-		feedbackIsShow.value = false;
-		feedbackContent.value = '';
-	}
-);
 //#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) {
+	min-height: 20px;
+}
+
+:deep(.el-step:last-of-type .el-step__description) {
+	// display: none;
+}
 </style>

--
Gitblit v1.9.3