From 91142fc15ee81cdc3f80134e5e8faaff237f1e53 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期一, 04 十一月 2024 11:13:36 +0800
Subject: [PATCH] 简单流式展示

---
 src/api/ai/chat.ts           |    2 
 src/components/chat/Chat.vue |  103 +++++++++++++++++++++++++++++++---
 src/utils/request.ts         |   58 +++++++++++--------
 3 files changed, 128 insertions(+), 35 deletions(-)

diff --git a/src/api/ai/chat.ts b/src/api/ai/chat.ts
index 3dc8652..ec45975 100644
--- a/src/api/ai/chat.ts
+++ b/src/api/ai/chat.ts
@@ -259,7 +259,7 @@
  * @description 娴佸紡澶фā鍨嬪璇�
  * @param {FormData} params
  **/
-export const questionStreamByPost = (params, callback: (value) => void) =>
+export const questionStreamByPost = (params, callback: (chunkRes) => void) =>
 	streamReq(
 		{
 			url: `/chat/question_stream`,
diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue
index 5a7e090..3f9d9aa 100644
--- a/src/components/chat/Chat.vue
+++ b/src/components/chat/Chat.vue
@@ -23,6 +23,21 @@
 						/>
 						<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="isTalking && index === computedMessageList.length - 1">
+									<div class="text-sm rounded-[6px] p-4 leading-relaxed bg-white"
+										 v-if="item.role === RoleEnum.assistant">
+										<!-- 杩囩▼杈撳嚭 -->
+										<el-steps direction="vertical" :active="activeStep">
+											<!-- <el-step title="process" status="process" />
+													<el-step title="success" status="success" />
+													<el-step title="wait" status="wait" />
+													<el-step title="finish" status="finish" />
+													<el-steps title="error" status="error" /> -->
+
+											<el-step v-for="item in stepList" :title="item.title" :status="stepEnumMap[item.status]" />
+										</el-steps>
+									</div>
+								</div>
 								<div class="w-full" v-if="item.content?.values">
 									<div
 										class="text-sm rounded-[6px] p-4 leading-relaxed"
@@ -123,8 +138,6 @@
 										</div>
 									</div>
 								</div>
-
-								<Loding v-if="isTalking && index === computedMessageList.length - 1" class="w-fit" :process="process" />
 							</div>
 						</div>
 					</div>
@@ -173,7 +186,7 @@
 import { useScrollToBottom } from './hooks/useScrollToBottom';
 import type { ChatContent } from './model/types';
 import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types';
-import { QuestionAi, extCallQuery } from '/@/api/ai/chat';
+import { QuestionAi, extCallQuery, questionStreamByPost } from '/@/api/ai/chat';
 import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
 import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
 import router from '/@/router';
@@ -264,12 +277,38 @@
 	return content;
 };
 const { clearQueryProcess, process, processId, queryProcess } = useQueryProcess();
+
+//#region ====================== 姝ラ step ======================
+const enum StepEnum {
+	Loading,
+	Success,
+	Error,
+}
+const stepEnumMap = {
+	[StepEnum.Loading]: 'process',
+	[StepEnum.Success]: 'success',
+	[StepEnum.Error]: 'error',
+};
+
+type StepItem = {
+	title: string;
+	status: StepEnum;
+};
+const activeStep = ref(-1);
+const stepList = ref<StepItem[]>([]);
+
+const resetStep = () => {
+	activeStep.value = -1;
+	stepList.value = [];
+};
+//#endregion
+
 const DEFAULT_SECTION_A_ID = 'knowledge_base';
 let questionRes = null;
 
 let finalCalcSectionAId = null;
 const questionAi = async (text) => {
-	processId.value = uuidv4();
+	// processId.value = uuidv4();
 	let judgeParams = null;
 	if (!preQuestion.value) {
 		// const aiContent = computedMessageList.value.filter((item) => item.role === RoleEnum.assistant);
@@ -300,7 +339,7 @@
 	finalCalcSectionAId = currentSectionAId;
 
 	const params = {
-		process_id: processId.value,
+		// process_id: processId.value,
 		question: text,
 		// FIXME: 鏆傛椂杩欐牱
 		// section_a_id: currentSectionAId,
@@ -321,10 +360,31 @@
 	// if (currentLLMId) {
 	// 	params.llm_id = currentLLMId;
 	// }
-	clearQueryProcess();
-	queryProcess();
-	const res = await QuestionAi(params).finally(() => {
-		clearQueryProcess();
+	// clearQueryProcess();
+	// queryProcess();
+	resetStep();
+	let res = null;
+	await questionStreamByPost(params, (chunkRes) => {
+		if (chunkRes.mode === 'result') {
+			res = chunkRes.value;
+		} else {
+			switch (chunkRes.mode) {
+				case 'begin':
+					break;
+				case 'end':
+					break;
+			}
+
+			stepList.value.push({
+				title: chunkRes.value,
+				status: StepEnum.Success,
+			});
+			scrollToBottom();
+
+			// process.value = chunkRes.value;
+		}
+	}).finally(() => {
+		resetStep();
 	});
 	questionRes = res;
 	const content = parseContent(res);
@@ -515,4 +575,29 @@
 		}
 	}
 }
+
+.el-step.is-horizontal.stepActive {
+	.el-step__head.is-finish {
+		.el-step__line {
+			// 褰撳墠姝ラ鏁版í绾挎牱寮忚缃�
+			.el-step__line-inner {
+				width: 50% !important;
+				border-width: 1px !important;
+			}
+		}
+
+		// 褰撳墠姝ラ鏁板渾鍦堟牱寮忚缃�
+		.el-step__icon.is-text {
+			//    background: #409eff;
+			color: #fff;
+		}
+	}
+}
+
+:deep(.el-step__icon-inner) {
+	font-size: 16px !important;
+}
+:deep(.el-step__description) {
+	height: 20px;
+}
 </style>
diff --git a/src/utils/request.ts b/src/utils/request.ts
index a79a4c5..6e9e5a9 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -148,35 +148,43 @@
 });
 const decoder = new TextDecoder();
 const encoder = new TextEncoder();
-const readStream = async (stream: ReadableStream, cb: (value) => void) => {
+const readStream = async (stream: ReadableStream, cb: (value) => void): Promise<any> => {
 	const reader = stream.getReader();
-	let lastValue = new Uint8Array();
-	while (1) {
-		const { done, value } = await reader.read();
-		if (done) {
-			break;
-		}
-		const txt = decoder.decode(Uint8Array.from([...lastValue, ...value]));
-		const txtArr = txt.split('\n');
-		txtArr.forEach((value, index, array) => {
-			// 涓�鑸笉浼氬嚭鐜拌繛缁崲琛岋紝鍙彲鑳芥渶鍚庝竴涓槸鎹㈣
-			if (index === array.length - 1) {
-				// 缂栫爜鍥炲幓锛屼腑鏂囧垏鍒嗕細涔辩爜
-				lastValue = encoder.encode(value);
-			} else {
-				cb(value);
+	let lastValue = '';
+	const p = new Promise(async (resolve, reject) => {
+		let fullValue = '';
+		while (1) {
+			const { done, value } = await reader.read();
+			if (done) {
+				break;
 			}
-		});
-	}
+			// const txt = decoder.decode(Uint8Array.from([...lastValue, ...value]));
+			const txt = decoder.decode(value);
+			const txtArr = txt.split('\n');
+			txtArr[0] =lastValue+ txtArr[0]
+			txtArr.forEach((value, index, array) => {
+				// 涓�鑸笉浼氬嚭鐜拌繛缁崲琛岋紝鍙彲鑳芥渶鍚庝竴涓槸鎹㈣
+				if (index === array.length - 1) {
+					lastValue =value;
+
+				} else {
+					const decodeValue = decodeURIComponent(value);
+					fullValue += decodeValue;
+					cb(decodeValue);
+				}
+			});
+		}
+		resolve(fullValue);
+	});
+	return p;
 };
 
-export const streamReq = (config: AxiosRequestConfig<any>, callback: (value) => void) => {
-	streamInstance(config).then((response) => {
-		const stream = response as unknown as ReadableStream;
-		readStream(stream, (value) => {
-			const jsonValue = JSON.parse(value);
-			callback(jsonValue);
-		});
+export const streamReq = async (config: AxiosRequestConfig<any>, callback: (value) => void) => {
+	const response = await streamInstance(config);
+	const stream = response as unknown as ReadableStream;
+	return readStream(stream, (value) => {
+		const jsonValue = JSON.parse(value);
+		callback(jsonValue);
 	});
 };
 //#endregion

--
Gitblit v1.9.3