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