Files
aiagent/frontend/src/views/Agents.vue
renjianbo de415ca310 feat: add Prompt template library, agent_call inter-agent tool, and RAG memory
- New PromptTemplatePicker component for browsing 13 preset prompt templates
- AgentConfig.vue: "Load from library" button for system prompt
- Agents.vue: "Create from Prompt template" entry with agent node + RAG memory
- seed_prompt_templates.py: 13 preset templates (客服/研发/教育/内容/分析/创意/健康医疗)
- agent_call tool: agents can delegate tasks to other agents (19th builtin tool)
- Created 全能助手 (general orchestrator) and 家庭医生助手 agents
- Switch template-created agents from type:llm to type:agent for full ReAct + RAG

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 21:57:30 +08:00

1159 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<MainLayout>
<div class="agents-page">
<el-card>
<template #header>
<div class="card-header">
<h2>Agent管理</h2>
<div>
<el-button @click="handleImport">
<el-icon><Upload /></el-icon>
导入Agent
</el-button>
<el-button @click="openTemplateDialog">
从场景模板创建
</el-button>
<el-button @click="openPromptCreateDialog">
<el-icon><Collection /></el-icon>
Prompt 模板创建
</el-button>
<el-button type="primary" @click="handleCreate" style="margin-left: 10px">
<el-icon><Plus /></el-icon>
创建Agent
</el-button>
</div>
</div>
</template>
<!-- 搜索和筛选 -->
<div class="filters">
<el-input
v-model="searchText"
placeholder="搜索Agent名称或描述"
style="width: 300px"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-select
v-model="statusFilter"
placeholder="状态筛选"
style="width: 150px; margin-left: 10px"
clearable
@change="handleSearch"
>
<el-option label="草稿" value="draft" />
<el-option label="已发布" value="published" />
<el-option label="运行中" value="running" />
<el-option label="已停止" value="stopped" />
</el-select>
<el-button type="primary" @click="handleSearch" style="margin-left: 10px">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleRefresh">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
<!-- Agent列表 -->
<el-table
v-loading="agentStore.loading"
:data="agentStore.agents"
style="width: 100%; margin-top: 20px"
stripe
>
<el-table-column prop="name" label="名称" width="200" />
<el-table-column prop="description" label="描述" min-width="160" show-overflow-tooltip />
<el-table-column label="技能 (tools)" min-width="220">
<template #default="{ row }">
<div v-if="skillTagsForRow(row).length" class="skill-tags">
<el-tag
v-for="t in skillTagsForRow(row)"
:key="t"
size="small"
class="skill-tag"
effect="plain"
>
{{ skillLabel(t) }}
</el-tag>
</div>
<span v-else class="skill-empty"></span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="version" label="版本" width="80" />
<el-table-column prop="created_at" label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="540" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">
<el-icon><Edit /></el-icon>
编辑
</el-button>
<el-button link type="primary" @click="handleSkillConfig(row)">
<el-icon><Tools /></el-icon>
能力配置
</el-button>
<el-button
v-if="row.status === 'published' || row.status === 'running'"
link
type="primary"
@click="handleUse(row)"
>
<el-icon><ChatDotRound /></el-icon>
使用
</el-button>
<el-button link type="primary" @click="handleDesign(row)">
<el-icon><Setting /></el-icon>
设计
</el-button>
<el-button link type="primary" @click="handleConfig(row)">
<el-icon><Operation /></el-icon>
配置
</el-button>
<el-button link type="info" @click="handleDuplicate(row)">
<el-icon><CopyDocument /></el-icon>
复制
</el-button>
<el-button link type="success" @click="handleExport(row)">
<el-icon><Download /></el-icon>
导出
</el-button>
<el-button
v-if="row.status === 'draft' || row.status === 'stopped'"
link
type="success"
@click="handleDeploy(row)"
>
<el-icon><VideoPlay /></el-icon>
部署
</el-button>
<el-button
v-if="row.status === 'published' || row.status === 'running'"
link
type="warning"
@click="handleStop(row)"
>
<el-icon><VideoPause /></el-icon>
停止
</el-button>
<el-button link type="danger" @click="handleDelete(row)">
<el-icon><Delete /></el-icon>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handlePageChange"
@current-change="handlePageChange"
/>
</div>
</el-card>
<!-- 创建/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
@close="handleDialogClose"
>
<el-form :model="form" label-width="100px" :rules="rules" ref="formRef">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入Agent名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入Agent描述"
/>
</el-form-item>
<el-form-item label="工作流配置" prop="workflow_config">
<el-alert
type="info"
:closable="false"
show-icon
>
<template #title>
<div style="font-size: 12px;">
工作流配置将在设计器中编辑创建后可以点击"设计"按钮配置工作流
</div>
</template>
</el-alert>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
确定
</el-button>
</template>
</el-dialog>
<!-- 导入Agent对话框 -->
<el-dialog
v-model="importDialogVisible"
title="导入Agent"
width="600px"
@close="handleImportDialogClose"
>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
accept=".json"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将JSON文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传JSON格式的Agent配置文件
</div>
</template>
</el-upload>
<div v-if="importFileContent" style="margin-top: 20px;">
<el-alert
type="info"
:closable="false"
show-icon
>
<template #title>
<div>
<div><strong>Agent名称:</strong> {{ importFileContent.name || '未命名' }}</div>
<div v-if="importFileContent.description" style="margin-top: 5px;">
<strong>描述:</strong> {{ importFileContent.description }}
</div>
<div style="margin-top: 5px;">
<strong>节点数:</strong> {{ importFileContent.workflow_config?.nodes?.length || 0 }}
<span style="margin-left: 20px;">
<strong>连接数:</strong> {{ importFileContent.workflow_config?.edges?.length || 0 }}
</span>
</div>
</div>
</template>
</el-alert>
</div>
<template #footer>
<el-button @click="importDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirmImport" :loading="importing" :disabled="!importFileContent">
确认导入
</el-button>
</template>
</el-dialog>
<!-- 从场景模板创建 -->
<el-dialog
v-model="templateDialogVisible"
title="从场景模板创建 Agent"
width="560px"
destroy-on-close
@close="resetTemplateForm"
>
<el-form label-width="150px">
<el-form-item label="场景模板" required>
<el-select
v-model="tplForm.template_id"
placeholder="选择模板"
filterable
style="width: 100%"
>
<el-option
v-for="t in sceneTemplates"
:key="t.id"
:label="`${t.title} (${t.id})`"
:value="t.id"
/>
</el-select>
</el-form-item>
<el-form-item label="名称" required>
<el-input v-model="tplForm.name" placeholder="新 Agent 名称" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="tplForm.description" type="textarea" :rows="2" placeholder="可选" />
</el-form-item>
<el-divider content-position="left">可选执行预算留空则用平台默认</el-divider>
<el-form-item label="max_steps">
<el-input-number v-model="tplForm.max_steps" :min="1" :max="1000000" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="max_llm_invocations">
<el-input-number v-model="tplForm.max_llm_invocations" :min="1" :max="100000" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="max_tool_calls">
<el-input-number v-model="tplForm.max_tool_calls" :min="1" :max="100000" controls-position="right" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="templateDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="templateSubmitting" @click="submitTemplateCreate">
创建
</el-button>
</template>
</el-dialog>
<!-- Prompt 模板创建 -->
<el-dialog
v-model="promptCreateDialogVisible"
title="从 Prompt 模板创建 Agent"
width="700px"
destroy-on-close
@close="resetPromptCreateForm"
>
<PromptTemplatePicker
ref="promptCreatePickerRef"
@use="onPromptCreateUse"
/>
<template #footer>
<span class="dialog-tip">请先在上方选择 Prompt 模板然后填写 Agent 信息</span>
</template>
</el-dialog>
<!-- 填写 Agent 信息Prompt 模板已有选中的 -->
<el-dialog
v-model="promptCreateInfoVisible"
title="完善 Agent 信息"
width="500px"
destroy-on-close
>
<el-form label-width="100px">
<el-form-item label="Prompt 模板">
<el-tag>{{ promptCreateSelected?.name }}</el-tag>
</el-form-item>
<el-form-item label="名称" required>
<el-input v-model="promptCreateForm.name" placeholder="新 Agent 名称" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="promptCreateForm.description" type="textarea" :rows="2" placeholder="可选" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="promptCreateInfoVisible = false">取消</el-button>
<el-button type="primary" :loading="promptCreateSubmitting" @click="submitPromptCreate">
创建 Agent
</el-button>
</template>
</el-dialog>
<!-- 能力 / 技能配置 -->
<el-dialog
v-model="skillDialogVisible"
title="能力配置LLM 工具)"
width="560px"
destroy-on-close
@close="handleSkillDialogClose"
>
<div v-loading="skillLoading">
<el-alert type="info" :closable="false" show-icon class="skill-alert">
<template #title>
<span style="font-size: 13px;">
勾选内置工具并保存后将写入工作流中主 LLM 节点优先
<code>llm-unified</code>
<code>tools</code> / <code>selected_tools</code>并同步
<code>enable_tools</code>多节点场景请以设计器为准
</span>
</template>
</el-alert>
<div v-if="skillAgentName" class="skill-agent-name">Agent{{ skillAgentName }}</div>
<el-form label-width="100px" style="margin-top: 12px">
<el-form-item label="">
<el-checkbox-group v-model="skillBuiltinSelected" class="skill-checkbox-group">
<el-checkbox
v-for="opt in skillBuiltinOptions"
:key="opt.name"
:label="opt.name"
border
>
{{ opt.label }}
<span class="skill-name">({{ opt.name }})</span>
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item v-if="skillExtraNames.length" label="其它工具名">
<div class="skill-extra-wrap">
<el-tag
v-for="ex in skillExtraNames"
:key="ex"
type="warning"
closable
class="skill-extra-tag"
@close="removeExtraSkill(ex)"
>
{{ ex }}
</el-tag>
<span class="skill-extra-hint">来自当前工作流可关闭以移除</span>
</div>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="skillDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="skillSaving" @click="handleSkillSave">
保存
</el-button>
</template>
</el-dialog>
</div>
</MainLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import MainLayout from '@/components/MainLayout.vue'
import PromptTemplatePicker from '@/components/PromptTemplatePicker.vue'
import {
Plus,
Search,
Refresh,
Edit,
Delete,
Setting,
VideoPlay,
VideoPause,
CopyDocument,
Upload,
Download,
UploadFilled,
ChatDotRound,
Tools,
Operation,
Collection
} from '@element-plus/icons-vue'
import { useAgentStore } from '@/stores/agent'
import type { Agent } from '@/stores/agent'
import {
BUILTIN_SKILL_OPTIONS,
BUILTIN_SKILL_LABELS,
extractSkillToolNames,
patchWorkflowSkillTools,
findPrimaryLlmNodeForTools
} from '@/utils/agentSkills'
const router = useRouter()
const agentStore = useAgentStore()
const templateDialogVisible = ref(false)
const templateSubmitting = ref(false)
const sceneTemplates = ref<Array<{ id: string; title: string; description: string }>>([])
const tplForm = ref({
template_id: '',
name: '',
description: '',
max_steps: undefined as number | undefined,
max_llm_invocations: undefined as number | undefined,
max_tool_calls: undefined as number | undefined
})
function resetTemplateForm() {
tplForm.value = {
template_id: '',
name: '',
description: '',
max_steps: undefined,
max_llm_invocations: undefined,
max_tool_calls: undefined
}
}
async function openTemplateDialog() {
resetTemplateForm()
templateDialogVisible.value = true
try {
sceneTemplates.value = await agentStore.fetchSceneTemplates()
if (sceneTemplates.value.length && !tplForm.value.template_id) {
tplForm.value.template_id = sceneTemplates.value[0].id
}
} catch (e: any) {
ElMessage.error(e.response?.data?.detail || '加载模板列表失败')
}
}
async function submitTemplateCreate() {
const name = tplForm.value.name.trim()
if (!tplForm.value.template_id) {
ElMessage.warning('请选择场景模板')
return
}
if (!name) {
ElMessage.warning('请输入名称')
return
}
const bc: Record<string, number> = {}
if (tplForm.value.max_steps != null && tplForm.value.max_steps > 0) {
bc.max_steps = tplForm.value.max_steps
}
if (tplForm.value.max_llm_invocations != null && tplForm.value.max_llm_invocations > 0) {
bc.max_llm_invocations = tplForm.value.max_llm_invocations
}
if (tplForm.value.max_tool_calls != null && tplForm.value.max_tool_calls > 0) {
bc.max_tool_calls = tplForm.value.max_tool_calls
}
templateSubmitting.value = true
try {
await agentStore.createFromSceneTemplate({
template_id: tplForm.value.template_id,
name,
description: tplForm.value.description?.trim() || undefined,
parameters: {},
budget_config: Object.keys(bc).length ? bc : undefined
})
ElMessage.success('已从模板创建 Agent')
templateDialogVisible.value = false
await loadAgents()
} catch (e: any) {
ElMessage.error(e.response?.data?.detail || '创建失败')
} finally {
templateSubmitting.value = false
}
}
// ── 从 Prompt 模板创建 ──
const promptCreateDialogVisible = ref(false)
const promptCreateInfoVisible = ref(false)
const promptCreateSubmitting = ref(false)
const promptCreateSelected = ref<any>(null)
const promptCreatePickerRef = ref()
const promptCreateForm = ref({
name: '',
description: '',
})
function openPromptCreateDialog() {
promptCreateDialogVisible.value = true
}
function resetPromptCreateForm() {
promptCreateSelected.value = null
promptCreateForm.value = { name: '', description: '' }
}
function onPromptCreateUse(tpl: any) {
promptCreateSelected.value = tpl
promptCreateForm.value.name = tpl.name || ''
promptCreateForm.value.description = tpl.description || ''
promptCreateDialogVisible.value = false
promptCreateInfoVisible.value = true
}
async function submitPromptCreate() {
const name = promptCreateForm.value.name.trim()
if (!name) {
ElMessage.warning('请输入名称')
return
}
if (!promptCreateSelected.value) {
ElMessage.warning('请先选择 Prompt 模板')
return
}
const tpl = promptCreateSelected.value
const workflowConfig = {
nodes: [
{ id: 'start-1', type: 'start', position: { x: 80, y: 120 }, data: {} },
{
id: 'agent-1',
type: 'agent',
position: { x: 320, y: 120 },
data: {
label: name,
system_prompt: tpl.prompt,
model: tpl.model || 'deepseek-v4-flash',
provider: tpl.provider || 'deepseek',
temperature: parseFloat(tpl.temperature || '0.7'),
max_iterations: 10,
tools: [],
memory: true,
},
},
{ id: 'end-1', type: 'end', position: { x: 560, y: 120 }, data: {} },
],
edges: [
{ id: 'e_start_agent', source: 'start-1', target: 'agent-1', sourceHandle: 'right', targetHandle: 'left' },
{ id: 'e_agent_end', source: 'agent-1', target: 'end-1', sourceHandle: 'right', targetHandle: 'left' },
],
}
promptCreateSubmitting.value = true
try {
await agentStore.createAgent({
name,
description: promptCreateForm.value.description?.trim() || undefined,
workflow_config: workflowConfig,
})
ElMessage.success(`已从 Prompt 模板创建 Agent「${name}`)
promptCreateInfoVisible.value = false
await loadAgents()
} catch (e: any) {
ElMessage.error(e.response?.data?.detail || '创建失败')
} finally {
promptCreateSubmitting.value = false
}
}
// 搜索和筛选
const searchText = ref('')
const statusFilter = ref('')
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
// 对话框
const dialogVisible = ref(false)
const dialogTitle = ref('创建Agent')
const formRef = ref()
const submitting = ref(false)
const form = ref({
name: '',
description: '',
workflow_config: {
nodes: [
{
id: 'start-1',
type: 'start',
position: { x: 0, y: 0 },
data: { label: '开始' }
},
{
id: 'end-1',
type: 'end',
position: { x: 200, y: 0 },
data: { label: '结束' }
}
],
edges: []
}
})
const currentAgentId = ref<string | null>(null)
// 导入相关
const importDialogVisible = ref(false)
const fileList = ref<any[]>([])
const importFileContent = ref<any>(null)
const importing = ref(false)
const uploadRef = ref()
// 能力 / 技能配置
const skillDialogVisible = ref(false)
const skillLoading = ref(false)
const skillSaving = ref(false)
const skillAgentId = ref<string | null>(null)
const skillAgentName = ref('')
const skillBuiltinSelected = ref<string[]>([])
const skillExtraNames = ref<string[]>([])
const skillBuiltinOptions = BUILTIN_SKILL_OPTIONS
const BUILTIN_NAME_SET = new Set(BUILTIN_SKILL_OPTIONS.map((o) => o.name))
function skillLabel(name: string): string {
return BUILTIN_SKILL_LABELS[name] || name
}
function skillTagsForRow(agent: Agent): string[] {
return extractSkillToolNames(agent.workflow_config)
}
function handleSkillDialogClose() {
skillAgentId.value = null
skillAgentName.value = ''
skillBuiltinSelected.value = []
skillExtraNames.value = []
}
async function handleSkillConfig(agent: Agent) {
skillAgentId.value = agent.id
skillAgentName.value = agent.name
skillDialogVisible.value = true
skillLoading.value = true
try {
const full = await agentStore.fetchAgent(agent.id)
const all = extractSkillToolNames(full.workflow_config)
skillBuiltinSelected.value = all.filter((n) => BUILTIN_NAME_SET.has(n))
skillExtraNames.value = all.filter((n) => !BUILTIN_NAME_SET.has(n))
const primary = findPrimaryLlmNodeForTools(full.workflow_config?.nodes)
if (!primary) {
ElMessage.warning('当前工作流中未找到 LLM 节点,请先在「设计」中添加 LLM 节点后再配置能力。')
}
} catch (e: any) {
ElMessage.error(e.response?.data?.detail || '加载 Agent 失败')
skillDialogVisible.value = false
} finally {
skillLoading.value = false
}
}
function removeExtraSkill(name: string) {
skillExtraNames.value = skillExtraNames.value.filter((x) => x !== name)
}
async function handleSkillSave() {
if (!skillAgentId.value) return
skillSaving.value = true
try {
const full = await agentStore.fetchAgent(skillAgentId.value)
const merged = [
...new Set([...skillBuiltinSelected.value, ...skillExtraNames.value].filter(Boolean))
].sort()
const wf = patchWorkflowSkillTools(full.workflow_config, merged)
await agentStore.updateAgent(skillAgentId.value, {
workflow_config: wf
})
ElMessage.success('能力配置已保存')
skillDialogVisible.value = false
await loadAgents()
} catch (e: any) {
ElMessage.error(e.response?.data?.detail || '保存失败')
} finally {
skillSaving.value = false
}
}
// 表单验证规则
const rules = {
name: [
{ required: true, message: '请输入Agent名称', trigger: 'blur' },
{ min: 1, max: 100, message: '名称长度在1到100个字符', trigger: 'blur' }
]
}
// 获取状态类型
const getStatusType = (status: string) => {
const typeMap: Record<string, string> = {
draft: 'info',
published: 'success',
running: 'success',
stopped: 'warning'
}
return typeMap[status] || 'info'
}
// 获取状态文本
const getStatusText = (status: string) => {
const textMap: Record<string, string> = {
draft: '草稿',
published: '已发布',
running: '运行中',
stopped: '已停止'
}
return textMap[status] || status
}
// 格式化日期
const formatDate = (dateStr: string) => {
if (!dateStr) return ''
const date = new Date(dateStr)
return date.toLocaleString('zh-CN')
}
// 搜索
const handleSearch = async () => {
currentPage.value = 1
await loadAgents()
}
// 刷新
const handleRefresh = async () => {
searchText.value = ''
statusFilter.value = ''
currentPage.value = 1
await loadAgents()
}
// 加载Agent列表
const loadAgents = async () => {
try {
const agents = await agentStore.fetchAgents({
search: searchText.value || undefined,
status: statusFilter.value || undefined,
skip: (currentPage.value - 1) * pageSize.value,
limit: pageSize.value
})
total.value = agents.length
} catch (error: any) {
ElMessage.error(error.response?.data?.detail || '加载Agent列表失败')
}
}
// 创建
const handleCreate = () => {
dialogTitle.value = '创建Agent'
currentAgentId.value = null
form.value = {
name: '',
description: '',
workflow_config: {
nodes: [
{
id: 'start-1',
type: 'start',
position: { x: 0, y: 0 },
data: { label: '开始' }
},
{
id: 'end-1',
type: 'end',
position: { x: 200, y: 0 },
data: { label: '结束' }
}
],
edges: []
}
}
dialogVisible.value = true
}
// 编辑
const handleEdit = (agent: Agent) => {
dialogTitle.value = '编辑Agent'
currentAgentId.value = agent.id
form.value = {
name: agent.name,
description: agent.description || '',
workflow_config: agent.workflow_config
}
dialogVisible.value = true
}
// 使用Agent跳转到设计器那里有聊天界面
const handleUse = (agent: Agent) => {
router.push({
name: 'AgentDesigner',
params: { id: agent.id }
})
}
// 设计
const handleDesign = (agent: Agent) => {
router.push({
name: 'AgentDesigner',
params: { id: agent.id }
})
}
// 配置页
const handleConfig = (agent: Agent) => {
router.push({
name: 'agent-config',
params: { id: agent.id }
})
}
// 部署
const handleDeploy = async (agent: Agent) => {
try {
await ElMessageBox.confirm(
`确定要部署Agent "${agent.name}" 吗?`,
'确认部署',
{
type: 'warning'
}
)
await agentStore.deployAgent(agent.id)
ElMessage.success('部署成功')
await loadAgents()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.detail || '部署失败')
}
}
}
// 停止
const handleStop = async (agent: Agent) => {
try {
await ElMessageBox.confirm(
`确定要停止Agent "${agent.name}" 吗?`,
'确认停止',
{
type: 'warning'
}
)
await agentStore.stopAgent(agent.id)
ElMessage.success('停止成功')
await loadAgents()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.detail || '停止失败')
}
}
}
// 复制
const handleDuplicate = async (agent: Agent) => {
try {
const { value } = await ElMessageBox.prompt(
`请输入新Agent的名称留空将自动生成`,
'复制Agent',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: `留空将使用: ${agent.name} (副本)`,
inputValidator: (val: string) => {
if (val && val.length > 100) {
return '名称长度不能超过100个字符'
}
return true
}
}
)
await agentStore.duplicateAgent(agent.id, value || undefined)
ElMessage.success('复制成功')
await loadAgents()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.detail || '复制失败')
}
}
}
// 删除
const handleDelete = async (agent: Agent) => {
try {
await ElMessageBox.confirm(
`确定要删除Agent "${agent.name}" 吗?此操作不可恢复。`,
'确认删除',
{
type: 'error'
}
)
await agentStore.deleteAgent(agent.id)
ElMessage.success('删除成功')
await loadAgents()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.detail || '删除失败')
}
}
}
// 导出Agent
const handleExport = async (agent: Agent) => {
try {
await agentStore.exportAgent(agent.id)
ElMessage.success('Agent导出成功')
} catch (error: any) {
console.error('导出失败详情:', error)
const errorMessage = error.message || error.response?.data?.detail || '导出失败,请查看控制台获取详细信息'
ElMessage.error(errorMessage)
}
}
// 显示导入对话框
const handleImport = () => {
importDialogVisible.value = true
fileList.value = []
importFileContent.value = null
}
// 文件选择
const handleFileChange = (file: any) => {
const reader = new FileReader()
reader.onload = (e) => {
try {
const content = JSON.parse(e.target?.result as string)
importFileContent.value = content
} catch (error) {
ElMessage.error('文件格式错误请上传有效的JSON文件')
fileList.value = []
importFileContent.value = null
}
}
reader.readAsText(file.raw)
}
// 关闭导入对话框
const handleImportDialogClose = () => {
fileList.value = []
importFileContent.value = null
if (uploadRef.value) {
uploadRef.value.clearFiles()
}
}
// 确认导入
const handleConfirmImport = async () => {
if (!importFileContent.value) {
ElMessage.warning('请先选择要导入的文件')
return
}
importing.value = true
try {
const agent = await agentStore.importAgent(importFileContent.value)
importDialogVisible.value = false
ElMessage.success('Agent导入成功')
await loadAgents()
// 跳转到Agent设计器
router.push({
name: 'AgentDesigner',
params: { id: agent.id }
})
} catch (error: any) {
ElMessage.error(error.response?.data?.detail || '导入Agent失败')
} finally {
importing.value = false
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return
submitting.value = true
try {
if (currentAgentId.value) {
// 更新
await agentStore.updateAgent(currentAgentId.value, form.value)
ElMessage.success('更新成功')
} else {
// 创建
await agentStore.createAgent(form.value)
ElMessage.success('创建成功')
}
dialogVisible.value = false
await loadAgents()
} catch (error: any) {
ElMessage.error(error.response?.data?.detail || '操作失败')
} finally {
submitting.value = false
}
})
}
// 对话框关闭
const handleDialogClose = () => {
formRef.value?.resetFields()
currentAgentId.value = null
}
// 分页变化
const handlePageChange = () => {
loadAgents()
}
// 初始化
onMounted(() => {
loadAgents()
})
</script>
<style scoped>
.agents-page {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header h2 {
margin: 0;
}
.filters {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.skill-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
align-items: center;
max-width: 100%;
}
.skill-tag {
margin: 0;
}
.skill-empty {
color: var(--el-text-color-placeholder);
font-size: 13px;
}
.skill-alert {
margin-bottom: 8px;
}
.skill-agent-name {
font-weight: 500;
margin-bottom: 4px;
}
.skill-checkbox-group {
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-start;
}
.skill-name {
color: var(--el-text-color-secondary);
font-size: 12px;
margin-left: 4px;
}
.skill-extra-wrap {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.skill-extra-tag {
margin: 0;
}
.skill-extra-hint {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.dialog-tip {
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>