mirror of https://github.com/jeecgboot/jeecg-boot
【v3.8.3】airag优化体验升级
parent
8c64db46e5
commit
d76842ae07
|
@ -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;
|
||||||
|
|
|
@ -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-12297】【AI】聊天,超时以后提示---
|
//update-end---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天,超时以后提示---
|
||||||
});
|
});
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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("向量化失败!");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue