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 |  845 +++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 686 insertions(+), 159 deletions(-)

diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue
index b0d66bc..ff4ec3f 100644
--- a/src/components/chat/Chat.vue
+++ b/src/components/chat/Chat.vue
@@ -1,214 +1,741 @@
 <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 w-[100ch]">
-				<div
-					class="group flex px-4 py-4 hover:bg-slate-100 rounded-lg"
-					v-for="(item, index) of messageList.filter((v) => v.role !== 'system')"
-					:key="index"
-				>
-					<img class="rounded-full size-12 mr-4" :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>
 
-					<div class="flex">
-						<div class="relative" v-if="item.content">
-							<div
-								:class="{ 'bg-[#d8d8ff]': item.role === RoleEnum.assistant, 'bg-white': item.role === RoleEnum.user }"
-								class="prose text-sm rounded-[6px] p-4 leading-relaxed max-w-[100ch]"
-								v-html="md.render(item.content)"
-							></div>
-							<div v-if="item.role === RoleEnum.assistant" class="absolute flex items-center right-0 space-x-2 mr-2 mt-2">
-								<SvgIcon class="cursor-pointer" name="ele-CopyDocument" @click="copyClick(item.content)" />
-								<SvgIcon class="cursor-pointer" name="ywicon icon-dianzan" />
-								<SvgIcon class="cursor-pointer" :size="12" name="ywicon icon-buzan" />
-							</div>
+					<AssistantMsg
+						v-else
+						:msg="item"
+						:msgList="computedMessageList"
+						:isLast="msgIndex === computedMessageList.length - 1"
+						@sendChatMessage="sendChatMessage"
+						@shareMsg="shareClick"
+						:isTalking="isTalking"
+					/>
+				</div>
+				<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
+							v-for="item in computedMessageList.at(-1).content.askMoreList"
+							:key="item.history_id"
+							class="bg-white p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg"
+							@click="askMoreClick(item)"
+						>
+							{{ item.question }}
 						</div>
-
-						<Loding v-else />
 					</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-gray-100 flex justify-center">
-			<PlayBar :isTalking="isTalking" v-model="messageContent" @sendClick="sendOrSave" />
-		</div>
-	</div>
+		<!-- 杈撳叆鍖哄煙 -->
+		<template #input-area>
+			<PlayBar
+				v-model:voicePageIsShow="voicePageIsShow"
+				:isTalking="isTalking"
+				:isHome="false"
+				v-model="messageContent.values"
+				@sendClick="sendClick"
+				@showUpChatClick="showUpChatClick"
+				@stopGenClick="stopGenClick"
+				@showDownChatClick="showDownChatClick"
+				:style="{ width: chatWidth }"
+				:setCommonQuestionInfo="setCommonQuestionInfo"
+			/>
+		</template>
+
+		<!-- 鎶藉眽 -->
+		<template #drawer>
+			<CustomDrawer v-model:isShow="drawerIsShow" @updateChatInput="updateChatInput" />
+			<ShareLinkDlg ref="shareLinkDlgRef" />
+		</template>
+	</ChatContainer>
 </template>
 
 <script setup lang="ts">
