feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -0,0 +1,356 @@
<script setup lang="ts">
/* cspell:ignore tryit */
import type {AgentCapabilityKind, AgentOption, AgentValidationIssue,} from './types';
import {computed, onMounted, ref} from 'vue';
import {useRoute, useRouter} from 'vue-router';
import {ElMessage, ElMessageBox} from 'element-plus';
import {tryit} from 'radash';
import {api} from '#/api/request';
import {
canAiResourceOffline,
canAiResourcePublish,
canAiResourceRepublish,
isAiResourceApprovalPending,
} from '#/views/ai/shared/publish-status';
import {
getAgentDetail,
getAgentModels,
getPublishedKnowledgeList,
saveAgent,
submitAgentOfflineApproval,
submitAgentPublishApproval,
updateAgent,
updateAgentKnowledgeBindings,
updateAgentToolBindings,
} from './api';
import AgentStudioCanvas from './components/agent-studio/AgentStudioCanvas.vue';
import AgentCommandBar from './components/AgentCommandBar.vue';
import AgentInspectorPanel from './components/AgentInspectorPanel.vue';
import {useAgentDesignerState} from './composables/useAgentDesignerState';
const route = useRoute();
const router = useRouter();
const {
state,
addKnowledgeNode,
addToolNode,
buildKnowledgePayload,
buildPayloadAgent,
buildToolPayload,
markDirty,
openTryout,
removeSelectedCapability,
reset,
selectBase,
selectNode,
validate,
} = useAgentDesignerState();
const pageLoading = ref(false);
const saveLoading = ref(false);
const publishLoading = ref(false);
const issues = ref<AgentValidationIssue[]>([]);
const categories = ref<AgentOption[]>([]);
const models = ref<AgentOption[]>([]);
const knowledges = ref<AgentOption[]>([]);
const workflows = ref<AgentOption[]>([]);
const pluginTools = ref<AgentOption[]>([]);
const isNew = computed(() => String(route.params.id || '') === 'new');
const publishText = computed(() => {
if (
canAiResourceOffline(
state.agent.displayPublishStatus,
state.agent.publishStatus,
)
) {
return '下线';
}
if (
canAiResourceRepublish(
state.agent.displayPublishStatus,
state.agent.publishStatus,
)
) {
return '重新发布';
}
return '发布';
});
const publishDisabled = computed(() => {
if (!state.agent.id) return true;
if (
isAiResourceApprovalPending(
state.agent.displayPublishStatus,
state.agent.publishStatus,
)
) {
return true;
}
return !(
canAiResourcePublish(
state.agent.displayPublishStatus,
state.agent.publishStatus,
) ||
canAiResourceRepublish(
state.agent.displayPublishStatus,
state.agent.publishStatus,
) ||
canAiResourceOffline(
state.agent.displayPublishStatus,
state.agent.publishStatus,
)
);
});
onMounted(async () => {
pageLoading.value = true;
try {
await Promise.all([loadOptions(), loadAgent()]);
} finally {
pageLoading.value = false;
}
});
async function loadAgent() {
if (isNew.value) {
reset();
return;
}
const [, res] = await tryit(getAgentDetail)(String(route.params.id));
if (res?.errorCode === 0) {
reset(res.data);
}
}
async function loadOptions() {
const [categoryRes, modelRes, knowledgeRes, workflowRes, pluginRes] =
await Promise.all([
api.get('/api/v1/agentCategory/visibleList', {
params: { sortKey: 'sortNo', sortType: 'asc' },
}),
getAgentModels(),
getPublishedKnowledgeList(),
api.get('/api/v1/workflow/page', {
params: { pageNumber: 1, pageSize: 200 },
}),
api.get('/api/v1/plugin/pageByCategory', {
params: { pageNumber: 1, pageSize: 200, category: 0 },
}),
]);
categories.value = (categoryRes.data || []).map((item: any) => ({
label: item.categoryName || item.name,
value: String(item.id),
raw: item,
}));
models.value = (modelRes.data || []).map((item: any) => ({
label: item.title || item.name,
value: String(item.id),
raw: item,
}));
knowledges.value = (knowledgeRes.data || []).map((item: any) => ({
label: item.title || item.name,
value: String(item.id),
raw: item,
}));
workflows.value = (
(workflowRes.data?.records || workflowRes.data || []) as any[]
).map((item) => ({
label: item.title || item.name,
value: String(item.id),
raw: item,
}));
pluginTools.value = flattenPluginTools(
pluginRes.data?.records || pluginRes.data || [],
);
}
function flattenPluginTools(list: any[]): AgentOption[] {
const result: AgentOption[] = [];
list.forEach((plugin) => {
const tools = Array.isArray(plugin.tools) ? plugin.tools : [];
tools.forEach((tool: any) => {
result.push({
label: tool.name || tool.title,
value: String(tool.id),
raw: { ...tool, pluginName: plugin.name || plugin.title },
});
});
});
return result;
}
async function handleAdd(kind: AgentCapabilityKind) {
if (kind === 'knowledge') {
addKnowledgeNode();
return;
}
addToolNode(kind);
}
function handleSelectNode(nodeId: string) {
selectNode(nodeId);
}
function handleSelectIssue(nodeId: string) {
selectNode(nodeId);
}
function runValidation() {
issues.value = validate();
if (issues.value.length > 0) {
selectNode(issues.value[0]!.nodeId);
ElMessage.warning('请先完成必要配置');
return false;
}
return true;
}
async function handleSave(showMessage = true) {
if (!runValidation()) return false;
saveLoading.value = true;
try {
const agentPayload = buildPayloadAgent();
const agentRes = state.agent.id
? await updateAgent(agentPayload)
: await saveAgent(agentPayload);
if (agentRes.errorCode !== 0 || !agentRes.data?.id) {
return false;
}
const id = agentRes.data.id;
const toolBindingRes = await updateAgentToolBindings(
id,
buildToolPayload(id),
);
if (toolBindingRes.errorCode !== 0) {
return false;
}
const knowledgeBindingRes = await updateAgentKnowledgeBindings(
id,
buildKnowledgePayload(id),
);
if (knowledgeBindingRes.errorCode !== 0) {
return false;
}
state.agent = {
...state.agent,
...agentRes.data,
id,
};
state.dirty = false;
if (isNew.value) {
await router.replace(`/ai/agents/designer/${id}`);
}
if (showMessage) {
ElMessage.success('已保存');
}
return true;
} finally {
saveLoading.value = false;
}
}
async function handlePublish() {
if (!state.agent.id) return;
const saved = await handleSave(false);
if (!saved) return;
const offline = canAiResourceOffline(
state.agent.displayPublishStatus,
state.agent.publishStatus,
);
try {
await ElMessageBox.confirm(
offline ? '确认提交下线审批?' : '确认提交发布审批?',
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: offline ? 'warning' : 'info',
},
);
} catch {
return;
}
publishLoading.value = true;
try {
const res = offline
? await submitAgentOfflineApproval(String(state.agent.id))
: await submitAgentPublishApproval(String(state.agent.id));
if (res.errorCode === 0) {
ElMessage.success(res.message || '已提交');
await loadAgent();
}
} finally {
publishLoading.value = false;
}
}
function handleTryout() {
if (!runValidation()) return;
openTryout();
}
function handleCloseTryout() {
selectBase();
}
</script>
<template>
<div v-loading="pageLoading" class="agent-designer">
<AgentStudioCanvas
:state="state"
:knowledge-options="knowledges"
:selected-node-id="state.selectedNodeId"
@select="handleSelectNode"
/>
<AgentInspectorPanel
:state="state"
:models="models"
:categories="categories"
:knowledges="knowledges"
:workflows="workflows"
:plugin-tools="pluginTools"
:issues="issues"
@change="markDirty"
@remove-capability="removeSelectedCapability"
@close-tryout="handleCloseTryout"
@select-issue="handleSelectIssue"
/>
<AgentCommandBar
:save-loading="saveLoading"
:publish-loading="publishLoading"
:publish-disabled="publishDisabled"
:publish-text="publishText"
@add="handleAdd"
@save="handleSave()"
@publish="handlePublish"
@tryout="handleTryout"
/>
</div>
</template>
<style scoped>
.agent-designer {
position: relative;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
background:
radial-gradient(
circle at 50% 42%,
var(--el-color-primary-light-9),
transparent 32%
),
var(--el-fill-color-extra-light);
}
</style>