From d76842ae075d6469e49010bf18760e285927e5c4 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Sun, 14 Sep 2025 10:38:41 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90v3.8.3=E3=80=91airag=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/super/airag/aiapp/chat/AiChat.vue | 2 +- .../src/views/super/airag/aiapp/chat/chat.vue | 202 ++++++++++++------ .../aiapp/components/AiAppSettingModal.vue | 2 +- .../airag/aiknowledge/AiKnowledgeBase.data.ts | 2 +- .../airag/aiknowledge/AiKnowledgeBaseList.vue | 2 +- .../views/super/airag/aimodel/AiModelList.vue | 14 ++ .../airag/aimodel/components/AiModelModal.vue | 24 ++- 7 files changed, 174 insertions(+), 74 deletions(-) diff --git a/jeecgboot-vue3/src/views/super/airag/aiapp/chat/AiChat.vue b/jeecgboot-vue3/src/views/super/airag/aiapp/chat/AiChat.vue index c2218c96e..f7f4e0af1 100644 --- a/jeecgboot-vue3/src/views/super/airag/aiapp/chat/AiChat.vue +++ b/jeecgboot-vue3/src/views/super/airag/aiapp/chat/AiChat.vue @@ -326,7 +326,7 @@ background: white; display: flex; overflow: hidden; - z-index: 999; + z-index: 800; border: 1px solid #eeeeee; :deep(.ant-spin) { position: absolute; diff --git a/jeecgboot-vue3/src/views/super/airag/aiapp/chat/chat.vue b/jeecgboot-vue3/src/views/super/airag/aiapp/chat/chat.vue index a92f6ef1c..72689b882 100644 --- a/jeecgboot-vue3/src/views/super/airag/aiapp/chat/chat.vue +++ b/jeecgboot-vue3/src/views/super/airag/aiapp/chat/chat.vue @@ -447,7 +447,7 @@ uploadUrlList.value = []; fileInfoList.value = []; knowList.value = []; - + options.message = message; const readableStream = await defHttp.post( { url: props.url, @@ -473,72 +473,7 @@ console.error(e) //update-end---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天,超时以后提示--- }); - const reader = readableStream.getReader(); - const decoder = new TextDecoder('UTF-8'); - let conversationId = ''; - let buffer = ''; - let text = ''; // 按 SSE 协议分割消息 - while (true) { - const { done, value } = await reader.read(); - if (done) { - break; - } - //update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息--- - let result = decoder.decode(value, { stream: true }); - result = buffer + result; - const lines = result.split('\n\n'); - for (let line of lines) { - if (line.startsWith('data:')) { - let content = line.replace('data:', '').trim(); - if(!content){ - continue; - } - if(!content.endsWith('}')){ - buffer = buffer + line; - continue; - } - buffer = ""; - try { - //update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- - if(content.indexOf(":::card:::") !== -1){ - content = content.replace(/\s+/g, ''); - } - let parse = JSON.parse(content); - await renderText(parse,conversationId,text,options).then((res)=>{ - text = res.returnText; - conversationId = res.conversationId; - }); - //update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- - } catch (error) { - console.log('Error parsing update:', error); - } - //update-end---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息--- - }else{ - if(!line){ - continue; - } - if(!line.endsWith('}')){ - buffer = buffer + line; - continue; - } - buffer = ""; - //update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- - try { - if(line.indexOf(":::card:::") !== -1){ - line = line.replace(/\s+/g, ''); - } - let parse = JSON.parse(line); - await renderText(parse, conversationId, text, options).then((res) => { - text = res.returnText; - conversationId = res.conversationId; - }); - }catch (error) { - console.log('Error parsing update:', error); - } - //update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- - } - } - } + await renderChatByResult(readableStream,options); } // 是否使用上下文 const handleUsingContext = () => { @@ -598,12 +533,14 @@ if(item.event == 'INIT_REQUEST_ID'){ if (item.requestId) { requestId.value = item.requestId; + localStorage.setItem('chat_requestId_' + uuid.value, JSON.stringify({ requestId: item.requestId, message: options.message })); } } if (item.event == 'MESSAGE_END') { topicId.value = item.topicId; conversationId = item.conversationId; uuid.value = item.conversationId; + localStorage.removeItem('chat_requestId_' + uuid.value); handleStop(); } if (item.event == 'FLOW_FINISHED') { @@ -831,6 +768,135 @@ await defHttp.uploadFile({ url: "/airag/chat/upload" }, { file: image }, { success: isReturn }); } + /** + * 渲染返回来的结果 + * @param readableStream + * @param options + */ + async function renderChatByResult(readableStream, options) { + const reader = readableStream.getReader(); + const decoder = new TextDecoder('UTF-8'); + let conversationId = ''; + let buffer = ''; + let text = ''; // 按 SSE 协议分割消息 + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + //update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息--- + let result = decoder.decode(value, { stream: true }); + result = buffer + result; + const lines = result.split('\n\n'); + for (let line of lines) { + if (line.startsWith('data:')) { + let content = line.replace('data:', '').trim(); + if(!content){ + continue; + } + if(!content.endsWith('}')){ + buffer = buffer + line; + continue; + } + buffer = ""; + try { + //update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- + if(content.indexOf(":::card:::") !== -1){ + content = content.replace(/\s+/g, ''); + } + let parse = JSON.parse(content); + await renderText(parse,conversationId,text,options).then((res)=>{ + text = res.returnText; + conversationId = res.conversationId; + }); + //update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- + } catch (error) { + console.log('Error parsing update:', error); + } + //update-end---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息--- + }else{ + if(!line){ + continue; + } + if(!line.endsWith('}')){ + buffer = buffer + line; + continue; + } + buffer = ""; + //update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- + try { + if(line.indexOf(":::card:::") !== -1){ + line = line.replace(/\s+/g, ''); + } + let parse = JSON.parse(line); + await renderText(parse, conversationId, text, options).then((res) => { + text = res.returnText; + conversationId = res.conversationId; + }); + } catch (error) { + console.log('Error parsing update:', error); + } + //update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答--- + } + } + } + } + + /** + * ai重连 + */ + async function aiReConnection() { + //查询requestId + let chat = localStorage.getItem("chat_requestId_" + uuid.value); + if(chat) { + let array = JSON.parse(chat); + let message = array.message; + let requestId = array.requestId; + const result = await defHttp.get({ url: '/airag/chat/receive/' + requestId , + adapter: 'fetch', + responseType: 'stream', + timeout: 5 * 60 * 1000 + }, { isTransformResponse: false }).catch(async (err)=>{ + loading.value = false; + }); + if(result && message){ + loading.value = true; + //发送用户消息 + addChat(uuid.value, { + dateTime: new Date().toLocaleString(), + content: message, + images:uploadUrlList.value?uploadUrlList.value:[], + inversion: 'user', + error: false, + conversationOptions: null, + requestOptions: { prompt: message, options: null }, + }); + let options: any = {}; + const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions; + if (lastContext && usingContext.value) { + options = { ...lastContext }; + } + //添加ai消息 + addChat(uuid.value, { + dateTime: new Date().toLocaleString(), + content: '请稍后', + loading: false, + inversion: 'ai', + error: false, + conversationOptions: null, + requestOptions: { prompt: message, options: { ...options } }, + referenceKnowledge: [], + }); + options.message = message; + scrollToBottom(); + //流式输出 + await renderChatByResult(result,options); + } else { + loading.value = false; + } + } + } + //监听开场白 watch( () => props.prologue, @@ -882,6 +948,8 @@ if(props.prologue && props.chatTitle){ topChat(props.prologue) } + //ai回复重连 + aiReConnection(); } catch (e) { console.log(e) } diff --git a/jeecgboot-vue3/src/views/super/airag/aiapp/components/AiAppSettingModal.vue b/jeecgboot-vue3/src/views/super/airag/aiapp/components/AiAppSettingModal.vue index c190b3085..9b77f8146 100644 --- a/jeecgboot-vue3/src/views/super/airag/aiapp/components/AiAppSettingModal.vue +++ b/jeecgboot-vue3/src/views/super/airag/aiapp/components/AiAppSettingModal.vue @@ -183,7 +183,7 @@ v-model:value="formState.modelId" :disabled="isRelease" placeholder="请选择AI模型" - dict-code="airag_model where model_type = 'LLM',name,id" + dict-code="airag_model where model_type = 'LLM' and activate_flag = 1,name,id" style="width: 100%;" > diff --git a/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts b/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts index 331eff53e..bc5e123cc 100644 --- a/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts +++ b/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts @@ -40,7 +40,7 @@ export const formSchema: FormSchema[] = [ required: true, component: 'JDictSelectTag', componentProps: { - dictCode: "airag_model where model_type = 'EMBED',name,id", + dictCode: "airag_model where model_type = 'EMBED' and activate_flag = 1,name,id", }, }, { diff --git a/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue b/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue index 2e2db4317..172a260e4 100644 --- a/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue +++ b/jeecgboot-vue3/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue @@ -277,7 +277,7 @@ async function handleVectorization(id) { rebuild({ knowIds: id }).then((res) =>{ if(res.success){ - createMessage.success("向量化成功!"); + createMessage.success("操作成功,开始异步重建知识库,请稍后查看!"); reload(); }else{ createMessage.warning("向量化失败!"); diff --git a/jeecgboot-vue3/src/views/super/airag/aimodel/AiModelList.vue b/jeecgboot-vue3/src/views/super/airag/aimodel/AiModelList.vue index 373c8395e..47c9d2630 100644 --- a/jeecgboot-vue3/src/views/super/airag/aimodel/AiModelList.vue +++ b/jeecgboot-vue3/src/views/super/airag/aimodel/AiModelList.vue @@ -47,6 +47,9 @@
  • 模型类型 {{ item.modelType_dictText }} + + 未激活 +
  • 基础模型 @@ -314,6 +317,17 @@ color: #8a8f98; overflow-wrap: break-word; } + .no-activate{ + font-size: 10px; + color: #ff4d4f; + border: 1px solid #ff4d4f; + border-radius: 10px; + padding: 0 6px; + height: 14px; + line-height: 12px; + margin-left: 6px; + align-self: center; + } .described { font-weight: 400; margin-left: 14px; diff --git a/jeecgboot-vue3/src/views/super/airag/aimodel/components/AiModelModal.vue b/jeecgboot-vue3/src/views/super/airag/aimodel/components/AiModelModal.vue index 75e3158fa..30ee24dd7 100644 --- a/jeecgboot-vue3/src/views/super/airag/aimodel/components/AiModelModal.vue +++ b/jeecgboot-vue3/src/views/super/airag/aimodel/components/AiModelModal.vue @@ -77,6 +77,7 @@ + @@ -96,9 +97,9 @@ @@ -159,6 +160,8 @@ const modelParamsRef = ref(); //测试按钮loading状态 const testLoading = ref(false); + //模型是否已激活 + const modelActivate = ref(false); const getImage = (name) => { return imageList.value[name]; @@ -209,6 +212,11 @@ if(values.result.modelType && values.result.modelType === 'LLM'){ modelParamsShow.value = true; } + if (values.result.activateFlag) { + modelActivate.value = true; + }else{ + modelActivate.value = false; + } if(values.result.modelParams){ modelParams.value = JSON.parse(values.result.modelParams) } @@ -303,6 +311,11 @@ values.modelParams = JSON.stringify(modelParams); } } + if(modelActivate.value){ + values.activateFlag = 1 + }else{ + values.activateFlag = 0; + } values.credential = JSON.stringify(credential); //新增 if (!values.id) { @@ -330,6 +343,7 @@ function cancel() { dataIndex.value = 'list'; closeModal(); + emit('success'); } /** @@ -354,7 +368,10 @@ values.provider = modelData.value.value; } //测试 - await testConn(values); + await testConn(values).then((result) => { + modelActivate.value = true; + save(); + }); } catch (e) { if (e.hasOwnProperty('errorFields')) { activeKey.value = 1; @@ -424,6 +441,7 @@ activeKey, modelParams, modelParamsShow, + modelActivate, modelParamsRef, filterOption, getTitle,