From 437142bb6cb308f38feddb467a9f13e444698c96 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期一, 04 十一月 2024 13:50:25 +0800
Subject: [PATCH] Merge branch 'test' of http://47.103.154.90:83/r/WI/Web.V1.0 into test

---
 src/api/ai/chat.ts                                              |   24 +++-
 src/components/chat/Chat.vue                                    |  131 ++++++++++++++++++++++++--
 package-lock.json                                               |   30 +++---
 src/components/chat/components/playBar/PlayBar.vue              |   11 +
 src/utils/request.ts                                            |   57 ++++++++++
 package.json                                                    |    2 
 src/components/chat/components/playBar/phrase/CommonPhrases.vue |   19 ++-
 7 files changed, 230 insertions(+), 44 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f29faae..d502b99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,7 @@
 				"@wangeditor/editor": "^5.1.23",
 				"@wangeditor/editor-for-vue": "^5.1.12",
 				"amis": "^3.2.0",
-				"axios": "^1.3.4",
+				"axios": "^1.7.7",
 				"bimfacesdkloader": "^0.1.0",
 				"countup.js": "^2.5.0",
 				"cropperjs": "^1.5.13",
@@ -2725,11 +2725,11 @@
 			}
 		},
 		"node_modules/axios": {
-			"version": "1.3.4",
-			"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
-			"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+			"version": "1.7.7",
+			"resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.7.tgz",
+			"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
 			"dependencies": {
-				"follow-redirects": "^1.15.0",
+				"follow-redirects": "^1.15.6",
 				"form-data": "^4.0.0",
 				"proxy-from-env": "^1.1.0"
 			}
@@ -4717,9 +4717,9 @@
 			}
 		},
 		"node_modules/follow-redirects": {
-			"version": "1.15.2",
-			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
-			"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+			"version": "1.15.9",
+			"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+			"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
 			"funding": [
 				{
 					"type": "individual",
@@ -11481,11 +11481,11 @@
 			}
 		},
 		"axios": {
-			"version": "1.3.4",
-			"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
-			"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+			"version": "1.7.7",
+			"resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.7.tgz",
+			"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
 			"requires": {
-				"follow-redirects": "^1.15.0",
+				"follow-redirects": "^1.15.6",
 				"form-data": "^4.0.0",
 				"proxy-from-env": "^1.1.0"
 			}
@@ -13047,9 +13047,9 @@
 			}
 		},
 		"follow-redirects": {
-			"version": "1.15.2",
-			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
-			"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
+			"version": "1.15.9",
+			"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+			"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
 		},
 		"foreground-child": {
 			"version": "3.2.1",
diff --git a/package.json b/package.json
index 73f3975..fe610e1 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",
 		"amis": "^3.2.0",
-		"axios": "^1.3.4",
+		"axios": "^1.7.7",
 		"bimfacesdkloader": "^0.1.0",
 		"countup.js": "^2.5.0",
 		"cropperjs": "^1.5.13",
diff --git a/src/api/ai/chat.ts b/src/api/ai/chat.ts
index 6686408..ec45975 100644
--- a/src/api/ai/chat.ts
+++ b/src/api/ai/chat.ts
@@ -1,4 +1,4 @@
-import request from '/@/utils/request';
+import request, { streamReq } from '/@/utils/request';
 const GET_SECTION_SAMPLE_API = '/scene/get_scene_group_sample';
 const GET_SECTION_A_LIST_API = '/scene/get_scene_group_tree';
 export const Get_LOGIN_SMS = '/login_sms';
@@ -255,6 +255,20 @@
 		},
 	});
 };
+/**
+ * @description 娴佸紡澶фā鍨嬪璇�
+ * @param {FormData} params
+ **/
+export const questionStreamByPost = (params, callback: (chunkRes) => void) =>
+	streamReq(
+		{
+			url: `/chat/question_stream`,
+			method: 'post',
+			params: {},
+			data: params,
+		},
+		callback
+	);
 
 /**
  * @summary AI澶фā鍨嬪璇�
@@ -360,11 +374,11 @@
 
 /**
  * 鏇茬嚎鏌ヨ
- * @param params 
- * @param req 
- * @returns 
+ * @param params
+ * @param req
+ * @returns
  */
