Files
aiagent/frontend/src/views/Agents.vue

502 lines
13 KiB
Vue
Raw Normal View History

2026-01-19 00:09:36 +08:00
<template>
<MainLayout>
<div class="agents-page">
<el-card>
<template #header>
<div class="card-header">
<h2>Agent管理</h2>
<el-button type="primary" @click="handleCreate">
<el-icon><Plus /></el-icon>
创建Agent
</el-button>
</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="描述" show-overflow-tooltip />
<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>
2026-01-19 17:52:29 +08:00
<el-table-column label="操作" width="350" fixed="right">
2026-01-19 00:09:36 +08:00
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">
<el-icon><Edit /></el-icon>
编辑
</el-button>
<el-button link type="primary" @click="handleDesign(row)">
<el-icon><Setting /></el-icon>
设计
</el-button>
2026-01-19 17:52:29 +08:00
<el-button link type="info" @click="handleDuplicate(row)">
<el-icon><CopyDocument /></el-icon>
复制
</el-button>
2026-01-19 00:09:36 +08:00
<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>
</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 {
Plus,
Search,
Refresh,
Edit,
Delete,
Setting,
VideoPlay,
2026-01-19 17:52:29 +08:00
VideoPause,
CopyDocument
2026-01-19 00:09:36 +08:00
} from '@element-plus/icons-vue'
import { useAgentStore } from '@/stores/agent'
import type { Agent } from '@/stores/agent'
const router = useRouter()
const agentStore = useAgentStore()
// 搜索和筛选
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 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
}
// 设计
const handleDesign = (agent: Agent) => {
router.push({
name: 'AgentDesigner',
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 || '停止失败')
}
}
}
2026-01-19 17:52:29 +08:00
// 复制
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 || '复制失败')
}
}
}
2026-01-19 00:09:36 +08:00
// 删除
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 || '删除失败')
}
}
}
// 提交表单
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;
}
</style>