-import cryptoJS from 'crypto-js';
-import { ElMessage } from 'element-plus';
-import { nextTick, onMounted, ref, watch } from 'vue';
-import useClipboard from 'vue-clipboard3';
-import Loding from './components/Loding.vue';
-import { md } from './libs/markdown';
-import { RoleEnum, roleImageMap, type ChatMessage } from './types';
+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 { 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';
 
-let apiKey = '';
-let isConfig = ref(false);
+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,
+	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('');
-const chatListDom = ref<HTMLDivElement>();
-const decoder = new TextDecoder('utf-8');
-const roleAlias = { user: 'ME', assistant: 'ChatGPT', system: 'System' };
-const messageList = ref<ChatMessage[]>([
-	// 	{
-	// 		role: RoleEnum.assistant,
-	// 		content: `浣犲ソ锛屾垜鏄疉I璇█妯″瀷锛屾垜鍙互鎻愪緵涓�浜涘父鐢ㄦ湇鍔″拰淇℃伅锛屼緥濡傦細
-	//   1. 缈昏瘧锛氭垜鍙互鎶婁腑鏂囩炕璇戞垚鑻辨枃锛岃嫳鏂囩炕璇戞垚涓枃锛岃繕鏈夊叾浠栦竴浜涜瑷�缈昏瘧锛屾瘮濡傛硶璇�佹棩璇�佽タ鐝墮璇瓑銆�
-	//   2. 鍜ㄨ鏈嶅姟锛氬鏋滀綘鏈変换浣曢棶棰橀渶瑕佸挩璇紝渚嬪鍋ュ悍銆佹硶寰嬨�佹姇璧勭瓑鏂归潰锛屾垜鍙互灏藉彲鑳戒负浣犳彁渚涘府鍔┿��
-	//   3. 闂茶亰锛氬鏋滀綘鎰熷埌瀵傚癁鎴栨棤鑱婏紝鎴戜滑鍙互鑱婁竴浜涙湁瓒g殑璇濋锛屼互鍑忚交浣犵殑鍘嬪姏銆�
-	//   璇峰憡璇夋垜浣犻渶瑕佸摢鏂归潰鐨勫府鍔╋紝鎴戜細鏍规嵁浣犵殑闇�姹傜粰浣犳彁渚涚浉搴旂殑淇℃伅鍜屽缓璁�俙,
-	// 	},
-	// 	{
-	// 		role: RoleEnum.user,
-	// 		content: `浣犲ソ`,
-	// 	},
-]);
 
-onMounted(() => {
-	if (getAPIKey()) {
-		switchConfigStatus();
-	}
-	const inputValue = history.state.inputValue;
-	messageContent.value = inputValue;
-	sendOrSave();
+let messageContent = ref<ChatContent>({
+	type: AnswerType.Text,
+	values: '',
+});
+const currentRoute = router.currentRoute;
+const currentRouteId = currentRoute.value.query.id as string;
+activeRoomId.value = currentRouteId;
+const messageList = ref<ChatMessage[]>([]);
+const computedMessageList = computed(() => {
+	return messageList.value.filter((v) => !!v);
 });
 
-const sendChatMessage = async (content: string = messageContent.value) => {
-	try {
-		isTalking.value = true;
-		// if (messageList.value.length === 0) {
-		// 	messageList.value.pop();
-		// }
-		messageList.value.push({ role: RoleEnum.user, content });
-		clearMessageContent();
-		messageList.value.push({ role: RoleEnum.assistant, content: '' });
+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 { body, status } = await chat(messageList.value, getAPIKey());
-		// if (body) {
-		//   const reader = body.getReader();
-		//   await readStream(reader, status);
-		// }
-		const a = new Promise<string>((resolve) => {
-			setTimeout(() => {
-				resolve('浣犲ソ锛屾垜鏄疉I璇█妯″瀷 ');
-			}, 500);
-		});
-
-		const msg = await a;
-		appendLastMessageContent(msg);
-	} catch (error: any) {
-		appendLastMessageContent(error);
-	} finally {
-		isTalking.value = false;
-	}
+	return {
+		askMoreList,
+		errCode,
+		errMsg,
+		origin,
+	};
 };
 
-const readStream = async (reader: ReadableStreamDefaultReader<Uint8Array>, status: number) => {
-	let partialLine = '';
-
-	while (true) {
-		// eslint-disable-next-line no-await-in-loop
-		const { value, done } = await reader.read();
-		if (done) break;
-
-		const decodedText = decoder.decode(value, { stream: true });
-
-		if (status !== 200) {
-			const json = JSON.parse(decodedText); // start with "data: "
-			const content = json.error.message ?? decodedText;
-			appendLastMessageContent(content);
-			return;
-		}
-
-		const chunk = partialLine + decodedText;
-		const newLines = chunk.split(/\r?\n/);
-
-		partialLine = newLines.pop() ?? '';
-
-		for (const line of newLines) {
-			if (line.length === 0) continue; // ignore empty message
-			if (line.startsWith(':')) continue; // ignore sse comment message
-			if (line === 'data: [DONE]') return; //
-
-			const json = JSON.parse(line.substring(6)); // start with "data: "
-			const content = status === 200 ? json.choices[0].delta.content ?? '' : json.error.message;
-			appendLastMessageContent(content);
-		}
+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);
 
-const appendLastMessageContent = (content: string) => (messageList.value[messageList.value.length - 1].content += content);
+	switch (res.answer_type) {
+		case AnswerType.RecordSet:
+			content = {
+				type: AnswerType.RecordSet,
+				values: res.values,
+			};
+			break;
+		case AnswerType.Text:
+			content = {
+				type: AnswerType.Text,
+				values: res.values ?? res.answer,
+			};
+			break;
+		case AnswerType.Script:
+			content = {
+				type: AnswerType.Script,
+				values: res,
+			};
+			break;
 
-const sendOrSave = () => {
-	if (!messageContent.value.length) return;
-	if (isConfig.value) {
-		if (saveAPIKey(messageContent.value.trim())) {
-			switchConfigStatus();
-		}
-		clearMessageContent();
+		case AnswerType.Knowledge:
+			content = {
+				type: AnswerType.Knowledge,
+				values: res.knowledge,
+			};
+
+			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?.map((item) => {
+					item.reportIsShow = reportIsShow;
+					return item;
+				}),
+			};
+			break;
+		case AnswerType.Url:
+			content = {
+				type: AnswerType.Url,
+				values: res.url,
+			};
+			break;
+		case AnswerType.Map:
+			content = {
+				type: AnswerType.Map,
+				values: res.values,
+			};
+			break;
+		default:
+			content = {
+				type: AnswerType.Text,
+				values: '瑙f瀽澶辫触锛�',
+			};
+			break;
+	}
+	if (!extraContent) {
+		content = {
+			...content,
+			...curExtraContent,
+		};
 	} else {
-		sendChatMessage();
+		content = {
+			...content,
+			...extraContent,
+		};
 	}
+
+	return content;
 };
 
-const clickConfig = () => {
-	if (!isConfig.value) {
-		messageContent.value = getAPIKey();
+let questionRes = null;
+let position = null;
+const preQuestion = ref(null);
+
+let lastAxiosSource: CancelTokenSource = null;
+const questionAi = async (text) => {
+	let judgeParams = null;
+	if (!preQuestion.value) {
+		judgeParams = {};
 	} else {
-		clearMessageContent();
+		judgeParams = {
+			prev_question: preQuestion.value,
+		};
 	}
-	switchConfigStatus();
+
+	const params = {
+		question: text,
+		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 = '';
+	}
+
+	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;
+				});
+			});
+	});
+
+	await resultP;
+	const content = parseContent(questionRes, true);
+	return content;
 };
 
