【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;
display: flex;
overflow: hidden;
z-index: 999;
z-index: 800;
border: 1px solid #eeeeee;
:deep(.ant-spin) {
position: absolute;

View File

@ -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-12297AI---
});
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)
}

View File

@ -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%;"
></JDictSelectTag>
</a-form-item>

View File

@ -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",
},
},
{

View File

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

View File

@ -47,6 +47,9 @@
<li class="flex mr-14">
<span class="label">模型类型</span>
<span class="described">{{ item.modelType_dictText }}</span>
<a-tooltip v-if="!item.activateFlag" title="未激活模型暂无法被系统其他功能调用,激活后可正常使用。">
<span class="no-activate">未激活</span>
</a-tooltip>
</li>
<li class="flex mr-14 mt-6">
<span class="label">基础模型</span>
@ -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;

View File

@ -77,6 +77,7 @@
</AutoComplete>
</template>
</BasicForm>
<a-alert v-if="!modelActivate" message="模型未激活,请通过下方「保存并激活」按钮激活当前模型" type="warning" show-icon />
</div>
</a-tab-pane>
<a-tab-pane :key="2" v-if="modelParamsShow">
@ -96,9 +97,9 @@
</div>
<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="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 v-else #footer> </template>
</BasicModal>
@ -159,6 +160,8 @@
const modelParamsRef = ref();
//loading
const testLoading = ref<boolean>(false);
//
const modelActivate = ref<boolean>(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,