mirror of https://github.com/jeecgboot/jeecg-boot
【v3.8.3】airag优化体验升级
parent
8c64db46e5
commit
d76842ae07
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -277,7 +277,7 @@
|
|||
async function handleVectorization(id) {
|
||||
rebuild({ knowIds: id }).then((res) =>{
|
||||
if(res.success){
|
||||
createMessage.success("向量化成功!");
|
||||
createMessage.success("操作成功,开始异步重建知识库,请稍后查看!");
|
||||
reload();
|
||||
}else{
|
||||
createMessage.warning("向量化失败!");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue