From ac95608283df6a73a4fc55af73a751a9f70b1660 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期五, 20 十二月 2024 11:35:36 +0800
Subject: [PATCH] isTalking.value = false;

---
 src/components/chat/Chat.vue |  937 +++++++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 720 insertions(+), 217 deletions(-)

diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue
index d606640..40c2553 100644
--- a/src/components/chat/Chat.vue
+++ b/src/components/chat/Chat.vue
@@ -1,51 +1,183 @@
 <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
-						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"
-					>
+	<ChatContainer
+		:loading="chatListLoading"
+		:more-is-loading="moreIsLoading"
+		:is-share-page="isSharePage"
+		:is-share-check="isShareCheck"
+		: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"
+						@copyMsg="copyClick"
+						@shareClick="shareClick"
+						@setCommonQuestion="setCommonQuestionClick"
+						v-if="item.role === RoleEnum.user"
+					></UserMsg>
+
+					<div v-else class="flex px-4 py-6 rounded-lg relative" :class="{ 'px-10': isShareCheck }">
+						<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 }"
+							:class="{ 'mr-4': item.role === RoleEnum.assistant }"
 							:src="roleImageMap[item.role]"
 							alt=""
 							srcset=""
 						/>
-						<div class="flex-auto flex" :class="{ 'justify-end': item.role === RoleEnum.user }">
+						<div class="flex-auto flex">
 							<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="flex-column w-full">
+								<div class="w-full">
+									<div class="rounded-[6px] p-4 leading-relaxed bg-white">
+										<!-- #region ====================== 娑堟伅鍐呭 ======================-->
+										<!-- <template v-if="item.content?.values"> -->
+										<!-- #region ====================== 鎶ラ敊淇℃伅 ======================-->
+										<div v-if="item.content?.errCode === ErrorCode.Message" class="flex-column w-full">
 											<p class="text-red-500">
 												{{ item.content.errMsg }}
 											</p>
-											<div class="mt-5 flex items-center" v-if="showFixQuestion(item)">
-												<div class="text-gray-600 flex-0">
-													{{ item.content.origin.err_json.fix_question.title + '锛�' }}
+											<div class="mt-3 flex" v-if="showFixQuestion(item)">
+												<div class="text-gray-600 flex-0 mb-auto py-3">
+													{{ '鐚滀綘鎯抽棶锛�' }}
 												</div>
-												<div class="ml-1 space-x-2 inline-flex flex-wrap">
+												<div class="flex-auto space-x-2 space-y-1 inline-flex flex-wrap items-center">
 													<div
-														v-for="fixItem in item.content.origin.err_json.fix_question?.values"
+														v-for="fixItem in item.content.origin?.sample_question"
 														:key="fixItem"
-														class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg"
+														class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg first-of-type:ml-2 first-of-type:mt-1"
 														@click="fixQuestionClick(fixItem, item.content.origin)"
 													>
-														{{ fixItem.title }}
+														{{ fixItem }}
 													</div>
 												</div>
 											</div>
 										</div>
+										<!-- #endregion -->
+										<!-- #region ====================== 鍥炵瓟缁勪欢 ======================-->
 										<template v-else>
-											<component :is="answerTypeMapCom[item.content.type]" :data="item.content.values" :originData="item" />
+											<template v-if="item.content.type === AnswerType.Report">
+												<div v-for="(num, index) in item?.stepGroup?.length" :key="index">
+													<!-- #region ====================== 鎰忓浘鍒嗘瀽 ======================-->
+													<div class="flex flex-col" v-if="item?.stepGroup?.[index]?.value?.length > 0">
+														<!-- #region ====================== 鎰忓浘鍒嗘瀽 ======================-->
+														<div class="flex items-center">
+															<span class="mr-2">鎰忓浘鍒嗘瀽锛�</span>
+															<div
+																@click="toggleStepList(item?.stepGroup?.[index])"
+																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?.stepGroup?.[index]) }}
+																</span>
+																<span
+																	class="ywifont"
+																	:class="{
+																		'ywicon-unfold': !item?.stepGroup?.[index].isShow,
+																		'ywicon-fold': item?.stepGroup?.[index].isShow,
+																	}"
+																></span>
+															</div>
+														</div>
+														<!-- #endregion -->
 
