feat: 全新智能体功能
- 基于先进智能体框架,增加智能体编排功能 - 增加智能体聊天,并对接持久化
This commit is contained in:
356
easyflow-ui-admin/app/src/views/ai/agents/AgentDesigner.vue
Normal file
356
easyflow-ui-admin/app/src/views/ai/agents/AgentDesigner.vue
Normal 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>
|
||||
Reference in New Issue
Block a user