Files
taiji-pda-v0/app/data-tools/page.tsx
T
zhanggangyong 5ee8c4736f feat: 根据后端接口更新自定义Agent创建功能
- 更新 API Client createCustomAgent 方法,添加 model、agentRole、agentCapabilities 参数
- 更新模板接口解析,支持新格式 frameworkTemplates 和 dataTemplates
- 添加数据存储模板选择器(mysql_agent、postgresql_agent 等)
- 添加动态环境变量配置,根据模板的 env_info.required/optional 动态生成表单
- 添加 A2A 框架专用配置(Agent角色、Agent能力)
- 更新表单验证和配置摘要
2026-01-13 12:51:04 +00:00

1341 lines
57 KiB
TypeScript
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.
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { useLanguage } from "@/hooks/useLanguage"
import { TaijiAPIClient } from "@/lib/api-client"
import { AuthGuard } from "@/components/auth-guard"
import { DashboardLayout } from "@/components/dashboard-layout"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Plus, Search, Trash2, FileJson, Cloud, Cpu, Code2, Boxes, Workflow, Upload, RefreshCw, Square, Play, Bot } from "lucide-react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
export default function DataToolsPage() {
const { t } = useLanguage()
const router = useRouter()
const [tools, setTools] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [stats, setStats] = useState({ totalApis: 0, generatedTools: 0, activePods: 0 })
const [showNewToolDialog, setShowNewToolDialog] = useState(false)
const [showTemplateDialog, setShowTemplateDialog] = useState(false)
const [templateType, setTemplateType] = useState<"json_api" | "cloud_storage" | "database">("json_api")
const [useJsonUpload, setUseJsonUpload] = useState(false)
const [templateName, setTemplateName] = useState("")
const [templateConfig, setTemplateConfig] = useState({
// JSON API 配置
url: "",
apiKey: "",
queryParams: "",
jsonContent: "",
// 云存储/数据库配置
service: "",
dbType: "",
connectionString: "",
})
const [creatingTemplate, setCreatingTemplate] = useState(false)
const [podConfig, setPodConfig] = useState({
agentCount: 1,
cpuCores: 1,
memoryGB: 2,
maxAgents: 5,
model: "gpt-4o-mini",
})
const [selectedFramework, setSelectedFramework] = useState<string>("")
const [selectedServiceGateway, setSelectedServiceGateway] = useState<string>("")
const [selectedDataTemplate, setSelectedDataTemplate] = useState<string>("")
const [dataTemplates, setDataTemplates] = useState<any[]>([])
const [frameworkTemplates, setFrameworkTemplates] = useState<any[]>([])
// 新增:API返回的数据存储模板(含环境变量信息)
const [apiDataTemplates, setApiDataTemplates] = useState<any[]>([])
const [selectedApiDataTemplate, setSelectedApiDataTemplate] = useState<string>("")
const [envConfig, setEnvConfig] = useState<Record<string, string>>({})
// A2A 框架专用字段
const [agentRole, setAgentRole] = useState<string>("")
const [agentCapabilities, setAgentCapabilities] = useState<string>("")
const [availableModels, setAvailableModels] = useState<any[]>([])
const [toolName, setToolName] = useState<string>("")
const [deploying, setDeploying] = useState(false)
// 自定义Agent管理状态
const [customAgents, setCustomAgents] = useState<any[]>([])
const [customAgentsLoading, setCustomAgentsLoading] = useState(true)
const [actionLoading, setActionLoading] = useState<string | null>(null)
useEffect(() => {
loadTools()
loadStats()
loadCustomAgents()
loadFrameworkTemplates()
loadModels()
}, [])
// 加载框架模板列表
const loadFrameworkTemplates = async () => {
try {
const result = await TaijiAPIClient.getCustomAgentTemplates()
if (result?.success && result.data) {
// 新格式:{ frameworkTemplates: [...], dataTemplates: [...] }
if (result.data.frameworkTemplates) {
// 框架模板:字符串数组 ["A2A", "langchain", "MCP"]
const templates = result.data.frameworkTemplates.map((template: string) => {
const displayNames: Record<string, string> = {
'A2A': 'A2A框架',
'langchain': 'LangChain框架',
'MCP': 'MCP框架'
}
return {
name: template,
displayName: displayNames[template] || template,
description: ''
}
})
setFrameworkTemplates(templates)
}
// 数据存储模板:对象数组,包含 env_info
if (result.data.dataTemplates) {
setApiDataTemplates(result.data.dataTemplates)
}
// 兼容旧格式:{ templates: [...] }
if (result.data.templates && !result.data.frameworkTemplates) {
const templates = result.data.templates.map((template: string | any) => {
if (typeof template === 'string') {
const displayNames: Record<string, string> = {
'A2A': 'A2A框架',
'langchain': 'LangChain框架',
'MCP': 'MCP框架'
}
return {
name: template,
displayName: displayNames[template] || template,
description: ''
}
}
return template
})
setFrameworkTemplates(templates)
}
}
} catch (error) {
console.error("Failed to load framework templates:", error)
}
}
// 加载模型列表
const loadModels = async () => {
try {
const result = await TaijiAPIClient.getUserModels()
if (result?.success && result.data?.models) {
setAvailableModels(result.data.models)
// 如果当前选择的模型不在列表中,使用第一个模型作为默认值
if (result.data.models.length > 0 && !result.data.models.find((m: any) => m.id === podConfig.model)) {
setPodConfig({ ...podConfig, model: result.data.models[0].id })
}
}
} catch (error) {
console.error("Failed to load models:", error)
}
}
const loadTools = async () => {
try {
setLoading(true)
// 使用文档规定的接口:GET /api/user/tools
const result = await TaijiAPIClient.getUserTools()
if (result?.success && result.data?.tools) {
setTools(result.data.tools)
// 将已注册的工具作为数据模板选项
setDataTemplates(result.data.tools)
} else if (Array.isArray(result)) {
// 兼容旧格式
setTools(result)
setDataTemplates(result)
}
} catch (error) {
console.error("Failed to load tools:", error)
} finally {
setLoading(false)
}
}
const loadStats = async () => {
try {
// 使用文档规定的接口:GET /api/user/tools/stats
const result = await TaijiAPIClient.getToolsStats()
if (result?.success && result.data) {
setStats({
totalApis: result.data.totalTools || 0,
generatedTools: result.data.generatedTools || 0,
activePods: result.data.activeTools || 0,
})
} else if (result) {
// 兼容旧格式(Data Ingestion Service)
setStats({
totalApis: result.total_apis || 0,
generatedTools: result.generated_tools || 0,
activePods: result.active_pods || 0,
})
}
} catch (error) {
console.error("Failed to load stats:", error)
}
}
// 加载自定义Agent列表
const loadCustomAgents = async () => {
try {
setCustomAgentsLoading(true)
const result = await TaijiAPIClient.getUserCustomAgents()
if (result?.success && result.data?.agents) {
setCustomAgents(result.data.agents)
}
} catch (error) {
console.error("Failed to load custom agents:", error)
} finally {
setCustomAgentsLoading(false)
}
}
// 删除自定义Agent
const handleDeleteCustomAgent = async (agentName: string) => {
if (!confirm(t(`确定要删除Agent "${agentName}" 吗?此操作不可恢复。`, `Are you sure you want to delete agent "${agentName}"? This action cannot be undone.`))) {
return
}
try {
setActionLoading(agentName)
const result = await TaijiAPIClient.deleteCustomAgent(agentName)
if (result?.success) {
loadCustomAgents()
}
} catch (error) {
console.error("Failed to delete custom agent:", error)
} finally {
setActionLoading(null)
}
}
// 停止自定义Agent
const handleStopCustomAgent = async (agentName: string) => {
if (!confirm(t(`确定要停止Agent "${agentName}" 吗?`, `Are you sure you want to stop agent "${agentName}"?`))) {
return
}
try {
setActionLoading(agentName)
const result = await TaijiAPIClient.stopCustomAgent(agentName)
if (result?.success) {
loadCustomAgents()
}
} catch (error) {
console.error("Failed to stop custom agent:", error)
} finally {
setActionLoading(null)
}
}
// 重启自定义Agent
const handleRestartCustomAgent = async (agentName: string) => {
try {
setActionLoading(agentName)
const result = await TaijiAPIClient.restartCustomAgent(agentName)
if (result?.success) {
loadCustomAgents()
}
} catch (error) {
console.error("Failed to restart custom agent:", error)
} finally {
setActionLoading(null)
}
}
// 删除工具
const handleDeleteTool = async (toolId: string) => {
if (!confirm(t(`确定要删除工具吗?此操作不可恢复。`, `Are you sure you want to delete this tool? This action cannot be undone.`))) {
return
}
try {
const result = await TaijiAPIClient.deleteUserTool(toolId)
if (result?.success) {
alert(t("工具删除成功", "Tool deleted successfully"))
loadTools()
loadStats()
} else {
throw new Error(result?.message || "Delete failed")
}
} catch (error: any) {
console.error("Failed to delete tool:", error)
alert(t("删除失败", "Delete failed") + ": " + (error.message || t("未知错误", "Unknown error")))
}
}
// 创建数据模板(实际调用创建工具接口)
const handleCreateTemplate = async () => {
if (!templateName) {
alert(t("请输入模板名称", "Please enter template name"))
return
}
try {
setCreatingTemplate(true)
let config: any = {}
let toolType = "api" // 默认工具类型
let description = ""
if (templateType === "json_api") {
// JSON API 模板
toolType = "api"
if (useJsonUpload && templateConfig.jsonContent) {
config.jsonContent = templateConfig.jsonContent
description = t("JSON API工具(JSON内容)", "JSON API Tool (JSON Content)")
} else if (templateConfig.url) {
config.endpoint = templateConfig.url
config.method = "GET"
if (templateConfig.apiKey) config.apiKey = templateConfig.apiKey
if (templateConfig.queryParams) config.queryParams = templateConfig.queryParams
description = t("JSON API工具", "JSON API Tool")
} else {
alert(t("请提供API URL或JSON内容", "Please provide API URL or JSON content"))
return
}
} else if (templateType === "cloud_storage") {
// 云存储模板
toolType = "cloud_storage"
if (!templateConfig.service || !templateConfig.connectionString) {
alert(t("请填写存储服务和连接字符串", "Please fill in storage service and connection string"))
return
}
config.service = templateConfig.service
config.connectionString = templateConfig.connectionString
config.endpoint = `storage://${templateConfig.service}`
config.method = "GET"
description = t("云存储工具", "Cloud Storage Tool")
} else if (templateType === "database") {
// 数据库模板
toolType = "database"
if (!templateConfig.dbType || !templateConfig.connectionString) {
alert(t("请填写数据库类型和连接字符串", "Please fill in database type and connection string"))
return
}
config.dbType = templateConfig.dbType
config.connectionString = templateConfig.connectionString
config.endpoint = `database://${templateConfig.dbType}`
config.method = "GET"
description = t("数据库工具", "Database Tool")
}
// 使用创建工具接口 POST /api/user/tools/create
const result = await TaijiAPIClient.createTool({
name: templateName,
description: description || t("数据模板工具", "Data Template Tool"),
type: toolType,
config: config,
})
if (result?.success) {
alert(t("工具创建成功", "Tool created successfully"))
setShowTemplateDialog(false)
// 重置表单
setTemplateName("")
setTemplateConfig({
url: "",
apiKey: "",
queryParams: "",
jsonContent: "",
service: "",
dbType: "",
connectionString: "",
})
setUseJsonUpload(false)
// 刷新工具列表和统计数据
await loadTools()
await loadStats()
} else {
throw new Error(result?.message || "Create tool failed")
}
} catch (error: any) {
console.error("Failed to create tool:", error)
alert(t("创建失败", "Create failed") + ": " + (error.message || t("未知错误", "Unknown error")))
} finally {
setCreatingTemplate(false)
}
}
// 部署Agent(创建自定义Agent)
const handleDeployAgent = async () => {
// 校验必填字段
if (!selectedApiDataTemplate || !toolName) {
alert(t("请选择数据存储模板并填写Agent名称", "Please select a data template and enter agent name"))
return
}
try {
setDeploying(true)
// 将CPU和内存转换为K8s格式
const cpuRequest = `${podConfig.cpuCores * 1000}m` // 转换为millicores
const cpuLimit = `${podConfig.cpuCores * 1000}m` // 默认与request相同
const memoryRequest = `${podConfig.memoryGB}Gi`
const memoryLimit = `${podConfig.memoryGB}Gi` // 默认与request相同
// 收集选择的工具ID列表
const selectedTools: string[] = []
if (selectedDataTemplate) {
// 从工具列表中找到对应的工具ID
const tool = tools.find((t) => t.name === selectedDataTemplate || t.id === selectedDataTemplate)
if (tool?.id) {
selectedTools.push(tool.id)
}
}
// 框架类型(默认MCP)
const frameworkName = selectedFramework || "MCP"
// 构建请求参数
const requestData: any = {
name: toolName,
template: selectedApiDataTemplate, // 必填:数据存储模板(如 mysql_agent)
frameworkTemplate: frameworkName, // 可选:框架类型(A2A/langchain/MCP)
description: toolName, // 可选:Agent描述
cpuRequest: cpuRequest, // 必填:CPU请求量
cpuLimit: cpuLimit, // 可选:CPU限制量
memoryRequest: memoryRequest, // 必填:内存请求量
memoryLimit: memoryLimit, // 可选:内存限制量
tools: selectedTools.length > 0 ? selectedTools : undefined, // 可选:工具ID列表
model: podConfig.model, // 可选:模型名称
}
// 添加环境变量配置(如数据库连接信息)
if (Object.keys(envConfig).length > 0) {
requestData.envConfig = envConfig
}
// A2A 框架专用字段
if (frameworkName === "A2A") {
if (agentRole) {
requestData.agentRole = agentRole
}
if (agentCapabilities) {
// 将逗号分隔的字符串转换为数组
requestData.agentCapabilities = agentCapabilities.split(",").map(s => s.trim()).filter(Boolean)
}
}
const result = await TaijiAPIClient.createCustomAgent(requestData)
if (result?.success) {
alert(t("自定义Agent创建成功,正在部署", "Custom Agent created successfully, deploying"))
setShowNewToolDialog(false)
// 重置表单
setSelectedFramework("")
setSelectedServiceGateway("")
setSelectedDataTemplate("")
setSelectedApiDataTemplate("")
setEnvConfig({})
setAgentRole("")
setAgentCapabilities("")
setToolName("")
setPodConfig({
agentCount: 1,
cpuCores: 1,
memoryGB: 2,
maxAgents: 5,
model: "gpt-4o-mini",
})
// 刷新数据
loadCustomAgents()
loadStats()
} else {
throw new Error(result?.message || "Deployment failed")
}
} catch (error: any) {
console.error("Failed to deploy agent:", error)
alert(t("部署失败", "Deployment failed") + ": " + (error.message || t("未知错误", "Unknown error")))
} finally {
setDeploying(false)
}
}
// 获取状态颜色
const getStatusColor = (status: string) => {
switch (status?.toLowerCase()) {
case "running":
return "bg-green-500/10 text-green-500"
case "stopped":
return "bg-gray-500/10 text-gray-500"
case "pending":
return "bg-yellow-500/10 text-yellow-500"
case "failed":
return "bg-red-500/10 text-red-500"
default:
return "bg-gray-500/10 text-gray-500"
}
}
return (
<AuthGuard>
<DashboardLayout>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">{t("数据与工具", "Data & Tools")}</h1>
<p className="text-muted-foreground mt-1">
{t("API市场和工具生成平台", "API marketplace and tool generation platform")}
</p>
</div>
<Button onClick={() => setShowNewToolDialog(true)} className="gap-2">
<Plus className="h-4 w-4" />
{t("部署Agent", "Deploy Agent")}
</Button>
</div>
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">{t("总工具数", "Total Tools")}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{loading ? (
<span className="inline-block h-7 w-12 animate-pulse bg-muted rounded" />
) : (
stats.totalApis
)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{loading ? "" : `${stats.totalApis - stats.generatedTools} ${t("个已处理", "processed")}`}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("生成的工具", "Generated Tools")}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{loading ? (
<span className="inline-block h-7 w-8 animate-pulse bg-muted rounded" />
) : (
stats.generatedTools
)}
</div>
<p className="text-xs text-muted-foreground mt-1">{t("可以使用", "Ready to use")}</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t("活跃的工具数", "Active Tools")}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{loading ? (
<span className="inline-block h-7 w-8 animate-pulse bg-muted rounded" />
) : (
stats.activePods
)}
</div>
<p className="text-xs text-muted-foreground mt-1">{t("运行中", "Running")}</p>
</CardContent>
</Card>
</div>
<Tabs defaultValue="tools" className="space-y-4">
<TabsList>
<TabsTrigger value="tools">{t("可用工具", "Available Tools")}</TabsTrigger>
<TabsTrigger value="templates">{t("数据模板", "Data Templates")}</TabsTrigger>
</TabsList>
<TabsContent value="tools" className="space-y-4">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>{t("工具注册表", "Tool Registry")}</CardTitle>
<CardDescription>
{t("管理和监控生成的API工具", "Manage and monitor generated API tools")}
</CardDescription>
</div>
<div className="flex gap-2">
<Input placeholder={t("搜索工具...", "Search tools...")} className="w-64" />
<Button variant="outline" size="icon">
<Search className="h-4 w-4" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("工具名称", "Tool Name")}</TableHead>
<TableHead>{t("类别", "Category")}</TableHead>
<TableHead>{t("方法", "Method")}</TableHead>
<TableHead>{t("端点", "Endpoint")}</TableHead>
<TableHead>{t("状态", "Status")}</TableHead>
<TableHead>{t("创建时间", "Created")}</TableHead>
<TableHead className="text-right">{t("操作", "Actions")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
<span className="text-muted-foreground">{t("加载中...", "Loading...")}</span>
</TableCell>
</TableRow>
) : tools.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
<span className="text-muted-foreground">{t("暂无工具", "No tools available")}</span>
</TableCell>
</TableRow>
) : (
tools.map((tool) => (
<TableRow key={tool.id || tool.name}>
<TableCell className="font-medium">{tool.name}</TableCell>
<TableCell>
<Badge variant="secondary">{tool.type || tool.category || "-"}</Badge>
</TableCell>
<TableCell>
<Badge variant="outline">{tool.config?.method || tool.method || "-"}</Badge>
</TableCell>
<TableCell className="text-xs text-muted-foreground">{tool.config?.endpoint || tool.config?.url || tool.url || "-"}</TableCell>
<TableCell>
<Badge className="bg-green-500/10 text-green-500">{tool.status || "active"}</Badge>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{tool.created_at ? new Date(tool.created_at).toLocaleString() : tool.created || "-"}
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
variant="ghost"
size="icon"
title={t("删除工具", "Delete Tool")}
onClick={() => handleDeleteTool(tool.id || tool.name)}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
{/* 自定义Agent管理 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<Bot className="h-5 w-5" />
{t("自定义Agent管理", "Custom Agent Management")}
</CardTitle>
<CardDescription>
{t("管理已部署的自定义Agent,支持停止、重启和删除操作", "Manage deployed custom agents with stop, restart and delete operations")}
</CardDescription>
</div>
<Button variant="outline" size="sm" onClick={loadCustomAgents} className="gap-2">
<RefreshCw className="h-4 w-4" />
{t("刷新", "Refresh")}
</Button>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("Agent名称", "Agent Name")}</TableHead>
<TableHead>{t("模板", "Template")}</TableHead>
<TableHead>{t("状态", "Status")}</TableHead>
<TableHead>{t("CPU", "CPU")}</TableHead>
<TableHead>{t("内存", "Memory")}</TableHead>
<TableHead>{t("运行时间", "Runtime")}</TableHead>
<TableHead className="text-right">{t("操作", "Actions")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{customAgentsLoading ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
<span className="text-muted-foreground">{t("加载中...", "Loading...")}</span>
</TableCell>
</TableRow>
) : customAgents.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
<span className="text-muted-foreground">{t("暂无自定义Agent", "No custom agents")}</span>
</TableCell>
</TableRow>
) : (
customAgents.map((agent) => (
<TableRow key={agent.name}>
<TableCell className="font-medium">{agent.name}</TableCell>
<TableCell>
<Badge variant="secondary">{agent.template || "-"}</Badge>
</TableCell>
<TableCell>
<Badge className={getStatusColor(agent.status)}>
{agent.status || "Unknown"}
</Badge>
</TableCell>
<TableCell className="text-sm">{agent.cpu || "-"}</TableCell>
<TableCell className="text-sm">{agent.memory || "-"}</TableCell>
<TableCell className="text-sm text-muted-foreground">
{agent.runningSeconds
? `${Math.floor(agent.runningSeconds / 3600)}h ${Math.floor((agent.runningSeconds % 3600) / 60)}m`
: "-"
}
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-1">
{agent.status?.toLowerCase() === "running" ? (
<>
<Button
variant="ghost"
size="icon"
title={t("停止", "Stop")}
disabled={actionLoading === agent.name}
onClick={() => handleStopCustomAgent(agent.name)}
>
<Square className="h-4 w-4 text-yellow-500" />
</Button>
<Button
variant="ghost"
size="icon"
title={t("重启", "Restart")}
disabled={actionLoading === agent.name}
onClick={() => handleRestartCustomAgent(agent.name)}
>
<RefreshCw className="h-4 w-4 text-blue-500" />
</Button>
</>
) : (
<Button
variant="ghost"
size="icon"
title={t("启动", "Start")}
disabled={actionLoading === agent.name}
onClick={() => handleRestartCustomAgent(agent.name)}
>
<Play className="h-4 w-4 text-green-500" />
</Button>
)}
<Button
variant="ghost"
size="icon"
title={t("删除", "Delete")}
disabled={actionLoading === agent.name}
onClick={() => handleDeleteCustomAgent(agent.name)}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="templates" className="space-y-4">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>{t("数据模板管理", "Data Template Management")}</CardTitle>
<CardDescription>
{t("配置JSON接口和云存储数据源", "Configure JSON interfaces and cloud storage data sources")}
</CardDescription>
</div>
<Button onClick={() => setShowTemplateDialog(true)} className="gap-2">
<Plus className="h-4 w-4" />
{t("创建数据模板", "Create Data Template")}
</Button>
</div>
</CardHeader>
<CardContent>
<div className="grid gap-4 md:grid-cols-2">
<Card className="border-primary/20 bg-primary/5">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<FileJson className="h-5 w-5 text-primary" />
<CardTitle className="text-base">{t("JSON API 模板", "JSON API Template")}</CardTitle>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">
{t(
"通过提供数据接口URL和查询参数来配置JSON API访问",
"Configure JSON API access by providing interface URL and query parameters",
)}
</p>
</CardContent>
</Card>
<Card className="border-blue-500/20 bg-blue-500/5">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Cloud className="h-5 w-5 text-blue-500" />
<CardTitle className="text-base">
{t("云存储与数据库模板", "Cloud Storage & Database Template")}
</CardTitle>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">
{t(
"支持Azure、Google、AWS存储服务和各种原生数据库",
"Support Azure, Google, AWS storage services and various native databases",
)}
</p>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
{/* 生成新工具对话框 - 包含Pod配置 */}
<Dialog open={showNewToolDialog} onOpenChange={setShowNewToolDialog}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{t("部署Agent", "Deploy Agent")}</DialogTitle>
<DialogDescription>
{t(
"根据Agent框架注册工具并配置Pod资源",
"Register tool based on agent framework and configure pod resources",
)}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>{t("Agent名称", "Agent Name")} <span className="text-destructive">*</span></Label>
<Input
placeholder={t("输入Agent名称(小写字母、数字、连字符)", "Enter agent name (lowercase, numbers, hyphens)")}
value={toolName}
onChange={(e) => setToolName(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '-'))}
/>
</div>
{/* 数据存储模板选择 */}
<div className="space-y-2">
<Label>{t("数据存储模板", "Data Storage Template")} <span className="text-destructive">*</span></Label>
<Select
value={selectedApiDataTemplate}
onValueChange={(value) => {
setSelectedApiDataTemplate(value)
// 重置环境变量配置
setEnvConfig({})
}}
>
<SelectTrigger>
<SelectValue placeholder={t("选择数据存储模板", "Select data storage template")} />
</SelectTrigger>
<SelectContent>
{apiDataTemplates.length === 0 ? (
<SelectItem value="" disabled>
{t("暂无可用模板", "No templates available")}
</SelectItem>
) : (
apiDataTemplates.map((template) => (
<SelectItem key={template.template} value={template.template}>
<div className="flex flex-col">
<span>{template.template}</span>
{template.description && (
<span className="text-xs text-muted-foreground">{template.description}</span>
)}
</div>
</SelectItem>
))
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("选择Agent使用的数据存储类型", "Select data storage type for the agent")}
</p>
</div>
{/* 动态环境变量配置(根据选择的模板) */}
{selectedApiDataTemplate && (() => {
const template = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!template?.env_info) return null
const required = template.env_info.required || {}
const optional = template.env_info.optional || {}
return (
<div className="border rounded-lg p-4 space-y-3 bg-muted/30">
<h4 className="font-medium text-sm">{t("数据库连接配置", "Database Connection Config")}</h4>
{Object.entries(required).map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm">{key} <span className="text-destructive">*</span></Label>
<Input
placeholder={String(desc)}
value={envConfig[key] || ""}
onChange={(e) => setEnvConfig({ ...envConfig, [key]: e.target.value })}
type={key.toLowerCase().includes('password') ? 'password' : 'text'}
/>
</div>
))}
{Object.entries(optional).map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm text-muted-foreground">{key} {t("(可选)", "(Optional)")}</Label>
<Input
placeholder={String(desc)}
value={envConfig[key] || ""}
onChange={(e) => setEnvConfig({ ...envConfig, [key]: e.target.value })}
/>
</div>
))}
</div>
)
})()}
{/* 框架类型选择 */}
<div className="space-y-2">
<Label>{t("框架类型", "Framework Type")}</Label>
<Select value={selectedFramework} onValueChange={setSelectedFramework}>
<SelectTrigger>
<SelectValue placeholder={t("选择框架类型(默认MCP)", "Select framework type (default MCP)")} />
</SelectTrigger>
<SelectContent>
{frameworkTemplates.length === 0 ? (
<SelectItem value="" disabled>
{t("加载中...", "Loading...")}
</SelectItem>
) : (
frameworkTemplates.map((template) => (
<SelectItem key={template.name} value={template.name}>
<div className="flex items-center gap-2">
<Code2 className="h-4 w-4" />
{template.displayName || template.name}
</div>
</SelectItem>
))
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("选择Agent运行框架,默认为MCP", "Select agent runtime framework, defaults to MCP")}
</p>
</div>
{/* A2A 框架专用配置 */}
{selectedFramework === "A2A" && (
<div className="border rounded-lg p-4 space-y-3 bg-blue-500/5 border-blue-500/20">
<h4 className="font-medium text-sm flex items-center gap-2">
<Workflow className="h-4 w-4 text-blue-500" />
{t("A2A 框架配置", "A2A Framework Config")}
</h4>
<div className="space-y-1">
<Label className="text-sm">{t("Agent角色", "Agent Role")}</Label>
<Input
placeholder={t("如:data_analyzer", "e.g., data_analyzer")}
value={agentRole}
onChange={(e) => setAgentRole(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label className="text-sm">{t("Agent能力", "Agent Capabilities")}</Label>
<Input
placeholder={t("多个能力用逗号分隔,如:sql_query, data_analysis", "Comma separated, e.g., sql_query, data_analysis")}
value={agentCapabilities}
onChange={(e) => setAgentCapabilities(e.target.value)}
/>
</div>
</div>
)}
<div className="border-t pt-4 space-y-4">
<h3 className="font-semibold flex items-center gap-2">
<Cpu className="h-4 w-4" />
{t("Pod资源配置", "Pod Resource Configuration")}
</h3>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>{t("服务网关", "Service Gateway")}</Label>
<Select value={selectedServiceGateway} onValueChange={setSelectedServiceGateway}>
<SelectTrigger>
<SelectValue placeholder={t("选择服务网关", "Select service gateway")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="mcp-gateway">MCP Gateway</SelectItem>
<SelectItem value="a2a-gateway">A2A Gateway</SelectItem>
<SelectItem value="api-gateway">API Gateway</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("选择Pod使用的服务网关", "Select service gateway for the pod")}
</p>
</div>
<div className="space-y-2">
<Label>{t("数据模板", "Data Template")}</Label>
<Select value={selectedDataTemplate} onValueChange={setSelectedDataTemplate}>
<SelectTrigger>
<SelectValue placeholder={t("选择已注册的工具", "Select registered tool")} />
</SelectTrigger>
<SelectContent>
{dataTemplates.length === 0 ? (
<SelectItem value="none" disabled>
{t("暂无可用工具", "No available tools")}
</SelectItem>
) : (
dataTemplates.map((template) => (
<SelectItem key={template.name} value={template.name}>
{template.name}
</SelectItem>
))
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("选择已注册的工具作为数据源", "Select registered tool as data source")}
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>{t("Agent个数", "Agent Count")}</Label>
<Input
type="number"
min="1"
max="10"
value={podConfig.agentCount}
onChange={(e) => setPodConfig({ ...podConfig, agentCount: Number.parseInt(e.target.value) })}
/>
</div>
<div className="space-y-2">
<Label>{t("CPU核数", "CPU Cores")}</Label>
<Input
type="number"
min="1"
max="16"
value={podConfig.cpuCores}
onChange={(e) => setPodConfig({ ...podConfig, cpuCores: Number.parseInt(e.target.value) })}
/>
</div>
<div className="space-y-2">
<Label>{t("内存大小 (GB)", "Memory Size (GB)")}</Label>
<Input
type="number"
min="1"
max="64"
value={podConfig.memoryGB}
onChange={(e) => setPodConfig({ ...podConfig, memoryGB: Number.parseInt(e.target.value) })}
/>
</div>
<div className="space-y-2">
<Label>{t("可扩展Agent数量", "Scalable Agent Count")}</Label>
<Input
type="number"
min="1"
max="100"
value={podConfig.maxAgents}
onChange={(e) => setPodConfig({ ...podConfig, maxAgents: Number.parseInt(e.target.value) })}
/>
</div>
</div>
<div className="space-y-2">
<Label>{t("使用的模型", "Model to Use")}</Label>
<Select value={podConfig.model} onValueChange={(value) => setPodConfig({ ...podConfig, model: value })}>
<SelectTrigger>
<SelectValue placeholder={t("选择模型", "Select model")} />
</SelectTrigger>
<SelectContent>
{availableModels.length === 0 ? (
<SelectItem value="" disabled>
{t("加载中...", "Loading...")}
</SelectItem>
) : (
availableModels.map((model) => (
<SelectItem key={model.id} value={model.id}>
<div className="flex flex-col">
<span>{model.name}</span>
{model.description && (
<span className="text-xs text-muted-foreground">{model.description}</span>
)}
</div>
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
<div className="rounded-lg bg-muted p-4 text-sm">
<p className="font-medium mb-2">{t("配置摘要", "Configuration Summary")}</p>
<div className="grid grid-cols-2 gap-2 text-muted-foreground">
<div className="col-span-2">
{t("Agent名称", "Agent Name")}: {toolName || t("未填写", "Not filled")}
</div>
<div className="col-span-2">
{t("数据存储模板", "Data Template")}: {selectedApiDataTemplate || t("未选择", "Not selected")}
</div>
<div className="col-span-2">
{t("框架", "Framework")}: {selectedFramework || "MCP"}
</div>
<div>
{t("CPU", "CPU")}: {podConfig.cpuCores} {t("核", "cores")}
</div>
<div>
{t("内存", "Memory")}: {podConfig.memoryGB}GB
</div>
<div>
{t("模型", "Model")}: {podConfig.model}
</div>
{selectedFramework === "A2A" && agentRole && (
<div className="col-span-2">
{t("Agent角色", "Agent Role")}: {agentRole}
</div>
)}
</div>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowNewToolDialog(false)} disabled={deploying}>
{t("取消", "Cancel")}
</Button>
<Button onClick={handleDeployAgent} disabled={deploying || !selectedApiDataTemplate || !toolName}>
{deploying ? t("部署中...", "Deploying...") : t("部署Agent", "Deploy Agent")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 数据模板对话框 */}
<Dialog open={showTemplateDialog} onOpenChange={setShowTemplateDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{t("创建数据模板", "Create Data Template")}</DialogTitle>
<DialogDescription>
{t("选择模板类型并填写配置信息", "Select template type and fill in configuration information")}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>{t("模板类型", "Template Type")}</Label>
<Select value={templateType} onValueChange={(value: "json_api" | "cloud_storage" | "database") => {
setTemplateType(value)
// 重置配置
setTemplateConfig({
url: "",
apiKey: "",
queryParams: "",
jsonContent: "",
service: "",
dbType: "",
connectionString: "",
})
}}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="json_api">
<div className="flex items-center gap-2">
<FileJson className="h-4 w-4" />
{t("JSON API 模板", "JSON API Template")}
</div>
</SelectItem>
<SelectItem value="cloud_storage">
<div className="flex items-center gap-2">
<Cloud className="h-4 w-4" />
{t("云存储模板", "Cloud Storage Template")}
</div>
</SelectItem>
<SelectItem value="database">
<div className="flex items-center gap-2">
<Cloud className="h-4 w-4" />
{t("数据库模板", "Database Template")}
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>{t("模板名称", "Template Name")}</Label>
<Input
placeholder={t("输入模板名称", "Enter template name")}
value={templateName}
onChange={(e) => setTemplateName(e.target.value)}
/>
</div>
{templateType === "json_api" ? (
<>
<div className="flex items-center space-x-2">
<Checkbox
id="useJsonUpload"
checked={useJsonUpload}
onCheckedChange={(checked) => setUseJsonUpload(checked === true)}
/>
<Label htmlFor="useJsonUpload" className="text-sm font-normal cursor-pointer">
{t("上传JSON文件", "Upload JSON File")}
</Label>
</div>
{useJsonUpload ? (
<>
<div className="space-y-2">
<Label>{t("选择JSON文件", "Select JSON File")}</Label>
<div className="flex items-center gap-2">
<Input
type="file"
accept=".json,application/json"
className="flex-1"
/>
<Button variant="outline" size="sm" className="gap-1">
<Upload className="h-4 w-4" />
{t("上传", "Upload")}
</Button>
</div>
<p className="text-xs text-muted-foreground">
{t("通过APILLAMA自动识别请求参数", "Automatically identify request parameters via APILLAMA")}
</p>
</div>
</>
) : (
<>
<div className="space-y-2">
<Label>{t("数据接口URL", "Data Interface URL")}</Label>
<Input
placeholder="https://api.example.com/data"
value={templateConfig.url}
onChange={(e) => setTemplateConfig({ ...templateConfig, url: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label>{t("密钥Key", "API Key")}</Label>
<Input
placeholder={t("输入API密钥", "Enter API Key")}
type="password"
value={templateConfig.apiKey}
onChange={(e) => setTemplateConfig({ ...templateConfig, apiKey: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label>{t("查询参数", "Query Parameters")}</Label>
<textarea
className="w-full h-24 rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder={t(
"输入查询参数,例如: page=1&limit=10",
"Enter query parameters, e.g., page=1&limit=10",
)}
value={templateConfig.queryParams}
onChange={(e) => setTemplateConfig({ ...templateConfig, queryParams: e.target.value })}
/>
</div>
</>
)}
</>
) : templateType === "cloud_storage" ? (
<>
<div className="space-y-2">
<Label>{t("存储服务", "Storage Service")}</Label>
<Select value={templateConfig.service} onValueChange={(value) => setTemplateConfig({ ...templateConfig, service: value })}>
<SelectTrigger>
<SelectValue placeholder={t("选择服务", "Select service")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="azure_blob">Azure Blob Storage</SelectItem>
<SelectItem value="aws_s3">AWS S3</SelectItem>
<SelectItem value="aliyun_oss">阿里云OSS</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>{t("连接字符串", "Connection String")}</Label>
<Input
placeholder={t("输入连接字符串", "Enter connection string")}
type="password"
value={templateConfig.connectionString}
onChange={(e) => setTemplateConfig({ ...templateConfig, connectionString: e.target.value })}
/>
</div>
</>
) : (
<>
<div className="space-y-2">
<Label>{t("数据库类型", "Database Type")}</Label>
<Select value={templateConfig.dbType} onValueChange={(value) => setTemplateConfig({ ...templateConfig, dbType: value })}>
<SelectTrigger>
<SelectValue placeholder={t("选择数据库类型", "Select database type")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="postgresql">PostgreSQL</SelectItem>
<SelectItem value="mysql">MySQL</SelectItem>
<SelectItem value="mongodb">MongoDB</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>{t("连接字符串", "Connection String")}</Label>
<Input
placeholder={t("输入连接字符串", "Enter connection string")}
type="password"
value={templateConfig.connectionString}
onChange={(e) => setTemplateConfig({ ...templateConfig, connectionString: e.target.value })}
/>
</div>
</>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowTemplateDialog(false)} disabled={creatingTemplate}>
{t("取消", "Cancel")}
</Button>
<Button onClick={handleCreateTemplate} disabled={creatingTemplate || !templateName}>
{creatingTemplate ? t("创建中...", "Creating...") : t("创建模板", "Create Template")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DashboardLayout>
</AuthGuard>
)
}