Files
taiji-pda-v0/app/data-tools/page.tsx
T
zhanggangyong 6672904036 feat: 适配后端接口文档v4,更新工具和Agent创建逻辑
- 更新 createTool 接口:使用 template + envConfig 格式替代 type + config
- 更新 createCustomAgent 接口:添加 tools 参数支持,从工具获取配置
- 更新 updateTool 接口:支持 is_active 和 envConfig 参数
- 工具列表展示 template、description、is_active 字段
- 部署Agent时从已创建的工具列表中选择
- 环境变量配置过滤 OPENAI_API_KEY(系统自动注入)
2026-01-13 12:51:04 +00:00

1436 lines
63 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")))
}
}
// 创建数据模块(实际调用创建工具接口)
// 根据接口文档 v4:使用 template + envConfig 格式
const handleCreateTemplate = async () => {
if (!templateName) {
alert(t("请输入模块名称", "Please enter module name"))
return
}
try {
setCreatingTemplate(true)
let description = ""
let template = ""
let toolEnvConfig: Record<string, string> = {}
// 数据存储模块(来自API的dataTemplates)
if (templateType === "data_storage" as any) {
if (!selectedApiDataTemplate) {
alert(t("请选择数据存储模块", "Please select a data storage module"))
return
}
const templateInfo = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!templateInfo) {
alert(t("无效的模块", "Invalid module"))
return
}
// 校验必填环境变量(排除 OPENAI_API_KEY,系统会自动注入)
const required = templateInfo.env_info?.required || {}
for (const key of Object.keys(required)) {
// OPENAI_API_KEY 无需用户填写,系统自动注入
if (key === "OPENAI_API_KEY") continue
if (!envConfig[key]) {
alert(t(`请填写 ${key}`, `Please fill in ${key}`))
return
}
}
template = selectedApiDataTemplate
toolEnvConfig = { ...envConfig }
description = templateInfo.description || t("数据存储工具", "Data Storage Tool")
} else if (templateType === "json_api") {
// JSON API 模块 - 暂不支持新格式,保留原逻辑提示
alert(t("JSON API模块即将支持,请使用数据存储模块", "JSON API module coming soon, please use data storage module"))
return
} else if (templateType === "cloud_storage") {
// 云存储模块 - 暂不支持新格式,保留原逻辑提示
alert(t("云存储模块即将支持,请使用数据存储模块", "Cloud storage module coming soon, please use data storage module"))
return
}
// 使用创建工具接口 POST /api/user/tools/create
// 根据接口文档 v4 格式:name, description, template, envConfig
const result = await TaijiAPIClient.createTool({
name: templateName,
description: description || t("数据模块工具", "Data Module Tool"),
template: template,
envConfig: toolEnvConfig,
})
if (result?.success) {
alert(t("工具创建成功", "Tool created successfully"))
setShowTemplateDialog(false)
// 重置表单
setTemplateName("")
setTemplateConfig({
url: "",
apiKey: "",
queryParams: "",
jsonContent: "",
service: "",
dbType: "",
connectionString: "",
})
setUseJsonUpload(false)
setSelectedApiDataTemplate("")
setEnvConfig({})
// 刷新工具列表和统计数据
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)
// 根据接口文档 v4:使用 tools 参数,系统自动从工具获取 template 和 envConfig
const handleDeployAgent = async () => {
// 校验必填字段:Agent名称必填,工具或数据存储模块二选一
if (!toolName) {
alert(t("请填写Agent名称", "Please enter agent name"))
return
}
// 获取选中的工具(从已创建的工具列表中选择)
const selectedToolIds = selectedDataTemplate ? [selectedDataTemplate] : []
// 如果没有选择已有工具,但选择了数据存储模块,需要提示先创建工具
if (selectedToolIds.length === 0 && selectedApiDataTemplate) {
// 如果选了模板但没选工具,说明要直接用模板创建(旧模式)
// 新接口要求必须先创建工具,再基于工具创建Agent
alert(t("请先在「工具模块」标签页创建工具,然后再部署Agent", "Please create a tool first in the 'Tool Modules' tab, then deploy the Agent"))
return
}
if (selectedToolIds.length === 0) {
alert(t("请选择一个已创建的工具", "Please select a created tool"))
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相同
// 框架类型(默认MCP)
const frameworkName = selectedFramework || "MCP"
// 构建请求参数 - 根据接口文档 v4 格式
const requestData: any = {
name: toolName, // 必填
tools: selectedToolIds, // 必填:工具ID列表
frameworkTemplate: frameworkName, // 可选:框架类型(A2A/langchain/MCP)
description: toolName, // 可选:Agent描述
cpuRequest: cpuRequest, // 可选:CPU请求量,默认 "100m"
cpuLimit: cpuLimit, // 可选:CPU限制量
memoryRequest: memoryRequest, // 可选:内存请求量,默认 "128Mi"
memoryLimit: memoryLimit, // 可选:内存限制量
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("工具模块", "Tool Modules")}</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("描述", "Description")}</TableHead>
<TableHead>{t("模板类型", "Template")}</TableHead>
<TableHead>{t("分类", "Category")}</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 className="text-sm text-muted-foreground max-w-[200px] truncate">
{tool.description || "-"}
</TableCell>
<TableCell>
<Badge variant="outline">{tool.template || "-"}</Badge>
</TableCell>
<TableCell>
<Badge variant="secondary">{tool.category || tool.type || "database"}</Badge>
</TableCell>
<TableCell>
<Badge className={tool.is_active !== false ? "bg-green-500/10 text-green-500" : "bg-gray-500/10 text-gray-500"}>
{tool.is_active !== false ? t("激活", "Active") : t("停用", "Inactive")}
</Badge>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{tool.created_at ? new Date(tool.created_at).toLocaleString() : "-"}
</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("模块", "Module")}</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 Module 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 Module")}
</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 Module")}</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 Module")}
</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("框架类型", "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("选择工具", "Select Tool")} <span className="text-destructive">*</span></Label>
<Select
value={selectedDataTemplate}
onValueChange={(value) => {
setSelectedDataTemplate(value)
// 找到对应工具,可以显示其模板信息
const tool = dataTemplates.find(t => t.id === value)
if (tool?.template) {
setSelectedApiDataTemplate(tool.template)
}
}}
>
<SelectTrigger>
<SelectValue placeholder={t("选择已创建的工具", "Select a created tool")} />
</SelectTrigger>
<SelectContent>
{dataTemplates.length === 0 ? (
<SelectItem value="" disabled>
{t("暂无可用工具,请先创建工具", "No tools available, please create a tool first")}
</SelectItem>
) : (
dataTemplates.map((tool) => (
<SelectItem key={tool.id} value={tool.id}>
<div className="flex flex-col">
<span>{tool.name}</span>
<span className="text-xs text-muted-foreground">
{tool.template || tool.category || t("工具", "Tool")}
</span>
</div>
</SelectItem>
))
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("从已创建的工具中选择,系统将自动获取工具的模板和配置", "Select from created tools, system will automatically get template and config from tool")}
</p>
</div>
</div>
{/* 动态环境变量配置(根据选择的数据存储模块) */}
{/* 注意:如果选择了已有工具,通常不需要再填写环境变量,因为系统会从工具中获取 */}
{selectedApiDataTemplate && !selectedDataTemplate && (() => {
const template = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!template?.env_info) return null
// 过滤掉 OPENAI_API_KEY,系统会自动注入
const required = Object.entries(template.env_info.required || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
const optional = Object.entries(template.env_info.optional || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
if (required.length === 0 && optional.length === 0) return null
return (
<div className="border rounded-lg p-4 space-y-3 bg-muted/30">
<h4 className="font-medium text-sm">{t("额外环境变量配置(可选)", "Additional Environment Config (Optional)")}</h4>
<p className="text-xs text-muted-foreground">
{t("如需覆盖工具的默认配置,可在此填写", "Fill in here if you need to override tool's default config")}
</p>
{required.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm">{key}</Label>
<Input
placeholder={String(desc)}
value={envConfig[key] || ""}
onChange={(e) => setEnvConfig({ ...envConfig, [key]: e.target.value })}
type={key.toLowerCase().includes('password') ? 'password' : 'text'}
/>
</div>
))}
{optional.length > 0 && (
<>
<div className="border-t pt-2 mt-2">
<p className="text-xs text-muted-foreground mb-2">{t("可选配置", "Optional Config")}</p>
</div>
{optional.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm text-muted-foreground">{key}</Label>
<Input
placeholder={String(desc)}
value={envConfig[key] || ""}
onChange={(e) => setEnvConfig({ ...envConfig, [key]: e.target.value })}
/>
</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("使用工具", "Tool")}: {
selectedDataTemplate
? (dataTemplates.find(t => t.id === selectedDataTemplate)?.name || selectedDataTemplate)
: t("未选择", "Not selected")
}
</div>
<div className="col-span-2">
{t("工具模板", "Tool Template")}: {
selectedDataTemplate
? (dataTemplates.find(t => t.id === selectedDataTemplate)?.template || "-")
: "-"
}
</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 || !selectedDataTemplate || !toolName}>
{deploying ? t("部署中...", "Deploying...") : t("部署Agent", "Deploy Agent")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 数据模块对话框 */}
<Dialog open={showTemplateDialog} onOpenChange={setShowTemplateDialog}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{t("创建数据模块", "Create Data Module")}</DialogTitle>
<DialogDescription>
{t("选择模块类型并填写配置信息", "Select module type and fill in configuration information")}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>{t("模块类型", "Module Type")}</Label>
<Select value={templateType} onValueChange={(value: "json_api" | "cloud_storage" | "database" | "data_storage") => {
setTemplateType(value as any)
// 重置配置
setTemplateConfig({
url: "",
apiKey: "",
queryParams: "",
jsonContent: "",
service: "",
dbType: "",
connectionString: "",
})
setSelectedApiDataTemplate("")
setEnvConfig({})
}}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{/* 数据库模块(来自API) */}
{apiDataTemplates.length > 0 && (
<SelectItem value="data_storage">
<div className="flex items-center gap-2">
<Boxes className="h-4 w-4" />
{t("数据库模块", "Database Module")}
</div>
</SelectItem>
)}
<SelectItem value="json_api">
<div className="flex items-center gap-2">
<FileJson className="h-4 w-4" />
{t("JSON API 模块", "JSON API Module")}
</div>
</SelectItem>
<SelectItem value="cloud_storage">
<div className="flex items-center gap-2">
<Cloud className="h-4 w-4" />
{t("云存储模块", "Cloud Storage Module")}
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 数据库模块(来自API的dataTemplates) */}
{templateType === "data_storage" as any ? (
<>
<div className="space-y-2">
<Label>{t("选择数据库模块", "Select Database Module")} <span className="text-destructive">*</span></Label>
<Select
value={selectedApiDataTemplate}
onValueChange={(value) => {
setSelectedApiDataTemplate(value)
setEnvConfig({})
// 自动填充模块名称
const template = apiDataTemplates.find(t => t.template === value)
if (template) {
setTemplateName(value)
}
}}
>
<SelectTrigger>
<SelectValue placeholder={t("选择模块", "Select module")} />
</SelectTrigger>
<SelectContent>
{apiDataTemplates.map((template) => (
<SelectItem key={template.template} value={template.template}>
<div className="flex flex-col">
<span className="font-medium">{template.template}</span>
{template.description && (
<span className="text-xs text-muted-foreground">{template.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 模块名称(自定义) */}
<div className="space-y-2">
<Label>{t("模块名称", "Module Name")} <span className="text-destructive">*</span></Label>
<Input
placeholder={t("输入模块名称", "Enter module name")}
value={templateName}
onChange={(e) => setTemplateName(e.target.value)}
/>
</div>
{/* 动态环境变量配置 - 根据后端返回的 env_info 生成,过滤掉 OPENAI_API_KEY(系统自动注入) */}
{selectedApiDataTemplate && (() => {
const template = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!template?.env_info) return null
// 过滤掉 OPENAI_API_KEY,系统会自动注入用户的 LiteLLM 密钥
const required = Object.entries(template.env_info.required || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
const optional = Object.entries(template.env_info.optional || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
return (
<div className="border rounded-lg p-4 space-y-3 bg-muted/30">
<h4 className="font-medium text-sm">{t("连接配置", "Connection Config")}</h4>
<p className="text-xs text-muted-foreground">
{t("注意:API密钥由系统自动注入,无需填写", "Note: API key is automatically injected by system, no need to fill in")}
</p>
{/* 只渲染后端返回的必填字段(排除 OPENAI_API_KEY) */}
{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>
))}
{/* 只渲染后端返回的可选字段(排除 OPENAI_API_KEY) */}
{optional.length > 0 && (
<>
<div className="border-t pt-2 mt-2">
<p className="text-xs text-muted-foreground mb-2">{t("可选配置", "Optional Config")}</p>
</div>
{optional.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm text-muted-foreground">{key}</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("模块名称", "Module Name")}</Label>
<Input
placeholder={t("输入模块名称", "Enter module 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>
<DialogFooter>
<Button variant="outline" onClick={() => setShowTemplateDialog(false)} disabled={creatingTemplate}>
{t("取消", "Cancel")}
</Button>
<Button
onClick={handleCreateTemplate}
disabled={creatingTemplate || !templateName || (templateType === "data_storage" as any && !selectedApiDataTemplate)}
>
{creatingTemplate ? t("创建中...", "Creating...") : t("创建模块", "Create Module")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DashboardLayout>
</AuthGuard>
)
}