- 将 AgentDefinitionCompiler 升级为 AgentRuntimeCompiler - 接入 Workflow 和 Plugin 的同步/异步工具编译与 Redis 任务态 - 增加异步执行配置开关、聊天时间线聚合和后端测试
244 lines
5.8 KiB
Vue
244 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
/* eslint-disable vue/no-mutating-props */
|
|
import type { AgentOption, AgentToolBinding } from '../types';
|
|
|
|
import { computed } from 'vue';
|
|
|
|
import {
|
|
ElButton,
|
|
ElEmpty,
|
|
ElForm,
|
|
ElFormItem,
|
|
ElInput,
|
|
ElOption,
|
|
ElSelect,
|
|
ElSwitch,
|
|
} from 'element-plus';
|
|
|
|
const props = defineProps<{
|
|
binding: AgentToolBinding;
|
|
kind: 'mcp' | 'plugin' | 'workflow';
|
|
options: AgentOption[];
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
change: [];
|
|
remove: [];
|
|
}>();
|
|
|
|
const SAFE_TOOL_NAME_PATTERN = /^[\w-]+$/;
|
|
|
|
function isSafeToolName(name?: string) {
|
|
return SAFE_TOOL_NAME_PATTERN.test(String(name || ''));
|
|
}
|
|
|
|
function resolveToolName(option?: AgentOption) {
|
|
const raw = option?.raw || {};
|
|
if (isSafeToolName(raw.englishName)) {
|
|
return String(raw.englishName);
|
|
}
|
|
if (isSafeToolName(raw.name)) {
|
|
return String(raw.name);
|
|
}
|
|
const prefix = props.kind === 'workflow' ? 'workflow' : 'plugin';
|
|
return `${prefix}_${option?.value || Date.now()}`;
|
|
}
|
|
|
|
function shouldSyncToolName() {
|
|
const name = String(props.binding.toolName || '');
|
|
if (!name) return true;
|
|
const prefix = props.kind === 'workflow' ? 'workflow_' : 'plugin_';
|
|
return name.startsWith(prefix);
|
|
}
|
|
|
|
const targetValue = computed({
|
|
get() {
|
|
return props.binding.targetId ? String(props.binding.targetId) : '';
|
|
},
|
|
set(value: string) {
|
|
props.binding.targetId = value;
|
|
},
|
|
});
|
|
|
|
const resourceLabel = computed(() => {
|
|
if (props.kind === 'workflow') return '工作流';
|
|
if (props.kind === 'mcp') return 'MCP';
|
|
return '插件工具';
|
|
});
|
|
|
|
const selectedMcpTools = computed(() => {
|
|
if (props.kind !== 'mcp') {
|
|
return [];
|
|
}
|
|
const option = props.options.find(
|
|
(item) => String(item.value) === String(props.binding.targetId),
|
|
);
|
|
const tools = option?.raw?.tools || props.binding.resourceSummary?.tools;
|
|
return Array.isArray(tools) ? tools : [];
|
|
});
|
|
|
|
const selectedMcpToolCount = computed(() => selectedMcpTools.value.length);
|
|
|
|
const asyncExecutionEnabled = computed({
|
|
get() {
|
|
return String(props.binding.optionsJson?.executionMode || '').toUpperCase() === 'ASYNC';
|
|
},
|
|
set(value: boolean) {
|
|
props.binding.optionsJson = {
|
|
...(props.binding.optionsJson || {}),
|
|
executionMode: value ? 'ASYNC' : 'SYNC',
|
|
};
|
|
emit('change');
|
|
},
|
|
});
|
|
|
|
function handleTargetChange(value: string) {
|
|
const option = props.options.find(
|
|
(item) => String(item.value) === String(value),
|
|
);
|
|
props.binding.resourceSummary = option?.raw || {};
|
|
if (props.kind === 'mcp') {
|
|
props.binding.targetId = option?.raw?.mcpId
|
|
? String(option.raw.mcpId)
|
|
: value;
|
|
props.binding.toolName = '';
|
|
emit('change');
|
|
return;
|
|
}
|
|
if (shouldSyncToolName()) {
|
|
props.binding.toolName = resolveToolName(option);
|
|
}
|
|
emit('change');
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ElForm label-position="top" class="agent-form">
|
|
<ElFormItem :label="resourceLabel" required>
|
|
<ElSelect
|
|
v-model="targetValue"
|
|
filterable
|
|
placeholder="选择资源"
|
|
@change="handleTargetChange"
|
|
>
|
|
<ElOption
|
|
v-for="item in options"
|
|
:key="item.value"
|
|
:label="item.label"
|
|
:value="item.value"
|
|
/>
|
|
</ElSelect>
|
|
</ElFormItem>
|
|
<div v-if="kind === 'mcp'" class="agent-form__mcp-tools">
|
|
<div class="agent-form__mcp-tools-header">
|
|
<span>工具列表</span>
|
|
<span>{{ selectedMcpToolCount }} 个</span>
|
|
</div>
|
|
<div v-if="selectedMcpTools.length > 0" class="agent-form__mcp-tool-list">
|
|
<div
|
|
v-for="tool in selectedMcpTools"
|
|
:key="tool.name || tool.title"
|
|
class="agent-form__mcp-tool"
|
|
>
|
|
<div class="agent-form__mcp-tool-name">
|
|
{{ tool.name || tool.title || '未命名工具' }}
|
|
</div>
|
|
<div v-if="tool.description" class="agent-form__mcp-tool-desc">
|
|
{{ tool.description }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ElEmpty v-else description="暂无工具" :image-size="64" />
|
|
</div>
|
|
<ElFormItem v-if="kind !== 'mcp'" label="工具名称" required>
|
|
<ElInput
|
|
v-model="binding.toolName"
|
|
placeholder="仅支持英文、数字、下划线或中划线"
|
|
@input="emit('change')"
|
|
/>
|
|
</ElFormItem>
|
|
<ElFormItem label="执行前确认">
|
|
<ElSwitch v-model="binding.hitlEnabled" @change="emit('change')" />
|
|
</ElFormItem>
|
|
<ElFormItem v-if="kind !== 'mcp'" label="异步执行">
|
|
<ElSwitch v-model="asyncExecutionEnabled" />
|
|
</ElFormItem>
|
|
<ElButton
|
|
class="agent-form__danger"
|
|
type="danger"
|
|
plain
|
|
@click="emit('remove')"
|
|
>
|
|
删除节点
|
|
</ElButton>
|
|
</ElForm>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.agent-form {
|
|
padding: 16px;
|
|
}
|
|
|
|
.agent-form :deep(.el-select) {
|
|
width: 100%;
|
|
}
|
|
|
|
.agent-form__mcp-tools {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.agent-form__mcp-tools-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
}
|
|
|
|
.agent-form__mcp-tools-header span:last-child {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
|
|
.agent-form__mcp-tool-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.agent-form__mcp-tool {
|
|
padding: 10px 12px;
|
|
background: var(--el-fill-color-lighter);
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.agent-form__mcp-tool-name {
|
|
overflow: hidden;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
line-height: 20px;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.agent-form__mcp-tool-desc {
|
|
display: -webkit-box;
|
|
margin-top: 4px;
|
|
overflow: hidden;
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
color: var(--el-text-color-secondary);
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
}
|
|
|
|
.agent-form__danger {
|
|
width: 100%;
|
|
margin-top: 8px;
|
|
}
|
|
</style>
|