-export const curveQuery = (params,req:any=request) =>{
+export const curveQuery = (params, req: any = request) => {
 	return req({
 		url: 'chat/chat_supervisor_json',
 		method: 'POST',
diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue
index 5a7e090..2de9848 100644
--- a/src/components/chat/Chat.vue
+++ b/src/components/chat/Chat.vue
@@ -23,6 +23,20 @@
 						/>
 						<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 +137,6 @@
 										</div>
 									</div>
 								</div>
-
-								<Loding v-if="isTalking && index === computedMessageList.length - 1" class="w-fit" :process="process" />
 							</div>
 						</div>
 					</div>
@@ -151,6 +163,8 @@
 					:isHome="false"
 					v-model="messageContent.values"
 					@sendClick="sendClick"
+					@showUpChatClick="showUpChatClick"
+					@showDownChatClick="showDownChatClick"
 					:style="{ width: chatWidth }"
 				></PlayBar>
 			</div>
@@ -173,7 +187,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 +278,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 +340,7 @@
 	finalCalcSectionAId = currentSectionAId;
 
 	const params = {
-		process_id: processId.value,
+		// process_id: processId.value,
 		question: text,
 		// FIXME: 鏆傛椂杩欐牱
 		// section_a_id: currentSectionAId,
@@ -321,10 +361,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);
@@ -468,7 +529,32 @@
 	);
 };
 //#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 === null) {
+		currentIndex.value = history_data.value.length - 1;
+	} 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 === 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,
@@ -515,4 +601,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/components/chat/components/playBar/PlayBar.vue b/src/components/chat/components/playBar/PlayBar.vue
index 1eff5c0..e9ba850 100644
--- a/src/components/chat/components/playBar/PlayBar.vue
+++ b/src/components/chat/components/playBar/PlayBar.vue
@@ -132,7 +132,7 @@
 import VoicePage from './voicePage/VoicePage.vue';
 import { getMetricsNames, querySimilarityHistory } from '/@/api/ai/chat';
 import { activeGroupType, groupTypeList, groupTypeMapIcon } from '/@/stores/chatRoom';
-const emits = defineEmits(['sendClick']);
+const emits = defineEmits(['sendClick', 'showUpChatClick', 'showDownChatClick']);
 const props = defineProps(['isTalking', 'isHome']);
 const voicePageIsShow = defineModel('voicePageIsShow', {
 	type: Boolean,
@@ -157,7 +157,9 @@
 	if (props.isTalking) return;
 	const isEnterInput = !e.shiftKey && e.key == 'Enter';
 	const isDigitalInput = e.ctrlKey && e.code.startsWith('Digit') && tipIsShow.value;
-	if (isEnterInput || isDigitalInput) {
+	const arrowUp = e.key === 'ArrowUp';
+	const arrowDown = e.key === 'ArrowDown';
+	if (isEnterInput || isDigitalInput || arrowUp || arrowDown) {
 		e.cancelBubble = true; //ie闃绘鍐掓场琛屼负
 		e.stopPropagation(); //Firefox闃绘鍐掓场琛屼负
 		e.preventDefault(); //鍙栨秷浜嬩欢鐨勯粯璁ゅ姩浣�*鎹㈣
@@ -171,6 +173,10 @@
 				inputValue.value = mapValue;
 				triggerShow.value = false;
 			}
+		} else if (arrowUp) {
+			emits('showUpChatClick');
+		} else if (arrowDown) {
+			emits('showDownChatClick');
 		}
 	}
 };
@@ -443,6 +449,7 @@
 };
 const updateCommonChatInput = (val) => {
 	inputValue.value = val;
+	isShowPhrase.value = false;
 };
 //#endregion
 </script>
diff --git a/src/components/chat/components/playBar/phrase/CommonPhrases.vue b/src/components/chat/components/playBar/phrase/CommonPhrases.vue
index d3a1ff4..ee4703f 100644
--- a/src/components/chat/components/playBar/phrase/CommonPhrases.vue
+++ b/src/components/chat/components/playBar/phrase/CommonPhrases.vue
@@ -9,9 +9,9 @@
 				<div class="w-full h-full absolute top-0">
 					<div class="py-0 mt-0 box-border h-full">
 						<div style="overflow-anchor: none" v-for="(item, index) in commonPhrases" :key="index">
