From 28706df7da34b8854cdce96ad89c035eaded6ea9 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期三, 09 四月 2025 15:54:50 +0800
Subject: [PATCH] 完善数字人

---
 src/components/chat/messageList/index.vue |  550 +++---------------------------------------------------
 1 files changed, 38 insertions(+), 512 deletions(-)

diff --git a/src/components/chat/messageList/index.vue b/src/components/chat/messageList/index.vue
index 1491d1d..6029e7b 100644
--- a/src/components/chat/messageList/index.vue
+++ b/src/components/chat/messageList/index.vue
@@ -1,6 +1,6 @@
 <template>
-	<div class="message-list">
-		<div v-for="(item, msgIndex) of msgList" :key="`${item.historyId}_${item.role}`">
+	<div class="message-list pb-10">
+		<div v-for="(item, msgIndex) of msgList" :key="`${item.historyId ?? msgIndex}_${item.role}`">
 			<UserMsg
 				:msg="item"
 				@shareClick="shareClick"
@@ -15,10 +15,11 @@
 				:isLast="msgIndex === msgList.length - 1"
 				@sendChatMessage="sendChatMessage"
 				@shareMsg="shareClick"
+				@stopGenClick="stopGenClick"
 				:isTalking="isTalking"
 			/>
 		</div>
-		<div v-if="showAskMore" class="ml-4 mt-5 pb-10">
+		<div v-if="showAskMore" class="ml-4 mt-5 ">
 			<div class="text-gray-600 mb-5">浣犲彲浠ョ户缁棶鎴戯細</div>
 			<div class="space-y-2 inline-flex flex-col">
 				<div
@@ -35,468 +36,30 @@
 </template>
 
 <script setup lang="ts" name="MessageList">
-import type { CancelTokenSource } from 'axios';
-import axios from 'axios';
-import { orderBy } from 'lodash-es';
-import moment from 'moment';
-import { computed, ref } from 'vue';
-import { convertProcessItem, formatShowTimeYear, useScrollLoad } from '../hooks/useScrollLoad';
-import type { ChatContent } from '../model/types';
-import { AnswerState, AnswerType, RoleEnum, type ChatMessage } from '../model/types';
-import { questionStreamByPost } from '/@/api/ai/chat';
-import { Logger } from '/@/model/logger/Logger';
-
-import { ElMessage } from 'element-plus';
+import { computed, defineProps, type PropType } from 'vue';
 import AssistantMsg from '../assistant/index.vue';
-import ChatContainer from '../components/ChatContainer.vue';
-import ShareLinkDlg from '../components/shareLink/index.vue';
+import type { ChatContent, ChatMessage, ContextHistory } from '../model/types';
+import { RoleEnum } from '../model/types';
 import UserMsg from '../user/index.vue';
-import router from '/@/router';
-import { activeGroupType, activeLLMId, activeRoomId, activeSampleId, isSharePage, roomConfig } from '/@/stores/chatRoom';
-import { useCompRef } from '/@/utils/types';
-import { toMyFixed } from '/@/utils/util';
-const props = defineProps<{
-	msgList: Array<ChatMessage>;
-}>();
-const containerRef = useCompRef(ChatContainer);
-const chatListDom = computed(() => containerRef.value?.chatListDom);
+import { isSharePage } from '/@/stores/chatRoom';
 