+														<!-- #region ====================== 杩囩▼杈撳嚭 ======================-->
+														<el-steps v-show="item?.stepGroup?.[index].isShow" class="mt-3" direction="vertical" :active="activeStep">
+															<el-step
+																:key="`template-${stepIndex}`"
+																v-for="(subItem, stepIndex) in item?.stepGroup?.[index].value"
+																:title="subItem.title"
+																:status="stepEnumMap[subItem.status]"
+															>
+																<template
+																	#icon
+																	v-if="
+																		stepIndex + 1 === item?.stepGroup?.[index].value.length &&
+																		isTalking &&
+																		msgIndex === computedMessageList.length - 1
+																	"
+																>
+																	<span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
+																</template>
+																<template #title>
+																	<span class="">
+																		{{ subItem.title }}
+
+																		<span v-if="subItem.ms" class="text-green-600">{{ `锛�${subItem.ms}锛塦 }}</span></span
+																	>
+																</template>
+
+																<template #description v-if="subItem?.subStep?.length > 0">
+																	<div class="my-1 flex flex-col gap-1 text-[14px]">
+																		<div
+																			:key="`${item.historyId}-${stepIndex + 1}-${multiChatIndex + 1}`"
+																			v-for="(multiChatItem, multiChatIndex) in subItem.subStep"
+																		>
+																			<component
+																				v-if="multiChatItem.type === MultiChatType.Select"
+																				:order="`${stepIndex + 1}-${multiChatIndex + 1}`"
+																				:item="multiChatItem"
+																				:is="multiChatTypeMapCom[multiChatItem.type]"
+																				:disabled="
+																					!(
+																						stepIndex + 1 === item?.stepGroup?.[index].value.length &&
+																						isTalking &&
+																						msgIndex === computedMessageList.length - 1
+																					)
+																				"
+																			/>
+																			<component
+																				v-else-if="multiChatItem.type === MultiChatType.Result"
+																				:is="answerTypeMapCom['summary']"
+																				:data="multiChatItem.data.content.values"
+																				:originData="multiChatItem.data"
+																			/>
+																			<div v-else-if="multiChatItem.type === MultiChatType.Summary" 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 multiChatItem.data.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>
+																				</div>
+																			</div>
+																		</div>
+																	</div>
+																</template>
+															</el-step>
+														</el-steps>
+														<!-- #endregion -->
+													</div>
+													<!-- #endregion -->
+
+													<component
+														v-if="item.content?.values?.[index]"
+														:reportIndex="index"
+														:conclusion="item.content.values[index].conclusion"
+														:is="answerTypeMapCom[item.content.values[index].content.type]"
+														:data="item.content.values[index].content.values"
+														:originData="item.content.values[index]"
+														:historyId="item.historyId"
+														:isTalking="isTalking && msgIndex === computedMessageList.length - 1"
+													/>
+												</div>
+											</template>
+											<component
+												v-else
+												:historyId="item.historyId"
+												:conclusion="item.conclusion"
+												:is="answerTypeMapCom[item.content.type]"
+												:data="item.content.values"
+												:originData="item"
+												:isTalking="isTalking && msgIndex === computedMessageList.length - 1"
+											/>
 											<div
 												v-if="item.role === RoleEnum.assistant && item.content.origin?.ext_call_list"
 												class="flex font-bold items-center mt-6"
@@ -63,138 +195,229 @@
 												</div>
 											</div>
 										</template>
-									</div>
+										<!-- #endregion -->
+										<!-- </template> -->
 
-									<!-- 鎿嶄綔 -->
-									<div v-if="item.role === RoleEnum.assistant" class="absolute flex items-center right-0 mr-4 mt-2 space-x-2">
+										<!-- #endregion -->
+										<!-- #region ====================== 闄勫姞鍐呭 ======================-->
+										<!-- #region ====================== 鍋滄 ======================-->
+										<span v-if="item.isStopMsg && item?.role === RoleEnum.assistant" class="text-gray-400 text-[12px]"
+											>锛堝凡鍋滄锛�</span
+										>
+										<!-- parseContent 杩斿洖涓� null -->
+										<p v-if="!item.content && !isTalking && !item.isStopMsg" class="text-red-500">鏆傛棤鏁版嵁</p>
+										<!-- #endregion -->
+										<!-- #endregion -->
+									</div>
+									<!-- #region ====================== ai 娑堟伅鎿嶄綔 ======================-->
+									<div
+										v-if="item.role === RoleEnum.assistant && item.content?.values && !isSharePage && !isShareCheck"
+										class="absolute flex items-center right-0 mr-4 mt-2 space-x-2"
+									>
 										<div
 											class="flex items-center justify-center size-[15px]"
 											v-if="item.content?.type === AnswerType.Text || item.content?.type === AnswerType.Knowledge"
 										>
 											<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>
 										<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>
+											<el-tooltip effect="dark" content="鐐硅禐" placement="top">
+												<div class="flex items-center justify-center size-[15px]">
+													<i
+														:class="{ 'text-[#0284ff]': item.state === AnswerState.Like }"
+														class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
+														@click="likeClick(item)"
+													/>
+												</div>
+											</el-tooltip>
+											<el-tooltip effect="dark" content="鐐硅俯" placement="top">
+												<div class="flex items-center justify-center size-[15px]">
+													<i
+														:class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }"
+														class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
+														@click="unLikeClick(item)"
+													/>
+												</div>
+											</el-tooltip>
 										</template>
-
-										<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>
+										<el-tooltip effect="dark" content="鍒嗕韩" placement="top">
+											<div class="flex items-center justify-center size-[15px]">
+												<i
+													class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
+													@click="shareClick(item)"
+												/>
+											</div>
+										</el-tooltip>
+										<el-tooltip effect="dark" content="鍙嶉" placement="top">
+											<div class="flex items-center justify-center size-[15px] relative">
+												<i
+													class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
+													@click="
+														($event) =>
+															feedbackClick(
+																$event,
+																item,
+																computedMessageList
+																	.filter((v) => v.role === RoleEnum.assistant)
+																	.findIndex((v) => v.historyId === item.historyId)
+															)
+													"
+												/>
+												<FeedbackPanel
+													v-show="feedbackIsShow && currentFeedbackMapItem === item"
+													ref="feedbackPanelRef"
+													v-model:isShow="feedbackIsShow"
+													v-model:content="feedbackContent"
+													:chatItem="currentFeedbackMapItem"
+													:position="feedbackPosition"
+												/>
+											</div>
+										</el-tooltip>
 									</div>
+									<!-- #endregion -->
 								</div>
-
-								<Loding v-else class="w-fit" :process="process" />
-							</div>
-						</div>
-					</div>
-					<div v-if="showAskMore" class="ml-4 mt-5 text-sm">
-						<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>
 						</div>
 					</div>
 				</div>
-			</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>
+					</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">
-				<PlayBar
-					v-model:voicePageIsShow="voicePageIsShow"
-					:isTalking="isTalking"
-					:isHome="false"
-					v-model="messageContent.values"
-					@sendClick="sendClick"
-					:style="{ width: chatWidth }"
-				></PlayBar>
-			</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>
 
-		<CustomDrawer v-model:isShow="drawerIsShow" @updateChatInput="updateChatInput" />
-	</div>
+		<!-- 鎶藉眽 -->
+		<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 _ from 'lodash';
-import { v4 as uuidv4 } from 'uuid';
-import { computed, onMounted, ref } from 'vue';
+import type { CancelTokenSource } from 'axios';
+import axios from 'axios';
+import { findLast, orderBy } from 'lodash-es';
+import moment from 'moment';
+import { computed, onActivated, onMounted, ref } from 'vue';
+import useClipboard from 'vue-clipboard3';
+import { loadAmisSource } from '../amis/load';
 import FeedbackPanel from './components/FeedbackPanel.vue';
-import Loding from './components/Loding.vue';
 import { useAssistantContentOpt } from './hooks/useAssistantContentOpt';
-import { useQueryProcess } from './hooks/useQueryProcess';
+import { convertProcessItem, useScrollLoad } from './hooks/useScrollLoad';
 import { useScrollToBottom } from './hooks/useScrollToBottom';
 import type { ChatContent } from './model/types';
-import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types';
-import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, extCallQuery } from '/@/api/ai/chat';
+import {
+	AnswerState,
+	AnswerType,
+	MultiChatType,
+	RoleEnum,
+	answerTypeMapCom,
+	roleImageMap,
+	stepEnumMap,
+	type ChatMessage,
+} from './model/types';
+import { extCallQuery, questionStreamByPost } from '/@/api/ai/chat';
 import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
 import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
-import router from '/@/router';
-import { activeChatRoom, activeLLMId, activeSampleId, activeSectionAId, getRoomConfig, roomConfig } from '/@/stores/chatRoom';
-import { ErrorCode } from '/@/utils/request';
+import { Logger } from '/@/model/logger/Logger';
 
+import { ElMessage } from 'element-plus';
+import ChatContainer from './components/ChatContainer.vue';
+import ShareLinkDlg from './components/shareLink/index.vue';
+import UserMsg from './user/index.vue';
+import { multiChatTypeMapCom } from '/@/components/chat/chatComponents/multiChat';
+import router from '/@/router';
+import {
+	activeChatRoom,
+	activeGroupType,
+	activeLLMId,
+	activeRoomId,
+	activeSampleId,
+	activeSectionAId,
+	getRoomConfig,
+	isSharePage,
+	roomConfig,
+} from '/@/stores/chatRoom';
+import emitter from '/@/utils/mitt';
+import { ErrorCode } from '/@/utils/request';
+import { useCompRef } from '/@/utils/types';
+import { toMyFixed } from '/@/utils/util';
+const containerRef = ref();
+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);
 });
 
-const parseContent = (res) => {
+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瀽澶辫触锛�',
 	};
+
+	const curExtraContent = parseExtraContent(res);
 
 	switch (res.answer_type) {
 		case AnswerType.RecordSet:
@@ -217,10 +440,22 @@
 			};
 
 			break;
+		case AnswerType.Report:
+			content = {
+				type: AnswerType.Report,
+				values: (res?.reports ?? []).map((item) => ({
+					content: parseContent(item, reportIsShow, { origin: item }),
+				})),
+			};
+			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:
@@ -242,64 +477,231 @@
 			};
 			break;
 	}
-	content.askMoreList = _.orderBy(res.context_history, [(item) => Number(item.radio)], ['desc']);
-	content.errCode = res?.err_code;
-	content.errMsg = res?.json_msg;
-	content.origin = res;
+	if (!extraContent) {
+		content = {
+			...content,
+			...curExtraContent,
+		};
+	} else {
+		content = {
+			...content,
+			...extraContent,
+		};
+	}
+
 	return content;
 };
 
-const { clearQueryProcess, process, processId, queryProcess } = useQueryProcess();
+//#region ====================== 姝ラ step ======================
+const activeStep = ref(-1);
 
+const resetStep = () => {
+	activeStep.value = -1;
+};
+
+const toggleStepLabel = (item: any) => (item.isShow ? '鏀惰捣' : '灞曞紑');
+const toggleStepList = (item: any) => {
+	item.isShow = !item.isShow;
+};
+
+//#endregion
+
+const DEFAULT_SECTION_A_ID = 'knowledge_base';
 let questionRes = null;
+let position = null;
+let finalCalcSectionAId = null;
+
+let lastAxiosSource: CancelTokenSource = null;
 const questionAi = async (text) => {
-	if (!currentSectionId) {
-		ElMessage.warning('鍙戦�佸け璐ワ紝鏈‘瀹氬簲鐢ㄥ満鏅紒');
-	}
-	processId.value = uuidv4();
 	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,
-			  }
-			: {};
+		judgeParams = {};
 	} else {
 		judgeParams = {
 			prev_question: preQuestion.value,
 		};
 	}
-	// const judgeParams = !preQuestion.value
-	// 	? {}
-	// 	: {
-	// 			prev_question: preQuestion.value,
-	// 	  };
+	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.value,
 		question: text,
-		// FIXME: 鏆傛椂杩欐牱
-		section_a_id: currentSectionId,
 		history_group_id: currentRouteId,
 		raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false,
 		...judgeParams,
 	} as any;
 
-	if (currentSampleId) {
-		params.sample_id = currentSampleId;
+	if (position) {
+		const longitude = position.coords.longitude;
+		const latitude = position.coords.latitude;
+		params.cur_pos = [longitude, latitude].join(',');
 	}
 
-	if (currentLLMId) {
-		params.llm_id = currentLLMId;
+	if (activeGroupType.value) {
+		params.group_type = activeGroupType.value;
 	}
-	clearQueryProcess();
-	queryProcess();
-	const res = await QuestionAi(params).finally(() => {
-		clearQueryProcess();
+
+	if (currentSampleId) {
+		params.sample_id = currentSampleId;
+		currentSampleId = '';
+	}
+
+	resetStep();
+	let lastTimestamp = new Date().getTime();
+	questionRes = {};
+	let lastIsResult = false;
+	const resultP = new Promise((resolve, reject) => {
+		const currentSource = axios.CancelToken.source();
+		lastAxiosSource = currentSource;
+		questionStreamByPost(
+			params,
+			(chunkRes) => {
+				Logger.info('chunk response锛歕n\n' + JSON.stringify(chunkRes));
+
+				if (chunkRes.mode === 'result') {
+					lastIsResult = true;
+					const res = chunkRes.value;
+
+					if (!questionRes?.reports || questionRes?.reports?.length === 0) {
+						const resReport = {
+							answer_type: AnswerType.Report,
+							reports: [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);
+
+					// 姝ゅ璇濆凡缁忓姞鍏ュ埌瀵硅瘽鍒楄〃
+					if (lastMsg.content && 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;
+								}
+							}
+						}
+					}
+
+					if (Object.keys(questionRes).length === 0) {
+						questionRes = chunkRes.value;
+					}
+
+					// 姝ゅ璇濊繕鏈姞鍏ュ埌瀵硅瘽鍒楄〃
+					if (!lastMsg.content && questionRes) {
+						questionRes = {
+							...questionRes,
+							...chunkRes.value,
+						};
+					}
+					// computedMessageList.value[computedMessageList.value.length - 1] = finalMsg;
+					scrollToBottom();
+					// chunkRes.value = '浣犲彲浠ョ户缁棶鎴�';
+					return;
+				}
+
+				if (chunkRes.mode === 'conclusion') {
+					computedMessageList.value.at(-1).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;
+				}
+
+				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;
+				});
+
+				resetStep();
+			});
 	});
-	questionRes = res;
-	const content = parseContent(res);
+
+	await resultP;
+	const content = parseContent(questionRes, true);
 	return content;
 };
 
@@ -309,19 +711,27 @@
 		values: '',
 	});
 
-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;
+	resetStep();
+	computedMessageList.value.at(-1).isStopMsg = true;
 };
 
 const sendChatMessage = async (content: ChatContent = messageContent.value, cb?: any, isCallExtParams?: any) => {
-	if (!content?.values) return;
+	if (!content?.values) {
+		return;
+	}
+	if (isTalking.value || chatListLoading.value) {
+		ElMessage.warning('ai 姝e湪鍥炲涓紝璇风◢鍚庡皾璇曟彁闂�');
+		return;
+	}
+
 	const isNewChat = messageList.value.length === 0;
 	if (isNewChat) {
 		if (activeSampleId.value) {
@@ -331,112 +741,123 @@
 		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 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();
+
 		if (isCallExtParams) {
 			const extRes = await extCallQuery(isCallExtParams);
 			questionRes = extRes;
-			resMsgContent = parseContent(extRes);
+			resMsgContent = parseContent(extRes, true);
 		} else {
 			resMsgContent = await questionAi(content.values);
 		}
-
+		nextUserMsgEndIndex.value++;
 		if (isNewChat) {
 			const firstResCb = getRoomConfig(currentRouteId, 'firstResCb');
 			firstResCb?.(resMsgContent);
 		} else {
 			cb?.(resMsgContent);
 		}
-		userItem.historyId = questionRes.history_id;
-		assistantItem.historyId = questionRes.history_id;
+		userItem.historyId = questionRes?.history_id;
+		userItem.content.values = questionRes?.question ?? userItem.content.values;
+		assistantItem.historyId = questionRes?.history_id;
+		assistantItem.sectionAId = finalCalcSectionAId;
 		appendLastMessageContent(resMsgContent);
-	} catch (error: any) {
-		// appendLastMessageContent({
-		// 	type: AnswerType.Text,
-		// 	values: '鍙戠敓閿欒锛�',
-		// });
-	} finally {
-		isTalking.value = false;
-	}
+		setTimeout(() => {
+			// 鏀跺埌鍥炲锛岀户缁粴
+			scrollToBottom();
+		}, 300);
+	} catch (error: any) {}
 };
 
 const sendClick = (cb) => {
 	sendChatMessage(messageContent.value, cb);
 };
 const appendLastMessageContent = (content: ChatContent) => {
+	const currentTime = moment().format('MM鏈圖D鏃� HH:mm:ss');
 	if (messageList.value.at(-1)) {
 		messageList.value.at(-1).content = content;
+		messageList.value.at(-1).createTime = currentTime;
 	}
 };
 
+const { loadRangeData, onChatListScroll, moreIsLoading, nextUserMsgEndIndex } = useScrollLoad({
+	container: chatListDom,
+	historyGroupId: currentRouteId,
+	messageList,
+	parseAnswerContent: parseContent,
+});
+
+const chatListLoading = ref(true);
+
+const { scrollToBottom, scrollToTop, isBottom } = useScrollToBottom({
+	chatListDom: chatListDom,
+});
+
+onActivated(() => {
+	emitter.emit('updateHeaderTitle', activeChatRoom.value?.title ?? '');
+});
+
 onMounted(async () => {
-	const res = await QueryHistoryDetail({
-		history_group_id: currentRouteId,
+	messageList.value = [];
+	chatListLoading.value = true;
+	await loadRangeData().finally(() => {
+		chatListLoading.value = false;
 	});
-
-	messageList.value = (res.details ?? []).map((item) => {
-		return {
-			historyId: item.history_id,
-			role: RoleEnum.user,
-			content: {
-				type: AnswerType.Text,
-				values: item.question,
-			},
-		} as ChatMessage;
-	});
-	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++;
-	});
+	setTimeout(() => {
+		emitter.emit('updateHeaderTitle', activeChatRoom.value?.title ?? '');
+	}, 300);
 
 	if (messageList.value.length === 0) {
 		messageContent.value = {
 			type: AnswerType.Text,
-			values: activeChatRoom.value.title,
+			values: activeChatRoom.value?.title,
 		};
 
 		sendChatMessage();
+	} else {
+		if (isSharePage.value) {
+			// setTimeout(() => {
+			// 	// 婊氬姩鍒伴《閮�
+			// 	scrollToTop();
+			// }, 300);
+		} else {
+			setTimeout(() => {
+				// 鍒濆鐘舵�佹粴涓�涓�
+				scrollToBottom();
+
+				setTimeout(() => {
+					chatListDom.value.addEventListener('scroll', onChatListScroll);
+				}, 300);
+			}, 300);
+		}
 	}
+	loadAmisSource();
 });
-
-const { forbidScroll } = useScrollToBottom({
-	chatListDom: chatListDom,
-	displayMessageList: computedMessageList,
-});
-
 //#region ====================== 鍏宠仈鏌ヨ ======================
 const relativeQueryClick = async (val) => {
 	sendChatMessage(
@@ -454,7 +875,37 @@
 	);
 };
 //#endregion
-
+//#region ====================== 鍏夋爣杈撳叆涓婁笅绠ご鏄剧ず鍘嗗彶娑堟伅 ======================
+const currentIndex = ref(null);
+const history_data = computed(() => {
+	return computedMessageList.value.filter((item) => item.role === RoleEnum.user);
+});
+//鏄剧ず涓婁竴鏉℃秷鎭�
+const showUpChatClick = () => {
+	if (computedMessageList.value.length === 0) return;
+	if (currentIndex.value == 0) {
+		messageContent.value.values = history_data.value[currentIndex.value].content.values;
+		return;
+	} else {
+		currentIndex.value = (currentIndex.value + history_data.value.length - 1) % history_data.value.length;
+	}
+	messageContent.value.values = history_data.value[currentIndex.value].content.values;
+};
+//鏄剧ず涓嬩竴鏉℃秷鎭�
+const showDownChatClick = () => {
+	if (computedMessageList.value.length === 0) return;
+	if (currentIndex.value == history_data.value.length - 1) {
+		messageContent.value.values = history_data.value[currentIndex.value].content.values;
+		return;
+	}
+	if (currentIndex.value === null) {
+		currentIndex.value = 0;
+	} else {
+		currentIndex.value = (currentIndex.value + 1) % history_data.value.length;
+	}
+	messageContent.value.values = history_data.value[currentIndex.value].content.values;
+};
+//#endregion
 const {
 	copyClick,
 	likeClick,
@@ -471,7 +922,6 @@
 	showFixQuestion,
 	showAskMore,
 } = useAssistantContentOpt({
-	forbidScroll,
 	sendChatMessage,
 	displayMessageList: computedMessageList,
 });
@@ -483,12 +933,65 @@
 	messageContent.value.values = content;
 };
 //#endregion
+//#region ====================== 鐢ㄦ埛璇㈤棶鐨勯棶棰樿缃负甯哥敤璇� ======================
+const setCommonQuestionInfo = ref({});
+const { toClipboard } = useClipboard();
+
+//鐢ㄦ埛澶嶅埗闂
+const copyUserClick = (item) => {
+	const text = item.content.values;
+	ElMessage.success('澶嶅埗鎴愬姛');
+	toClipboard(text);
+};
+//鐢ㄦ埛闂璁剧疆涓哄父鐢ㄨ
+const setCommonQuestionClick = (item) => {
+	setCommonQuestionInfo.value = item;
+};
+//#endregion
+
+//#region ====================== 鍒嗕韩 ======================
+
+const shareLinkDlgRef = useCompRef(ShareLinkDlg);
+const isShareCheck = ref(false);
+
+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