-							<div class="phase_item">
+							<div class="phase_item" @click="titleClick(item)">
 								<div class="flex flex-col">
-									<div class="title" @click="titleClick(item)">
+									<div class="title">
 										{{ item.title }}
 									</div>
 									<!-- <div class="content">
@@ -21,11 +21,11 @@
 								<div class="py-2">
 									<span
 										class="ywifont ywicon-bianji cursor-pointer text-[#767a97] pt-[4px] pr-[6px] pb-[2px] pl-0 rounded-lg !text-[13px]"
-										@click="editCommonPhrases(item)"
+										@click.stop="editCommonPhrases(item)"
 									></span>
 									<span
 										class="ywifont ywicon-shanchu3 cursor-pointer text-[red] pt-[4px] pr-[6px] pb-[2px] pl-0 rounded-lg"
-										@click="deleteCommonPhrases(item)"
+										@click.stop="deleteCommonPhrases(item)"
 									></span>
 								</div>
 							</div>
@@ -99,8 +99,8 @@
 };
 const editCommonPhrases = (item) => {
 	state.useCommonPhrasesDialog = true;
-	state.inputCommonPhrases = item.content;
 	state.show_sample_title = true;
+	state.inputCommonPhrases = item.title;
 };
 const deleteCommonPhrases = (item) => {
 	ElMessageBox.confirm(`浣犵‘瀹氳鍒犻櫎甯哥敤璇悧?<div style="white-space: pre-line;">[${item.title}]</div>`, '鎻愮ず', {
@@ -196,14 +196,17 @@
 				}
 				.title {
 					font-size: 14px;
-					color: #060607;
-					font-family: PingFang HK;
-					font-weight: 600;
+					color: #8d8e99;
+					font-weight: 400;
 					font-style: normal;
 					white-space: nowrap;
 					text-overflow: ellipsis;
 					overflow: hidden;
+					&:hover {
+						color: #060607;
+					}
 				}
+
 				.content {
 					font-size: 12px;
 					color: #5e6772;
diff --git a/src/utils/request.ts b/src/utils/request.ts
index c8756bf..6e9e5a9 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -1,13 +1,13 @@
-import type { AxiosInstance, AxiosRequestConfig } from 'axios';
+import type { AxiosInstance, AxiosRequestConfig, CreateAxiosDefaults } from 'axios';
 import axios from 'axios';
 import { ElMessage } from 'element-plus';
 import { NO_AUTH_API_LIST } from '../api/ai/chat';
 import { LOGIN_URL, TEL_LOGIN_URL } from '../api/ai/user';
+import { Logger } from '../model/logger/Logger';
 import emitter from './mitt';
 import { debounce, decodeFormData } from './util';
 import { AUTH_URL, MAIN_URL, SECONDARY_URL } from '/@/constants';
 import { Local, LoginInfo, Session } from '/@/utils/storage';
-import { Logger } from '../model/logger/Logger';
 // import JSONbig from 'json-bigint';
 
 //#region ====================== 鍚庣 res.err_code ======================
@@ -128,18 +128,69 @@
 	);
 };
 // 閰嶇疆鏂板缓涓�涓� axios 瀹炰緥
-const createAxiosInstance = () => {
+const createAxiosInstance = (option: Partial<CreateAxiosDefaults<any>> = {}) => {
 	return axios.create({
 		baseURL: MAIN_URL,
 		timeout: 50000,
 		headers: { 'Content-Type': 'application/json;charset=utf-8 ' },
+		...option,
 	});
 };
+
 const service = createAxiosInstance();
 
 export const mainRequest = service;
 
+//#region ====================== 娴佸搷搴旀暟鎹� ======================
+const streamInstance = createAxiosInstance({
+	adapter: 'fetch',
+	responseType: 'stream',
+});
+const decoder = new TextDecoder();
+const encoder = new TextEncoder();
+const readStream = async (stream: ReadableStream, cb: (value) => void): Promise<any> => {
+	const reader = stream.getReader();
+	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 = 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
+
 initRequestInterceptor(service);
+initRequestInterceptor(streamInstance);
 
 export function secondaryRequest(config: AxiosRequestConfig<any>) {
 	return service({

--
Gitblit v1.9.3