-const getSecretKey = () => 'lianginx';
+const clearMessageContent = () =>
+	(messageContent.value = {
+		type: AnswerType.Text,
+		values: '',
+	});
 
-const saveAPIKey = (apiKey: string) => {
-	if (apiKey.slice(0, 3) !== 'sk-' || apiKey.length !== 51) {
-		alert('API Key 閿欒锛岃妫�鏌ュ悗閲嶆柊杈撳叆锛�');
+let currentSampleId = '';
+
+let currentLLMId = null;
+
+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;
 	}
-	const aesAPIKey = cryptoJS.AES.encrypt(apiKey, getSecretKey()).toString();
-	localStorage.setItem('apiKey', aesAPIKey);
+	if (isTalking.value || chatListLoading.value) {
+		ElMessage.warning('ai 姝e湪鍥炲涓紝璇风◢鍚庡皾璇曟彁闂�');
+		return false;
+	}
 	return true;
 };
 
-const getAPIKey = () => {
-	if (apiKey) return apiKey;
-	const aesAPIKey = localStorage.getItem('apiKey') ?? '';
-	apiKey = cryptoJS.AES.decrypt(aesAPIKey, getSecretKey()).toString(cryptoJS.enc.Utf8);
-	return apiKey;
+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 switchConfigStatus = () => (isConfig.value = !isConfig.value);
+const sendChatMessage = async (content: ChatContent = messageContent.value) => {
+	if (!checkCanSend(content)) {
+		return;
+	}
+	const isNewChat = messageList.value.length === 0;
+	if (isNewChat) {
+		if (activeSampleId.value) {
+			currentSampleId = activeSampleId.value;
+		}
 
-const clearMessageContent = () => (messageContent.value = '');
+		if (activeLLMId.value) {
+			currentLLMId = activeLLMId.value;
+		}
+	}
+	let resMsgContent: ChatContent = null;
 
+	try {
+		const [userItem, assistantItem] = addChatItem(content);
+
+		resMsgContent = await questionAi(content.values);
+
+		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 = () => {
-	if (!chatListDom.value) return;
-	chatListDom.value.lastElementChild.scrollIntoView();
-	// scrollTo(0, chatListDom.value.scrollHeight);
+	containerRef.value?.scrollToBottom();
 };
 
-watch(messageList.value, () => nextTick(() => scrollToBottom()));
+const initHistoryChat = () => {
+	// 鍒濆鐘舵�佹粴涓�涓�
+	scrollToBottom();
 
-//#region ====================== 鑱婂ぉ鍐呭鎿嶄綔 ======================
+	setTimeout(() => {
+		chatListDom.value.addEventListener('scroll', onChatListScroll);
+	}, 300);
+};
 
-const { toClipboard } = useClipboard();
+/**
+ * 鍔犺浇鍒嗕韩鏁版嵁
+ */
+const loadShareData = async () => {
+	const res = await getShareChatJsonByPost({
+		share_id: router.currentRoute.value.query.id as string,
+	});
 
-const copyClick = (content) => {
-	ElMessage.success('澶嶅埗鎴愬姛');
-	toClipboard(content);
+	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 });
+};
+
+//#region ====================== 渚ц竟鏍廳rawer ======================
+const drawerIsShow = ref(false);
+
+const updateChatInput = (content) => {
+	messageContent.value.values = content;
+};
+//#endregion
+//#region ====================== 鐢ㄦ埛璇㈤棶鐨勯棶棰樿缃负甯哥敤璇� ======================
+const setCommonQuestionInfo = ref({});
+
+//鐢ㄦ埛闂璁剧疆涓哄父鐢ㄨ
+const setCommonQuestionClick = (item) => {
+	setCommonQuestionInfo.value = item;
+};
+//#endregion
+
+//#region ====================== 鍒嗕韩 ======================
+
+const shareLinkDlgRef = useCompRef(ShareLinkDlg);
+
+const shareClick = async (item: ChatMessage) => {
+	shareLinkDlgRef.value.openShare(item);
 };
 //#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