357 lines
8.5 KiB
Vue
357 lines
8.5 KiB
Vue
<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>
|