-let isTalking = ref(false);
-
-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 preQuestion = ref(null);
-
-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;
-
-	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:
-			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;
-
-		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 {
-		content = {
-			...content,
-			...extraContent,
-		};
-	}
-
-	return content;
-};
-
-let questionRes = null;
-let position = null;
-
-let lastAxiosSource: CancelTokenSource = null;
-const questionAi = async (text) => {
-	let judgeParams = null;
-	if (!preQuestion.value) {
-		judgeParams = {};
-	} else {
-		judgeParams = {
-			prev_question: preQuestion.value,
-		};
-	}
-
-	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 = props.msgList.at(-1);
-
-						// 宸茬粡瑙f瀽杩囦竴娆� reports
-						lastMsg.content.values.push({
-							content: parseContent(res, true, {
-								origin: res,
-							}),
-						});
-					}
-					return;
-					// chunkRes.value = '鍑嗗鏁版嵁鍒嗘瀽';
-				}
-
-				if (chunkRes.mode === 'summary') {
-					const lastMsg = props.msgList.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 = props.msgList.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);
-					}
-					// props.msgList[props.msgList.length - 1] = finalMsg;
-					scrollToBottom();
-					// chunkRes.value = '浣犲彲浠ョ户缁棶鎴�';
-					return;
-				}
-
-				if (chunkRes.mode === 'conclusion') {
-					const lastReport = props.msgList.at(-1)?.content?.values?.at(-1);
-					if (lastReport) {
-						lastReport.conclusion = chunkRes.value;
-						chunkRes.value = '鍒嗘瀽缁撴潫';
-					}
-				}
-
-				if (chunkRes.mode === 'question') {
-					const lastGroup = props.msgList.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
-				// 	props.msgList.at(-1).stepGroup.push({
-				// 		value: [],
-				// 		isShow: true,
-				// 	});
-				// 	lastIsResult = false;
-				// }
-				const lastGroup = props.msgList.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 = props.msgList.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
-				props.msgList.at(-1).stepGroup.forEach((item) => {
-					item.isShow = false;
-				});
-			});
-	});
-
-	await resultP;
-	const content = parseContent(questionRes, true);
-	return content;
-};
-
-const clearMessageContent = () =>
-	(messageContent.value = {
-		type: AnswerType.Text,
-		values: '',
-	});
-
-let currentSampleId = '';
-
-let currentLLMId = null;
-
-const stopGenClick = () => {
-	lastAxiosSource?.cancel();
-	isTalking.value = false;
-	chatListLoading.value = false;
-	props.msgList.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 (!checkCanSend(content)) {
-		return;
-	}
-	const isNewChat = messageList.value.length === 0;
-	if (isNewChat) {
-		if (activeSampleId.value) {
-			currentSampleId = activeSampleId.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) {
-		console.log(error);
-	}
-};
-
-const sendClick = () => {
-	sendChatMessage(messageContent.value);
-};
-
-const { loadRangeData, onChatListScroll, moreIsLoading, updateLoadIndex } = useScrollLoad({
-	container: chatListDom,
-	historyGroupId: currentRouteId,
-	messageList,
-	parseAnswerContent: parseContent,
+const props = defineProps({
+	msgList: {
+		type: Array as PropType<ChatMessage[]>,
+	},
+	isTalking: {
+		type: Boolean,
+		default: false,
+	},
 });
 
-const chatListLoading = ref(true);
-
-const scrollToBottom = () => {
-	containerRef.value?.scrollToBottom();
-};
+const emit = defineEmits({
+	shareClick: (msg: ChatMessage) => true,
+	setCommonQuestionClick: (msg: ChatMessage) => true,
+	sendChatMessage: (msg: ChatContent) => true,
+	askMoreClick: (msg: ContextHistory) => true,
+	stopGenClick: () => true,
+});
 const showAskMore = computed(() => {
 	if (!props.msgList || props.msgList.length === 0) return false;
 	const last = props.msgList.at(-1);
@@ -504,61 +67,24 @@
 	const result = isShow && !isSharePage.value;
 	return result;
 });
-const askMoreClick = (item) => {
-	if (!item.question) return;
-	sendChatMessage({ type: AnswerType.Text, values: item.question });
+const shareClick = (msg: ChatMessage) => {
+	emit('shareClick', msg);
 };
 
-//#region ====================== 鍒嗕韩 ======================
-
-const shareLinkDlgRef = useCompRef(ShareLinkDlg);
-
-const shareClick = async (item: ChatMessage) => {
-	shareLinkDlgRef.value.openShare(item);
+const setCommonQuestionClick = (msg: ChatMessage) => {
+	emit('setCommonQuestionClick', msg);
 };
-//#endregion
 
-//#region ====================== 鐢ㄦ埛璇㈤棶鐨勯棶棰樿缃负甯哥敤璇� ======================
-const setCommonQuestionInfo = ref({});
-
-//鐢ㄦ埛闂璁剧疆涓哄父鐢ㄨ
-const setCommonQuestionClick = (item) => {
-	setCommonQuestionInfo.value = item;
+const sendChatMessage = (msg: ChatContent) => {
+	emit('sendChatMessage', msg);
 };
-//#endregion
+
+const askMoreClick = (msg: ContextHistory) => {
+	emit('askMoreClick', msg);
+};
+
+const stopGenClick = () => {
+	emit('stopGenClick');
+};
 </script>
-<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>
+<style scoped lang="scss"></style>

--
Gitblit v1.9.3