前端源码 v3.9.1

This commit is contained in:
JEECG
2026-01-21 18:14:55 +08:00
parent 41877a6e8b
commit 901f05ed21
150 changed files with 38968 additions and 2698 deletions

View File

@ -2,30 +2,35 @@
<div class="p-2">
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
<div class="flex header">
<a-input
<JInput
@pressEnter="loadFlowData"
class="header-search"
size="small"
v-model:value="searchText"
placeholder="请输入流程名称,回车搜索"
></a-input>
/>
</div>
<a-row :span="24">
<a-col :span="12" v-for="item in flowList" @click="handleSelect(item)">
<a-card :style="item.id === flowId ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%;align-items:center">
<img :src="getImage(item.icon)" class="flow-icon"/>
<div style="display: grid;margin-left: 5px;align-items: center">
<span class="checkbox-name ellipsis">{{ item.name }}</span>
<div class="flex text-status" v-if="item.metadata && item.metadata.length>0">
<span class="tag-input">输入</span>
<div v-for="(metaItem, index) in item.metadata">
<a-tag color="#f2f3f8" class="tags-meadata">
<span v-if="index<3" class="tag-text">{{ metaItem.field }}</span>
</a-tag>
<!-- begin 流程选择支持单选和多选 -->
<a-card :style="getCardStyle(item)" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%;align-items:center; justify-content: space-between">
<div style="display: flex; align-items:center; flex: 1; overflow: hidden; margin-right: 10px;">
<img :src="getImage(item.icon)" class="flow-icon"/>
<div style="display: grid;margin-left: 5px;align-items: center">
<span class="checkbox-name ellipsis">{{ item.name }}</span>
<div class="flex text-status" v-if="item.metadata && item.metadata.length>0">
<span class="tag-input">输入</span>
<div v-for="(metaItem, index) in item.metadata">
<a-tag color="#f2f3f8" class="tags-meadata">
<span v-if="index<3" class="tag-text">{{ metaItem.field }}</span>
</a-tag>
</div>
</div>
</div>
</div>
<a-checkbox v-if="multiple" v-model:checked="item.checked" @click.stop @change="(e)=>handleChange(e,item)"></a-checkbox>
<!-- end 流程选择支持单选和多选 -->
</div>
<div class="text-desc mt-10">
{{ item.descr || '暂无描述' }}
@ -33,8 +38,13 @@
</a-card>
</a-col>
</a-row>
<div v-if="flowId" class="use-select">
已选择 <span class="ellipsis" style="max-width: 150px">{{flowData.name}}</span>
<div v-if="showFooterSelection" class="use-select">
<template v-if="!multiple">
已选择 <span class="ellipsis" style="max-width: 100px">{{flowData.name}}</span>
</template>
<template v-else>
已选择 {{ flowId.length }} 个流程
</template>
<span style="margin-left: 8px; color: #3d79fb; cursor: pointer" @click="handleClearClick">清空</span>
</div>
<Pagination
@ -54,10 +64,11 @@
</template>
<script lang="ts">
import { ref, unref } from 'vue';
import { ref, unref, computed } from 'vue';
import BasicModal from '@/components/Modal/src/BasicModal.vue';
import { useModal, useModalInner } from '@/components/Modal';
import { Pagination } from 'ant-design-vue';
import {JInput} from "@/components/Form";
import { list } from '@/views/super/airag/aiknowledge/AiKnowledgeBase.api';
import knowledge from '/@/views/super/airag/aiknowledge/icon/knowledge.png';
import { cloneDeep } from 'lodash-es';
@ -71,14 +82,20 @@
components: {
Pagination,
BasicModal,
JInput,
},
emits: ['success', 'register'],
props: {
multiple:{ type: Boolean, default: false },
// 排除的流程ID多个逗号分隔
excludedIds: { type: String, default: '' },
},
setup(props, { emit }) {
const title = ref<string>('选择流程');
//应用类型
const flowId = ref<any>([]);
//流程数据
const flowList = ref<any>({});
const flowList = ref<any>([]);
//选中的数据
const flowData = ref<any>({})
//当前页数
@ -93,9 +110,16 @@
const pageSizeOptions = ref<any>(['10', '20', '30']);
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
flowId.value = data.flowId ? cloneDeep(data.flowId) : '';
flowData.value = data.flowData ? cloneDeep(data.flowData) : {};
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if (props.multiple) {
flowId.value = data.flowId ? (Array.isArray(data.flowId) ? cloneDeep(data.flowId) : data.flowId.split(',')) : [];
flowData.value = data.flowData ? cloneDeep(data.flowData) : [];
} else {
flowId.value = data.flowId ? cloneDeep(data.flowId) : '';
flowData.value = data.flowData ? cloneDeep(data.flowData) : {};
}
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px', height: 'calc(100% - 20px)', overflowY: 'auto' } });
//update-end---author:wangshuai---date:2025-12-24---for:流程选择支持单选和多选---
loadFlowData();
});
@ -116,20 +140,27 @@
//复选框选中事件
const handleSelect = (item) => {
if(flowId.value === item.id){
flowId.value = "";
flowData.value = null;
return;
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if(!props.multiple) {
if (flowId.value === item.id) {
flowId.value = "";
flowData.value = null;
return;
}
flowId.value = item.id;
flowData.value = item;
} else {
item.checked = !item.checked;
updateMultipleSelection(item);
}
flowId.value = item.id;
flowData.value = item;
//update-end---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
};
/**
* 加载AI流程
*/
function loadFlowData() {
let params = {
let params: Recordable = {
pageNo: pageNo.value,
pageSize: pageSize.value,
column: 'createTime',
@ -137,10 +168,21 @@
name: searchText.value,
status: 'enable,release'
};
// 排除的流程ID多个逗号分隔
if (props.excludedIds) {
params.excludedIds = props.excludedIds;
}
getAiFlowList(params).then((res) =>{
if(res){
for (const data of res.records) {
data.metadata = getMetadata(data.metadata);
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if (props.multiple && Array.isArray(flowId.value) && flowId.value.includes(data.id)) {
data.checked = true;
}
//update-end---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
}
flowList.value = res.records;
total.value = res.total;
@ -170,8 +212,18 @@
* 清空选中状态
*/
function handleClearClick() {
flowId.value = "";
flowData.value = null;
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if (!props.multiple) {
flowId.value = "";
flowData.value = null;
} else {
flowId.value = [];
flowData.value = [];
if (flowList.value && Array.isArray(flowList.value)) {
flowList.value.forEach(item => item.checked = false);
}
}
//update-end---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
}
/**
@ -194,6 +246,41 @@
let inputsArr = parse['inputs'];
return [...inputsArr];
}
/*===========begin 流程选择支持多选 ===========*/
function handleChange(e, item) {
updateMultipleSelection(item);
}
function updateMultipleSelection(item) {
if (item.checked) {
if (!flowId.value.includes(item.id)) {
flowId.value.push(item.id);
flowData.value.push(item);
}
} else {
const index = flowId.value.indexOf(item.id);
if (index > -1) {
flowId.value.splice(index, 1);
flowData.value.splice(index, 1);
}
}
}
const showFooterSelection = computed(() => {
if (props.multiple) {
return flowId.value && flowId.value.length > 0;
}
return !!flowId.value;
});
function getCardStyle(item) {
if (props.multiple) {
return item.checked ? { border: '1px solid #3370ff' } : {};
}
return item.id === flowId.value ? { border: '1px solid #3370ff' } : {};
}
/*===========end 流程选择支持多选 ===========*/
return {
registerModal,
@ -214,6 +301,9 @@
handleClearClick,
flowData,
getImage,
handleChange,
getCardStyle,
showFooterSelection,
};
},
};
@ -243,7 +333,7 @@
.list-footer {
position: absolute;
bottom: 0;
left: 260px;
left: 210px;
}
.checkbox-card {
margin-bottom: 10px;
@ -333,4 +423,14 @@
font-weight: 500;
max-width: 100%;
}
:deep(.jeecg-modal-wrapper){
height: calc(100% - 20px);
}
.scroll-container {
height: 480px;
overflow-y: auto;
padding-bottom: 20px;
}
</style>

View File

@ -12,19 +12,24 @@
</div>
<a-row :span="24">
<a-col :span="12" v-for="item in appKnowledgeOption" @click="handleSelect(item)">
<a-card :style="item.checked ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<a-card :style="getCardStyle(item)" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%; justify-content: space-between">
<div>
<img class="checkbox-img" :src="knowledge" />
<span class="checkbox-name">{{ item.name }}</span>
</div>
<a-checkbox v-model:checked="item.checked" @click.stop class="quantum-checker" @change="(e)=>handleChange(e,item)"> </a-checkbox>
<a-checkbox v-if="multiple" v-model:checked="item.checked" @click.stop class="quantum-checker" @change="(e)=>handleChange(e,item)"> </a-checkbox>
</div>
</a-card>
</a-col>
</a-row>
<div v-if="knowledgeIds.length > 0" class="use-select">
已选择 {{ knowledgeIds.length }} 知识库
<div v-if="knowledgeIds && knowledgeIds.length > 0" class="use-select">
<template v-if="!multiple">
已选择 <span class="ellipsis" style="max-width: 150px">{{knowledgeData.name}}</span>
</template>
<template v-else>
已选择 {{ knowledgeIds.length }} 知识库
</template>
<span style="margin-left: 8px; color: #3d79fb; cursor: pointer" @click="handleClearClick">清空</span>
</div>
<Pagination
@ -59,6 +64,10 @@
BasicModal,
},
emits: ['success', 'register'],
props: {
multiple:{ type: Boolean, default: true },
type: { type: String, default: 'knowledge' }
},
setup(props, { emit }) {
const title = ref<string>('添加关联知识库');
@ -80,8 +89,15 @@
const pageSizeOptions = ref<any>(['10', '20', '30']);
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds.split(',')) : [];
knowledgeData.value = data.knowledgeDataList ? cloneDeep(data.knowledgeDataList) : [];
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if (props.multiple) {
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds.split(',')) : [];
knowledgeData.value = data.knowledgeDataList ? cloneDeep(data.knowledgeDataList) : [];
} else {
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds) : '';
knowledgeData.value = data.knowledgeData ? cloneDeep(data.knowledgeData) : {};
}
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
loadKnowledgeData();
});
@ -91,7 +107,13 @@
*/
async function handleOk() {
console.log("知识库确定选中的值",knowledgeData.value);
emit('success', knowledgeIds.value, knowledgeData.value);
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if (props.multiple) {
emit('success', knowledgeIds.value, knowledgeData.value);
} else {
emit('success', knowledgeIds.value, knowledgeData.value);
}
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
handleCancel();
}
@ -104,27 +126,39 @@
//复选框选中事件
function handleSelect(item){
let id = item.id;
const target = appKnowledgeOption.value.find((item) => item.id === id);
if (target) {
target.checked = !target.checked;
}
//存放选中的知识库的id
if (!knowledgeIds.value || knowledgeIds.value.length == 0) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
return;
}
let findIndex = knowledgeIds.value.findIndex((item) => item === id);
if (findIndex === -1) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if(!props.multiple) {
if (knowledgeIds.value === item.id) {
knowledgeIds.value = "";
knowledgeData.value = null;
return;
}
knowledgeIds.value = item.id;
knowledgeData.value = item;
} else {
knowledgeIds.value.splice(findIndex, 1);
knowledgeData.value.splice(findIndex, 1);
let id = item.id;
const target = appKnowledgeOption.value.find((item) => item.id === id);
if (target) {
target.checked = !target.checked;
}
//存放选中的知识库的id
if (!knowledgeIds.value || knowledgeIds.value.length == 0) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
return;
}
let findIndex = knowledgeIds.value.findIndex((item) => item === id);
if (findIndex === -1) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
} else {
knowledgeIds.value.splice(findIndex, 1);
knowledgeData.value.splice(findIndex, 1);
}
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
}
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
}
/**
@ -135,10 +169,11 @@
pageNo: pageNo.value,
pageSize: pageSize.value,
name: searchText.value,
type: props.type,
};
list(params).then((res) => {
if (res.success) {
if (knowledgeIds.value.length > 0) {
if (props.multiple && knowledgeIds.value.length > 0) {
for (const item of res.result.records) {
if (knowledgeIds.value.includes(item.id)) {
item.checked = true;
@ -171,11 +206,18 @@
* 清空选中状态
*/
function handleClearClick() {
knowledgeIds.value = [];
knowledgeData.value = [];
appKnowledgeOption.value.forEach((item) => {
item.checked = false;
});
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if (!props.multiple) {
knowledgeIds.value = "";
knowledgeData.value = null;
} else {
knowledgeIds.value = [];
knowledgeData.value = [];
appKnowledgeOption.value.forEach((item) => {
item.checked = false;
});
}
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
}
/**
@ -197,6 +239,18 @@
}
}
/**
* 获取卡片样式
*
* @param item
*/
function getCardStyle(item) {
if (props.multiple) {
return item.checked ? { border: '1px solid #3370ff' } : {};
}
return item.id === knowledgeIds.value ? { border: '1px solid #3370ff' } : {};
}
return {
registerModal,
title,
@ -215,12 +269,19 @@
loadKnowledgeData,
handleClearClick,
handleChange,
getCardStyle,
knowledgeData,
};
},
};
</script>
<style scoped lang="less">
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header {
color: #646a73;
width: 100%;

View File

@ -34,6 +34,9 @@ export default {
if(!data.metadata.hasOwnProperty("temperature") ){
data.metadata['temperature'] = 0.7;
}
if(!data.metadata.hasOwnProperty("timeout") ){
data.metadata['timeout'] = 60;
}
}else{
if(!data.metadata.hasOwnProperty("topNumber") ){
data.metadata['topNumber'] = 4;
@ -41,6 +44,9 @@ export default {
if(!data.metadata.hasOwnProperty("similarity") ){
data.metadata['similarity'] = 0.76;
}
if(!data.metadata.hasOwnProperty("timeout") ){
data.metadata['timeout'] = 60;
}
}
setTimeout(()=>{
aiModelSeniorFormRef.value.setModalParams(data.metadata);

View File

@ -0,0 +1,364 @@
<template>
<BasicModal
destroyOnClose
@register="registerModal"
:canFullscreen="false"
width="1000px"
@ok="handleOk"
@cancel="handleCancel"
okText="使用"
wrapClassName="ai-rag-generate-prompt-modal"
:confirmLoading="loading"
>
<div class="prompt-market-content">
<!-- 搜索区域 -->
<div class="search-section">
<a-input-search
v-model:value="searchText"
placeholder="搜索提示词名称或描述"
style="width: 300px"
@search="handleSearch"
@pressEnter="handleSearch"
/>
</div>
<!-- 提示词列表 -->
<div class="prompt-list-section">
<a-spin :spinning="loading">
<template v-if="promptList.length > 0">
<a-row :gutter="[24, 24]">
<a-col v-for="item in promptList" :key="item.id" :xs="24" :sm="12" :md="8" :lg="8">
<div class="prompt-card" @click="handleSelectPrompt(item)">
<a-card :class="['prompt-item-card', { selected: selectedPrompt?.id === item.id }]" :hoverable="true" size="small">
<template #title>
<div class="card-title">
<span class="title-text">{{ item.name }}</span>
</div>
</template>
<div class="card-content">
<p class="description">{{ item.description || item.desc }}</p>
<div class="card-footer" >
<span class="create-time">
{{ formatTime(item.createTime) }}
</span>
</div>
</div>
</a-card>
</div>
</a-col>
</a-row>
</template>
<!-- 空状态 -->
<empty v-else description="暂无提示词数据" class="empty-state" />
</a-spin>
</div>
<!-- 分页区域 -->
<div class="pagination-section">
<Pagination
v-model:current="pagination.current"
v-model:pageSize="pagination.pageSize"
:total="pagination.total"
:show-size-changer="true"
:page-size-options="['10', '20', '30', '50']"
:show-quick-jumper="true"
@change="handlePageChange"
@showSizeChange="handleSizeChange"
/>
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import { ref, reactive } from 'vue';
import { Empty } from 'ant-design-vue';
import BasicModal from '@/components/Modal/src/BasicModal.vue';
import { useModalInner } from '@/components/Modal';
import { list } from '@/views/super/airag/aiprompts/AiragPrompts.api';
import { formatToDateTime } from '@/utils/dateUtil';
import { Pagination } from 'ant-design-vue';
export default {
name: 'AiAppPromptMarketModal',
components: {
BasicModal,
Pagination,
Empty,
},
emits: ['ok', 'register', 'select'],
setup(props, { emit }) {
// 提示词列表
const promptList = ref<any[]>([]);
// 加载状态
const loading = ref<boolean>(false);
// 搜索文本
const searchText = ref<string>('');
// 选中的提示词
const selectedPrompt = ref<any>(null);
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 12,
total: 0,
});
// 注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async () => {
loading.value = false;
resetField();
await getPromptList();
setModalProps({
height: 600,
bodyStyle: { padding: '24px' },
});
});
/**
*
*/
function resetField() {
promptList.value = [];
selectedPrompt.value = null;
searchText.value = '';
pagination.current = 1;
}
/**
* 获取提示词列表
*/
async function getPromptList() {
loading.value = true;
try {
const params = {
pageNo: pagination.current,
pageSize: pagination.pageSize,
name: searchText.value ? `*${searchText.value}*` : '',
};
const res = await list(params);
console.log('获取提示词列表成功:', res);
if (res?.records) {
promptList.value = res?.records || [];
pagination.total = res?.total || 0;
} else {
promptList.value = [];
pagination.total = 0;
}
} catch (error) {
console.error('获取提示词列表失败:', error);
promptList.value = [];
pagination.total = 0;
} finally {
loading.value = false;
}
}
/**
* 处理搜索
*/
function handleSearch() {
pagination.current = 1;
getPromptList();
}
/**
* 处理页码变化
*/
function handlePageChange(page: number, pageSize: number) {
pagination.current = page;
pagination.pageSize = pageSize;
getPromptList();
}
/**
* 处理页面大小变化
*/
function handleSizeChange(current: number, size: number) {
pagination.current = current;
pagination.pageSize = size;
getPromptList();
}
/**
* 选择提示词
*/
function handleSelectPrompt(item: any) {
selectedPrompt.value = item;
}
/**
* 格式化时间
*/
function formatTime(time) {
console.log('formatTime:', formatToDateTime(time));
return formatToDateTime(time);
}
/**
* 保存
*/
async function handleOk() {
if (selectedPrompt.value) {
emit('ok', selectedPrompt.value.content);
} else {
emit('ok');
}
handleCancel();
}
/**
* 取消
*/
function handleCancel() {
closeModal();
}
return {
registerModal,
handleOk,
handleCancel,
promptList,
loading,
searchText,
selectedPrompt,
pagination,
handleSearch,
handlePageChange,
handleSizeChange,
handleSelectPrompt,
formatTime,
};
},
};
</script>
<style scoped lang="less">
.prompt-market-content {
min-height: 400px;
padding: 16px;
.search-section {
margin-bottom: 24px;
display: flex;
justify-content: flex-start;
.ant-input-search {
max-width: 400px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border-radius: 6px;
}
}
.prompt-list-section {
.prompt-card {
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
&:hover {
transform: translateY(-4px);
}
.prompt-item-card {
border-radius: 8px;
overflow: hidden;
height: 150px; // 增加卡片高度
transition: all 0.3s ease;
border: 1px solid #e8e8e8;
.ant-card-head {
border-bottom: 1px solid #f0f0f0;
padding: 12px 16px;
}
.ant-card-body {
padding: 12px 16px;
height: calc(100% - 48px); // 调整内容区域高度
display: flex;
flex-direction: column;
}
&.selected {
border-color: #1890ff;
background-color: #e6f7ff; // 添加选中背景色
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
.card-title .title-text {
color: #1890ff;
}
}
.card-title {
display: flex;
align-items: center;
.title-text {
font-weight: 600;
font-size: 16px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.card-content {
flex: 1; // 使用flex让内容区域自适应
display: flex;
flex-direction: column;
justify-content: space-between; // 让内容和时间信息在垂直方向上分布
.description {
color: #666;
font-size: 13px;
line-height: 1.5;
margin: 8px 0;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
flex: 1; // 让描述区域自适应
}
.card-footer {
margin-top: 8px; // 调整间距
padding-top: 8px;
border-top: 1px solid #f5f5f5;
display: flex;
justify-content: flex-end;
.create-time {
font-size: 12px;
color: #999;
}
}
}
}
}
}
.pagination-section {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: flex-end;
}
.empty-state {
margin-top: 40px;
}
}
// 模态框整体样式
.ai-rag-generate-prompt-modal {
.ant-modal-body {
padding: 0 !important;
}
.ant-modal-header {
border-radius: 8px 8px 0 0;
}
}
</style>

View File

@ -42,7 +42,7 @@
<template #label>
<div style="display: flex;justify-content: space-between;width: 100%;">
<span>关联流程</span>
<span v-if="!isRelease" @click="handleAddFlowClick" class="knowledge-txt">
<span v-if="!isRelease" @click="handleAddFlowClick('chatFLow')" class="knowledge-txt">
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 2px"></Icon>添加
</span>
</div>
@ -63,7 +63,7 @@
</div>
</div>
</div>
<Icon v-if="!isRelease" @click="handleDeleteFlow" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
<Icon v-if="!isRelease" @click="handleDeleteFlow('chatFLow')" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
</div>
</a-card>
<div v-else class="data-empty-text">
@ -78,12 +78,20 @@
<template #label>
<div class="prompt-title-padding item-title space-between">
<span>提示词</span>
<a-button v-if="!isRelease" size="middle" @click="generatedPrompt" ghost>
<span style="align-items: center;display:flex">
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"/></svg>
<span style="margin-left: 4px">生成</span>
</span>
</a-button>
<div style="align-items: center;display:flex;justify-content: center" v-if="!isRelease">
<a-button size="middle" ghost>
<span style="align-items: center;display:flex" @click="openPromptApps">
<Icon icon="ant-design:appstore-outlined"></Icon>
<span style="margin-left: 4px">模版</span>
</span>
</a-button>
<a-button size="middle" ghost>
<span style="align-items: center;display:flex" @click="generatedPrompt">
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"/></svg>
<span style="margin-left: 4px">生成</span>
</span>
</a-button>
</div>
</div>
</template>
<a-textarea :disabled="isRelease" :rows="8" v-model:value="formState.prompt" placeholder="请输入提示词"/>
@ -206,7 +214,7 @@
<span @click="handleParamSettingClick('knowledge')" class="knowledge-txt">
<Icon icon="ant-design:setting-outlined" size="13" style="margin-right: 2px"></Icon>参数配置
</span>
<span @click="handleAddKnowledgeIdClick" class="knowledge-txt">
<span @click="handleAddKnowledgeIdClick('knowledge')" class="knowledge-txt">
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 2px"></Icon>添加
</span>
</div>
@ -218,10 +226,10 @@
<div style="display: flex; width: 100%; justify-content: space-between">
<div>
<img class="knowledge-img" :src="knowledge" />
<span class="knowledge-name" style="color: #e03e2d;text-decoration: line-through" v-if="item.type">{{ item.name }}</span>
<span class="knowledge-name" style="color: #e03e2d;text-decoration: line-through" v-if="item.isDelete">{{ item.name }}</span>
<span class="knowledge-name" v-else>{{ item.name }}</span>
</div>
<Icon v-if="!isRelease" @click="handleDeleteKnowledge(item.id)" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
<Icon v-if="!isRelease" @click="handleDeleteKnowledge(item.id,'knowledge')" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
</div>
</a-card>
</a-col>
@ -232,6 +240,49 @@
</a-form-item>
</div>
</a-col>
<!-- 关联工作流多个 -->
<a-col :span="24" v-if="formState.type==='chatSimple'" class="mt-10">
<div class="prologue-chunk">
<a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol">
<template #label>
<div style="display: flex;justify-content: space-between;width: 100%;">
<span>关联流程</span>
<span v-if="!isRelease" @click="handleAddFlowClick('chatSimple')" class="knowledge-txt">
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 2px"></Icon>添加
</span>
</div>
</template>
<a-row :span="24">
<a-col :span="12" v-for="flowData in flowDataList" v-if="flowDataList && flowDataList.length>0">
<a-card hoverable class="knowledge-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%; justify-content: space-between;">
<div style="width: 100%;display: flex;">
<img :src="getFlowImage(flowData.icon)" class="flow-icon"/>
<div style="display: grid;margin-left: 5px;align-items: center;width: calc(100% - 20px)">
<span class="flow-name" style="color: #e03e2d;text-decoration: line-through" v-if="flowData.type">{{ flowData.name }}</span>
<span v-else class="flow-name ellipsis align-items: center;">{{ flowData.name }}</span>
<div class="flex text-status" v-if="flowData.metadata && flowData.metadata.length>0">
<span class="tag-input">输入</span>
<div v-for="(metaItem, index) in flowData.metadata">
<a-tag color="#f2f3f8" class="tags-meadata">
<span v-if="index<5" class="tag-text">{{ metaItem.field }}</span>
</a-tag>
</div>
</div>
</div>
</div>
<Icon v-if="!isRelease" @click="handleDeleteFlow('chatSimple',flowData.id)" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
</div>
</a-card>
</a-col>
<div v-else class="data-empty-text">
工作流支持通过可视化的方式对大语言模型脚本增强等功能进行组合从而实现复杂稳定的业务流程编排例如旅行规划报告分析
</div>
</a-row>
</a-form-item>
</div>
</a-col>
<!-- 插件关联区块 -->
<a-col :span="24" v-if="formState.type==='chatSimple'" class="mt-10">
<div class="prologue-chunk">
@ -269,6 +320,86 @@
</a-form-item>
</div>
</a-col>
<a-col :span="24" class="mt-10" v-if="formState.type==='chatSimple'">
<a-collapse v-model:activeKey="memoryActiveKey" :bordered="false" style="background-color: transparent">
<a-collapse-panel key="1">
<template #header>
<div style="font-weight: 600;color: rgba(32,41,69,0.62);font-size: 14px;justify-content: space-between;display: flex; width: 100%">
<span>记忆</span>
<a-switch @click.prevent.stop="" :disabled="isRelease" v-model:checked="izOpenMemoryChecked" checked-children="" un-checked-children="" @change="handleMemoryChange"></a-switch>
</div>
</template>
<div v-if="izOpenMemoryChecked">
<div class="prologue-chunk">
<div style="display: flex; justify-content: space-between; width: 100%;margin-left: 2px;">
<div class="item-title">变量</div>
<div v-if="!isRelease">
<span @click="handleAddVariable" class="knowledge-txt">
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 2px"></Icon>添加
</span>
</div>
</div>
<div v-if="formState.variables">
<div style="display: flex; flex-wrap: wrap; gap: 5px; padding-top: 8px;margin-bottom: 8px">
<a-tag v-for="(item, index) in variablesList"
:key="index"
color="#2e2e3814"
style="color: #6b6b75;border-radius: 4px; border: none; padding: 0 5px;">
{{ item.name }}
</a-tag>
</div>
</div>
<div v-else class="data-empty-text">
用于保存用户个人信息让智能体记住用户的特征使回复更加个性化
</div>
</div>
<div class="prologue-chunk" style="margin-top: 10px;">
<div style="display: flex; justify-content: space-between; width: 100%;margin-left: 2px;">
<div class="item-title">长期记忆</div>
<div v-if="!isRelease">
<span @click="handleAddKnowledgeIdClick('memory')" class="knowledge-txt">
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 2px"></Icon>添加
</span>
</div>
</div>
<div v-if="memoryData" class="prologue-chunk">
<a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" v-bind="validateInfos.memoryId">
<a-card hoverable class="knowledge-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%; justify-content: space-between">
<div>
<img class="knowledge-img" :src="knowledge" />
<span class="knowledge-name" style="color: #e03e2d;text-decoration: line-through" v-if="memoryData.isDelete">{{ memoryData.name }}</span>
<span class="knowledge-name" v-else>{{ memoryData.name }}</span>
</div>
<Icon v-if="!isRelease" @click="handleDeleteKnowledge(memoryData.id,'memgory')" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
</div>
</a-card>
</a-form-item>
</div>
<div v-else class="data-empty-text">
开启后可总结聊天对话的内容并用于更好的响应用户的消息
</div>
</div>
<div class="prologue-chunk" style="margin-top: 20px;">
<div class="prompt-title-padding item-title space-between">
<span>记忆与变量提示词</span>
<div style="align-items: center;display:flex;justify-content: center" v-if="!isRelease">
<a-button size="middle" ghost>
<span style="align-items: center;display:flex" @click="generateVariablePrompt">
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"/></svg>
<span style="margin-left: 4px">生成</span>
</span>
</a-button>
</div>
</div>
<a-spin :spinning="memoryLoading" tip="为您编排应用程序中…">
<a-textarea :disabled="memoryLoading" :rows="6" v-model:value="formState.memoryPrompt" placeholder="点击生成按钮生成记忆与变量提示词"/>
</a-spin>
</div>
</div>
</a-collapse-panel>
</a-collapse>
</a-col>
<a-col :span="24" class="mt-10">
<div class="prologue-chunk">
<a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" v-bind="validateInfos.msgNum">
@ -290,6 +421,28 @@
</div>
</a-form-item>
</a-row>
<a-row>
<a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol">
<div style="display: flex;margin-top: 10px">
<div style="margin-left: 2px">开启绘画能力</div>
<a-switch :disabled="isRelease" v-model:checked="izDrawChecked" checked-children="" un-checked-children="" @change="handleDrawChange"></a-switch>
</div>
</a-form-item>
</a-row>
<a-row v-if="izDrawChecked" class="mt-10">
<a-col :span="24">
<a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" v-bind="validateInfos.drawModelId">
<span style="margin-left: 2px; margin-bottom: 5px">绘画模型</span>
<JDictSelectTag
v-model:value="formState.drawModelId"
:disabled="isRelease"
placeholder="请选择会话模型"
dict-code="airag_model where model_type = 'IMAGE' and activate_flag = 1,name,id"
@change="handleDrawModelChange"
></JDictSelectTag>
</a-form-item>
</a-col>
</a-row>
</div>
</a-col>
</a-row>
@ -314,17 +467,19 @@
</BasicModal>
<!-- Ai知识库选择弹窗 -->
<AiAppAddKnowledgeModal @register="registerKnowledgeModal" @success="handleSuccess"></AiAppAddKnowledgeModal>
<AiAppAddKnowledgeModal :multiple="knowledgeMultiple" :type="knowledgeMultiple?'knowledge':'memory'" @register="registerKnowledgeModal" @success="handleSuccess"></AiAppAddKnowledgeModal>
<!-- 插件选择弹窗 -->
<AiAppAddMcpModal @register="registerMcpModal" @success="handleMcpSuccess"></AiAppAddMcpModal>
<!-- Ai添加流程弹窗 -->
<AiAppAddFlowModal @register="registerFlowModal" @success="handleAddFlowSuccess"></AiAppAddFlowModal>
<AiAppAddFlowModal @register="registerFlowModal" @success="handleAddFlowSuccess" :multiple="multiple"></AiAppAddFlowModal>
<!-- Ai配置弹窗 -->
<AiAppParamsSettingModal @register="registerParamsSettingModal" @ok="handleParamsSettingOk"></AiAppParamsSettingModal>
<!-- Ai应用新增编辑弹窗 -->
<AiAppModal @register="registerAiAppModal" @success="handelEditSuccess"></AiAppModal>
<!-- Ai生成器 -->
<AiAppGeneratedPromptModal @register="registerAiAppPromptModal" @ok="handleAiAppPromptOk"></AiAppGeneratedPromptModal>
<!-- Ai提示词选择弹窗 -->
<AiAppPromptMarketModal @register="registerAiPromptSelectModal" @ok="handleAiAppPromptOk"></AiAppPromptMarketModal>
<!-- Ai快捷指令 -->
<AiAppQuickCommandModal @register="registerAiAppCommandModal" @ok="handleAiAppCommandOk" @update-ok="handleAiAppCommandUpdateOk"></AiAppQuickCommandModal>
<!-- 对话设置弹窗 -->
@ -335,6 +490,8 @@
:existingSettings="conversationSettings"
@ok="handleSettingsOk"
/>
<!-- 用户变量 -->
<AiUserVariablesModal @register="registerVariablesModal" @ok="handleVariablesOk"></AiUserVariablesModal>
</div>
</template>
@ -342,15 +499,16 @@
import { ref, reactive, nextTick, computed, watch } from 'vue';
import BasicModal from '@/components/Modal/src/BasicModal.vue';
import { useModal, useModalInner } from '@/components/Modal';
import { Form, TimePicker } from 'ant-design-vue';
import { Form, TimePicker, Collapse, CollapsePanel } from 'ant-design-vue';
import { initDictOptions } from '@/utils/dict';
import {queryKnowledgeBathById, saveApp, queryById, queryFlowById} from '../AiApp.api';
import { queryKnowledgeBathById, saveApp, queryById, queryFlowById, queryFlowByIds, queryKnowledgeById, generateMemoryByAppId } from '../AiApp.api';
import { defHttp } from '@/utils/http/axios';
import JDictSelectTag from '@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import AiAppAddKnowledgeModal from './AiAppAddKnowledgeModal.vue';
import AiAppAddMcpModal from './AiAppAddMcpModal.vue';
import AiAppParamsSettingModal from './AiAppParamsSettingModal.vue';
import AiAppGeneratedPromptModal from './AiAppGeneratedPromptModal.vue';
import AiAppPromptMarketModal from './AiAppPromptMarketModal.vue';
import AiAppQuickCommandModal from './AiAppQuickCommandModal.vue';
import AiAppAddFlowModal from './AiAppAddFlowModal.vue';
import AiAppModal from './AiAppModal.vue';
@ -358,16 +516,17 @@
import ConversationSettingsModal from '../chat/components/ConversationSettingsModal.vue';
import knowledge from '/@/views/super/airag/aiknowledge/icon/knowledge.png';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
import JImageUpload from '@/components/Form/src/jeecg/components/JImageUpload.vue';
import defaultImg from '../img/ailogo.png';
import {getFileAccessHttpUrl, randomString, simpleDebounce} from "@/utils/common/compUtils";
import { getFileAccessHttpUrl, randomString } from "@/utils/common/compUtils";
import JSearchSelect from "@/components/Form/src/jeecg/components/JSearchSelect.vue";
import JMarkdownEditor from "@/components/Form/src/jeecg/components/JMarkdownEditor.vue";
import AiAppJson from './AiApp.json'
import draggable from 'vuedraggable';
import { VueDraggableNext as draggable } from 'vue-draggable-next';
import { useMessage } from "@/hooks/web/useMessage";
import defaultFlowImg from "@/assets/images/ai/aiflow.png";
import AiUserVariablesModal from "./AiUserVariablesModal.vue";
export default {
name: 'AiAppSettingModal',
components: {
@ -386,6 +545,8 @@
ConversationSettingsModal,
AiAppGeneratedPromptModal,
AiAppQuickCommandModal,
AiAppPromptMarketModal,
AiUserVariablesModal,
},
emits: ['success', 'register'],
setup(props, { emit }) {
@ -411,13 +572,20 @@
type: '',
modelId: '',
icon: '',
presetQuestion:''
presetQuestion:'',
memoryId: '',
variables: '',
izOpenMemory: 1,
memoryPrompt: '',
drawModelId: '',
});
//表单验证
const validatorRules = ref<any>({
name: [{ required: true, message: '请输入应用名称!' }],
modelId: [{ required: true, message: '请选择AI模型!' }],
flowId:[{ required: true, message: '请选择AI流程!' }]
flowId:[{ required: true, message: '请选择AI流程!' }],
drawModelId: [{ required: true, message: '请选择绘画模型!' }]
});
//注册form
const useForm = Form.useForm;
@ -428,6 +596,9 @@
const knowledgeIds = ref<any>('');
//知识库集合
const knowledgeDataList = ref<any>([]);
//记忆库的数据
const memoryData = ref<any>(null);
const knowledgeMultiple = ref<boolean>(true);
// 插件id集合只存id
const pluginIds = ref<any>([]);
// 插件对象集合包含name等
@ -450,6 +621,8 @@
const { createMessage } = useMessage();
//多会话模式选中状态
const multiSessionChecked = ref<boolean>(true);
//开启会话能力
const izDrawChecked = ref<boolean>(false);
// 是否已发布
const isRelease = ref<boolean>(false);
//对话设置弹窗ref
@ -458,6 +631,21 @@
const flowInputs = ref<any[]>([]);
//对话设置(用于调试模式)
const conversationSettings = ref<Record<string, any>>({});
//流程数据集合
const flowDataList = ref<any>([]);
//多个流程id
const flowIds = ref<string>('');
//是否多选
const multiple = ref<boolean>(false);
//变量数据
const variablesList = ref<any>([]);
//是否开启记忆
const izOpenMemoryChecked = ref<any>(false);
//记忆折叠面板
const memoryActiveKey = ref<any>();
//记忆提示词是否加载
const memoryLoading = ref<any>(false);
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
appId.value = data.id;
@ -497,6 +685,8 @@
const [registerAiAppModal, { openModal: aiAppModalOpen }] = useModal();
const [registerAiAppPromptModal, { openModal: aiAppPromptModalOpen }] = useModal();
const [registerAiAppCommandModal, { openModal: aiAppCommandModalOpen }] = useModal();
const [registerAiPromptSelectModal, { openModal: aiPromptSelectModalOpen }] = useModal();
const [registerVariablesModal, { openModal: aiVariablesModalOpen }] = useModal();
/**
* 保存
@ -506,6 +696,9 @@
let values = await validate();
setModalProps({ confirmLoading: true });
formState.knowledgeIds = knowledgeIds.value;
if(flowIds.value){
formState.flowId = flowIds.value;
}
await saveApp(formState);
emit('success')
} finally {
@ -547,12 +740,24 @@
/**
* 添加关联知识库
*
* @param type
*/
function handleAddKnowledgeIdClick() {
openModal(true, {
knowledgeIds: knowledgeIds.value,
knowledgeDataList: knowledgeDataList.value,
});
function handleAddKnowledgeIdClick(type) {
//update-begin---author:wangshuai---date:2025-12-29---for:【QQYUN-14265】【AI】支持记忆---
knowledgeMultiple.value = type === 'knowledge'
if(type === 'knowledge'){
openModal(true, {
knowledgeIds: knowledgeIds.value,
knowledgeDataList: knowledgeDataList.value,
});
} else {
openModal(true, {
knowledgeIds: formState.memoryId,
knowledgeData: memoryData.value,
});
}
//update-end---author:wangshuai---date:2025-12-29---for:【QQYUN-14265】【AI】支持记忆---
}
/**
@ -571,11 +776,16 @@
* @param knowledgeData
*/
function handleSuccess(knowledgeId, knowledgeData) {
knowledgeIds.value = cloneDeep(knowledgeId.join(','));
console.log("知识库id",knowledgeIds.value);
knowledgeDataList.value = cloneDeep(knowledgeData);
console.log("知识库的数据",knowledgeDataList.value);
formState.knowledgeIds = knowledgeIds.value;
//update-begin---author:wangshuai---date:2025-12-29---for:【QQYUN-14265】【AI】支持记忆---
if(knowledgeMultiple.value){
knowledgeIds.value = cloneDeep(knowledgeId.join(','));
knowledgeDataList.value = cloneDeep(knowledgeData);
formState.knowledgeIds = knowledgeIds.value;
} else {
formState.memoryId = knowledgeId;
memoryData.value = knowledgeData;
}
//update-end---author:wangshuai---date:2025-12-29---for:【QQYUN-14265】【AI】支持记忆---
}
/**
@ -594,14 +804,23 @@
/**
* 删除知识库
*/
function handleDeleteKnowledge(id) {
let array = knowledgeIds.value.split(',');
let findIndex = array.findIndex((item) => item === id);
if (findIndex != -1) {
array.splice(findIndex, 1);
knowledgeIds.value = array ? array.join(',') : '';
knowledgeDataList.value.splice(findIndex, 1);
formState.knowledgeIds = knowledgeIds.value;
function handleDeleteKnowledge(id, type) {
//update-begin---author:wangshuai---date:2025-12-29---for:【QQYUN-14265】【AI】支持记忆---
//update-begin---author:wangshuai---date:2026-01-14---for:【QQYUN-14562】【ai应用】记忆添加后×不掉---
if(type === 'knowledge'){
//update-end---author:wangshuai---date:2026-01-14---for:【QQYUN-14562】【ai应用】记忆添加后×不掉---
let array = knowledgeIds.value.split(',');
let findIndex = array.findIndex((item) => item === id);
if (findIndex != -1) {
array.splice(findIndex, 1);
knowledgeIds.value = array ? array.join(',') : '';
knowledgeDataList.value.splice(findIndex, 1);
formState.knowledgeIds = knowledgeIds.value;
}
} else {
formState.memoryId = "";
memoryData.value = null;
//update-end---author:wangshuai---date:2025-12-29---for:【QQYUN-14265】【AI】支持记忆---
}
}
@ -628,21 +847,21 @@
if (res.success && res.result) {
let result = res.result;
let idArray = ids.split(",");
let arr = [];
let arr:any = [];
for (const id of idArray) {
let filter = result.filter((item) => item.id === id);
if(filter && filter.length > 0) {
arr.push({ id: id, name: filter[0].name});
} else {
arr.push({ name: '该知识库已被删除', id: id,type: 'delete' })
arr.push({ name: '该知识库已被删除', id: id,isDelete: true })
}
}
knowledgeDataList.value = arr;
knowledgeIds.value = ids;
} else {
let arr = [];
let arr:any = [];
for (const id of ids) {
arr.push({ name: '该知识库已被删除', id: id})
arr.push({ name: '该知识库已被删除', id: id, isDelete: true })
}
knowledgeDataList.value = arr;
knowledgeIds.value = ids;
@ -733,9 +952,17 @@
/**
* 添加流程
* @param type 是否多选
*/
function handleAddFlowClick() {
registerFlowOpen(true,{ flowId: flowId.value, flowData: flowData.value })
function handleAddFlowClick(type) {
//update-begin---author:wangshuai---date:2025-12-24---for:【QQYUN-14267】创建应用的时候选择工作流让应用可以像调用MCP一样去调用流程: 单选和多选做区分---
multiple.value = type === 'chatSimple';
if(type === 'chatSimple'){
registerFlowOpen(true,{ flowId: flowIds.value, flowData: flowDataList.value })
} else {
registerFlowOpen(true,{ flowId: flowId.value, flowData: flowData.value })
}
//update-end---author:wangshuai---date:2025-12-24---for:【QQYUN-14267】创建应用的时候选择工作流让应用可以像调用MCP一样去调用流程: 单选和多选做区分---
}
/**
@ -743,18 +970,37 @@
* @param values
*/
function handleAddFlowSuccess(values) {
flowId.value = values.flowId;
formState.flowId = values.flowId;
flowData.value = values.flowData;
//update-begin---author:wangshuai---date:2025-12-24---for:【QQYUN-14267】创建应用的时候选择工作流让应用可以像调用MCP一样去调用流程: 单选和多选做区分---
if(multiple.value){
flowIds.value = values.flowId?.join(",");
flowDataList.value = values.flowData;
} else {
flowId.value = values.flowId;
formState.flowId = values.flowId;
flowData.value = values.flowData;
}
//update-end---author:wangshuai---date:2025-12-24---for:【QQYUN-14267】创建应用的时候选择工作流让应用可以像调用MCP一样去调用流程: 单选和多选做区分---
}
/**
* 删除流程
*/
function handleDeleteFlow() {
flowId.value = "";
formState.flowId = "";
flowData.value = null;
function handleDeleteFlow(type, id = '') {
//update-begin---author:wangshuai---date:2025-12-24---for:【QQYUN-14267】创建应用的时候选择工作流让应用可以像调用MCP一样去调用流程: 单选和多选做区分---
if(type === 'chatFLow'){
flowId.value = "";
formState.flowId = "";
flowData.value = null;
} else {
let array = flowIds.value.split(',');
let findIndex = array.findIndex((item) => item === id);
if (findIndex != -1) {
array.splice(findIndex, 1);
flowIds.value = array ? array.join(',') : '';
flowDataList.value.splice(findIndex, 1);
}
}
//update-end---author:wangshuai---date:2025-12-24---for:【QQYUN-14267】创建应用的时候选择工作流让应用可以像调用MCP一样去调用流程: 单选和多选做区分---
}
/**
@ -870,23 +1116,74 @@
//=========== end预设问题 ===========================
/**
* 根据多个流程id查询流程
*/
function getFlowDataByIds(ids) {
queryFlowByIds({ id: ids, pageNo:1, pageSize: 100 }).then((res) =>{
if(res.success && res.result){
let records = res.result.records || [];
let idArray = ids.split(",");
let arr:any = [];
let idsList:any = [];
for (const id of idArray) {
let item = records.find(r => r.id === id);
if (item) {
if(item.metadata){
try {
let metadata = JSON.parse(item.metadata);
if(metadata.inputs){
item.metadata = metadata.inputs;
}
} catch (e) {
console.error("metadata parse error", e);
}
}
arr.push(item);
} else {
arr.push({ name: '该流程已被删除', id: id, type: 'delete' });
}
idsList.push(id);
}
flowDataList.value = arr;
flowIds.value = idsList.join(",");
} else {
let arr:any = [];
let idArray = ids.split(",");
for (const id of idArray) {
arr.push({ name: '该流程已被删除', id: id, type: 'delete'})
}
flowDataList.value = arr;
flowIds.value = ids;
}
})
}
/**
* 清除参数
*/
function clearParam() {
knowledgeIds.value = '';
knowledgeDataList.value = [];
memoryData.value = null;
pluginIds.value = [];
pluginDataList.value = [];
plugins.value = [];
prologue.value = '';
flowId.value = '';
flowData.value = null;
flowDataList.value = [];
flowIds.value = '';
presetQuestion.value = '';
presetQuestionList.value = [{ key:1, sort: 1, descr: '' }];
quickCommandList.value = [];
quickCommand.value = '';
multiSessionChecked.value = true;
variablesList.value = [];
izOpenMemoryChecked.value = false;
memoryLoading.value = false;
memoryActiveKey.value = [];
}
/**
@ -907,6 +1204,14 @@
}else{
multiSessionChecked.value = true;
}
if(metadata.value?.izDraw){
izDrawChecked.value = metadata.value.izDraw === '1';
}else{
izDrawChecked.value = false;
}
if(metadata.value?.drawModelId){
formState.drawModelId = metadata.value.drawModelId;
}
}
if(data.presetQuestion){
presetQuestion.value = data.presetQuestion;
@ -943,10 +1248,32 @@
if (data.type === 'chatFLow' && data.flowId) {
getFlowDataById(data.flowId);
}
//根据流程的id查询流程信息
if (data.type === 'chatSimple' && data.flowId) {
getFlowDataByIds(data.flowId);
}
// 如果已有modelId查询模型信息并更新到metadata中
if (data.type === 'chatSimple' && data.modelId) {
handleModelIdChange(data.modelId);
}
// 存在记忆库的id,查询记忆库并显示
if(data.type === 'chatSimple' && data.memoryId){
getMemoryDataById(data.memoryId);
}
//变量
if(formState.variables){
variablesList.value = JSON.parse(formState.variables);
}
//是否开启记忆
if(formState.izOpenMemory){
console.log("formState.izOpenMemory:::",formState.izOpenMemory)
izOpenMemoryChecked.value = formState.izOpenMemory === 1;
} else {
izOpenMemoryChecked.value = false;
}
if(izOpenMemoryChecked.value){
memoryActiveKey.value = [1];
}
}
//============= begin 提示词 ================================
@ -1137,6 +1464,153 @@
console.error('获取模型信息失败', e);
}
}
function openPromptApps() {
aiPromptSelectModalOpen(true,{});
}
/**
* 获取记忆库的数据
*/
function getMemoryDataById(id) {
queryKnowledgeById({id: id}).then((res) =>{
if(res.success && res.result){
memoryData.value = res.result;
} else {
memoryData.value = { name: '该知识库已被删除', id: id, isDelete: true };
}
})
}
//================================================ begin 变量和记忆提示词功能 =========================================================
/**
* 添加变量
*/
function handleAddVariable() {
aiVariablesModalOpen(true,{
variables: formState.variables
})
}
/**
* 变量返回事件
*
* @param values
*/
function handleVariablesOk(values) {
if(values){
variablesList.value = values;
formState.variables = JSON.stringify(values);
}else{
formState.variables = "";
variablesList.value = [];
}
}
/**
* 是否开启会话模式返回值
* @param checked
*/
function handleMemoryChange(checked) {
if(checked){
formState.izOpenMemory = 1;
} else {
formState.izOpenMemory = 0;
}
}
/**
* 记忆和变量提示词
*/
async function generateVariablePrompt() {
formState.memoryPrompt = '';
memoryLoading.value = true;
let readableStream = await generateMemoryByAppId({ variables: formState.variables, memoryId: formState.memoryId }).catch(() => {
memoryLoading.value = false;
});
const reader = readableStream.getReader();
const decoder = new TextDecoder('UTF-8');
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
let result = decoder.decode(value, { stream: true });
const lines = result.split('\n\n');
for (const line of lines) {
if (line.startsWith('data:')) {
const content = line.replace('data:', '').trim();
if(!content){
continue;
}
if(!content.endsWith('}')){
buffer = buffer + line;
continue;
}
buffer = "";
renderText(content)
} else {
if(!line) {
continue;
}
if(!line.endsWith('}')) {
buffer = buffer + line;
continue;
}
buffer = "";
renderText(line)
}
}
}
}
/**
* 渲染文本
*
* @param item
*/
function renderText(item) {
try {
let parse = JSON.parse(item);
if (parse.event == 'MESSAGE') {
formState.memoryPrompt += parse.data.message;
if(memoryLoading.value){
memoryLoading.value = false;
}
}
if (parse.event == 'MESSAGE_END') {
memoryLoading.value = false;
}
if (parse.event == 'ERROR') {
formState.memoryPrompt = parse.data.message ? parse.data.message: '生成失败,请稍后重试!'
memoryLoading.value = false;
}
} catch (error) {
console.log('Error parsing update:', error);
}
}
//================================================ end 变量和记忆提示词功能 =========================================================
//================================================ begin 开启绘画 =========================================================
/**
* 开启会话能力回调
*/
function handleDrawChange(checked){
if(checked){
metadata.value.izDraw = "1";
}else{
metadata.value.izDraw = "0";
}
formState.metadata = JSON.stringify(metadata.value);
}
/**
* 会话模型改变回调
*/
function handleDrawModelChange(val){
metadata.value.drawModelId = val;
formState.metadata = JSON.stringify(metadata.value);
}
//================================================ end 开启绘画 ========================================================
return {
registerModal,
@ -1214,6 +1688,24 @@
handleEditSettings,
handleSettingsOk,
handleModelIdChange,
flowDataList,
multiple,
memoryData,
knowledgeMultiple,
registerAiPromptSelectModal,
openPromptApps,
handleAddVariable,
handleVariablesOk,
registerVariablesModal,
variablesList,
generateVariablePrompt,
izOpenMemoryChecked,
memoryLoading,
handleMemoryChange,
memoryActiveKey,
izDrawChecked,
handleDrawChange,
handleDrawModelChange,
};
},
};

View File

@ -0,0 +1,187 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="用户变量" :width="1000" @ok="handleSubmit" destroyOnClose>
<div class="p-4">
<div class="mb-4 text-gray-500"> 用于存储每个用户使用项目过程中需要持久化存储和读取的数据如用户的语言偏好个性化设置等 </div>
<JVxeTable
ref="tableRef"
toolbar
dragSort
:maxHeight="500"
:columns="columns"
:dataSource="dataSource"
:toolbarConfig="{ btns: ['remove', 'clearSelection'] }"
>
<template #toolbarSuffix>
<a-button type="primary" @click="handleAdd">
<Icon icon="ant-design:plus-outlined" />
新增
</a-button>
</template>
<template #action="props">
<div class="action-group">
<a-switch v-model:checked="props.row.enable" checked-children="" un-checked-children="" size="small" class="ml-2" />
<a-popconfirm title="确定删除吗?" @confirm="handleDelete(props)">
<Icon icon="ant-design:delete-outlined" class="cursor-pointer hover:text-red-500 ml-2" size="18" color="red" />
</a-popconfirm>
</div>
</template>
</JVxeTable>
</div>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
import { BasicModal, useModalInner } from '/src/components/Modal';
import { Icon } from '/src/components/Icon';
import { Button, Checkbox, Switch, Popconfirm } from 'ant-design-vue';
import { JVxeTypes, JVxeColumn, JVxeTableInstance } from '/src/components/jeecg/JVxeTable/types';
import { JVxeTable } from '/src/components/jeecg/JVxeTable';
import { useMessage } from '/src/hooks/web/useMessage';
export default defineComponent({
name: 'AiUserVariablesModal',
components: {
BasicModal,
Icon,
JVxeTable,
AButton: Button,
ACheckbox: Checkbox,
ASwitch: Switch,
APopconfirm: Popconfirm,
},
emits: ['register', 'ok'],
setup(props, { emit }) {
const { createMessage } = useMessage();
const tableRef = ref<JVxeTableInstance>();
// 定义列配置
const columns = ref<JVxeColumn[]>([
{
title: '名称',
key: 'name',
type: JVxeTypes.input,
width: 200,
placeholder: '请输入变量名',
validateRules: [
{ required: true, message: '名称不能为空' },
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '仅支持字母、数字和下划线,且以字母开头' },
],
},
{
title: '描述',
key: 'description',
type: JVxeTypes.input,
width: 300,
placeholder: '字段描述',
},
{
title: '默认值',
key: 'defaultValue',
type: JVxeTypes.input,
width: 200,
placeholder: '默认值',
},
{
title: '操作',
key: 'action',
type: JVxeTypes.slot,
slotName: 'action',
width: 220,
align: 'center',
},
]);
const dataSource = ref<any[]>([]);
const [registerModal, { setModalProps, closeModal }] = useModalInner((data) => {
// 如果传入了已有数据,进行初始化
if (data && data.variables) {
dataSource.value = JSON.parse(data.variables);
} else {
dataSource.value = [];
}
});
// 新增行
const handleAdd = () => {
if (dataSource.value.length > 9) {
createMessage.warn('最多支持10个变量');
return;
}
tableRef.value?.addRows({
name: '',
description: '',
defaultValue: '',
enable: true,
});
};
// 删除行
const handleDelete = (props) => {
tableRef.value?.removeRows(props.row);
};
const handleSubmit = async () => {
// 校验表格
const errMap = await tableRef.value?.validateTable();
if (errMap) {
createMessage.error('请检查表单填写是否正确');
return;
}
// 获取全量数据
const tableData = tableRef.value?.getTableData();
console.log('保存用户变量:', tableData);
closeModal();
emit('ok', tableData);
};
return {
registerModal,
tableRef,
columns,
dataSource,
handleAdd,
handleDelete,
handleSubmit,
};
},
});
</script>
<style scoped>
.p-4 {
padding: 1rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.text-gray-500 {
color: #6b7280;
}
.ml-2 {
margin-left: 0.5rem;
}
.cursor-pointer {
cursor: pointer;
}
.hover\:text-red-500:hover {
color: #ef4444;
}
.action-group {
display: flex;
align-items: center;
justify-content: center;
}
</style>