【v3.8.3】airag优化体验升级

pull/8786/merge
JEECG 2025-09-14 10:38:41 +08:00
parent 8c64db46e5
commit d76842ae07
7 changed files with 174 additions and 74 deletions

View File

@ -326,7 +326,7 @@
background: white; background: white;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
z-index: 999; z-index: 800;
border: 1px solid #eeeeee; border: 1px solid #eeeeee;
:deep(.ant-spin) { :deep(.ant-spin) {
position: absolute; position: absolute;

View File

@ -447,7 +447,7 @@
uploadUrlList.value = []; uploadUrlList.value = [];
fileInfoList.value = []; fileInfoList.value = [];
knowList.value = []; knowList.value = [];
options.message = message;
const readableStream = await defHttp.post( const readableStream = await defHttp.post(
{ {
url: props.url, url: props.url,
@ -473,72 +473,7 @@
console.error(e) console.error(e)
//update-end---author:wangshuai---date:2025-04-28---for:QQYUN-12297AI--- //update-end---author:wangshuai---date:2025-04-28---for:QQYUN-12297AI---
}); });
const reader = readableStream.getReader(); await renderChatByResult(readableStream,options);
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线---
}
}
}
} }
// 使 // 使
const handleUsingContext = () => { const handleUsingContext = () => {
@ -598,12 +533,14 @@
if(item.event == 'INIT_REQUEST_ID'){ if(item.event == 'INIT_REQUEST_ID'){
if (item.requestId) { if (item.requestId) {
requestId.value = 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') { if (item.event == 'MESSAGE_END') {
topicId.value = item.topicId; topicId.value = item.topicId;
conversationId = item.conversationId; conversationId = item.conversationId;
uuid.value = item.conversationId; uuid.value = item.conversationId;
localStorage.removeItem('chat_requestId_' + uuid.value);
handleStop(); handleStop();
} }
if (item.event == 'FLOW_FINISHED') { if (item.event == 'FLOW_FINISHED') {
@ -831,6 +768,135 @@
await defHttp.uploadFile({ url: "/airag/chat/upload" }, { file: image }, { success: isReturn }); 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( watch(
() => props.prologue, () => props.prologue,
@ -882,6 +948,8 @@
if(props.prologue && props.chatTitle){ if(props.prologue && props.chatTitle){
topChat(props.prologue) topChat(props.prologue)
} }
//ai
aiReConnection();
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }

View File

@ -183,7 +183,7 @@
v-model:value="formState.modelId" v-model:value="formState.modelId"
:disabled="isRelease" :disabled="isRelease"
placeholder="请选择AI模型" 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%;" style="width: 100%;"
></JDictSelectTag> ></JDictSelectTag>
</a-form-item> </a-form-item>

View File

@ -40,7 +40,7 @@ export const formSchema: FormSchema[] = [
required: true, required: true,
component: 'JDictSelectTag', component: 'JDictSelectTag',
componentProps: { componentProps: {
dictCode: "airag_model where model_type = 'EMBED',name,id", dictCode: "airag_model where model_type = 'EMBED' and activate_flag = 1,name,id",
}, },
}, },
{ {

View File

@ -277,7 +277,7 @@
async function handleVectorization(id) { async function handleVectorization(id) {
rebuild({ knowIds: id }).then((res) =>{ rebuild({ knowIds: id }).then((res) =>{
if(res.success){ if(res.success){
createMessage.success("向量化成功"); createMessage.success("操作成功,开始异步重建知识库,请稍后查看");
reload(); reload();
}else{ }else{
createMessage.warning("向量化失败!"); createMessage.warning("向量化失败!");

View File

@ -47,6 +47,9 @@
<li class="flex mr-14"> <li class="flex mr-14">
<span class="label">模型类型</span> <span class="label">模型类型</span>
<span class="described">{{ item.modelType_dictText }}</span> <span class="described">{{ item.modelType_dictText }}</span>
<a-tooltip v-if="!item.activateFlag" title="未激活模型暂无法被系统其他功能调用,激活后可正常使用。">
<span class="no-activate">未激活</span>
</a-tooltip>
</li> </li>
<li class="flex mr-14 mt-6"> <li class="flex mr-14 mt-6">
<span class="label">基础模型</span> <span class="label">基础模型</span>
@ -314,6 +317,17 @@
color: #8a8f98; color: #8a8f98;
overflow-wrap: break-word; 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 { .described {
font-weight: 400; font-weight: 400;
margin-left: 14px; margin-left: 14px;

View File

@ -77,6 +77,7 @@
</AutoComplete> </AutoComplete>
</template> </template>
</BasicForm> </BasicForm>
<a-alert v-if="!modelActivate" message="模型未激活,请通过下方「保存并激活」按钮激活当前模型" type="warning" show-icon />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane :key="2" v-if="modelParamsShow"> <a-tab-pane :key="2" v-if="modelParamsShow">
@ -96,9 +97,9 @@
</div> </div>
<template v-if="dataIndex === 'add' || dataIndex === 'edit'" #footer> <template v-if="dataIndex === 'add' || dataIndex === 'edit'" #footer>
<a-button @click="test" :loading="testLoading">测试</a-button>
<a-button @click="cancel"></a-button> <a-button @click="cancel"></a-button>
<a-button @click="save" type="primary">保存</a-button> <a-button @click="save" type="primary" ghost="true">保存</a-button>
<a-button @click="test" v-if="!modelActivate" :loading="testLoading" type="primary" >保存并激活</a-button>
</template> </template>
<template v-else #footer> </template> <template v-else #footer> </template>
</BasicModal> </BasicModal>
@ -159,6 +160,8 @@
const modelParamsRef = ref(); const modelParamsRef = ref();
//loading //loading
const testLoading = ref<boolean>(false); const testLoading = ref<boolean>(false);
//
const modelActivate = ref<boolean>(false);
const getImage = (name) => { const getImage = (name) => {
return imageList.value[name]; return imageList.value[name];
@ -209,6 +212,11 @@
if(values.result.modelType && values.result.modelType === 'LLM'){ if(values.result.modelType && values.result.modelType === 'LLM'){
modelParamsShow.value = true; modelParamsShow.value = true;
} }
if (values.result.activateFlag) {
modelActivate.value = true;
}else{
modelActivate.value = false;
}
if(values.result.modelParams){ if(values.result.modelParams){
modelParams.value = JSON.parse(values.result.modelParams) modelParams.value = JSON.parse(values.result.modelParams)
} }
@ -303,6 +311,11 @@
values.modelParams = JSON.stringify(modelParams); values.modelParams = JSON.stringify(modelParams);
} }
} }
if(modelActivate.value){
values.activateFlag = 1
}else{
values.activateFlag = 0;
}
values.credential = JSON.stringify(credential); values.credential = JSON.stringify(credential);
// //
if (!values.id) { if (!values.id) {
@ -330,6 +343,7 @@
function cancel() { function cancel() {
dataIndex.value = 'list'; dataIndex.value = 'list';
closeModal(); closeModal();
emit('success');
} }
/** /**
@ -354,7 +368,10 @@
values.provider = modelData.value.value; values.provider = modelData.value.value;
} }
// //
await testConn(values); await testConn(values).then((result) => {
modelActivate.value = true;
save();
});
} catch (e) { } catch (e) {
if (e.hasOwnProperty('errorFields')) { if (e.hasOwnProperty('errorFields')) {
activeKey.value = 1; activeKey.value = 1;
@ -424,6 +441,7 @@
activeKey, activeKey,
modelParams, modelParams,
modelParamsShow, modelParamsShow,
modelActivate,
modelParamsRef, modelParamsRef,
filterOption, filterOption,
getTitle, getTitle,