forked from xiaohei/taiji-pda-v0
- 更新 API Client createCustomAgent 方法,添加 model、agentRole、agentCapabilities 参数 - 更新模板接口解析,支持新格式 frameworkTemplates 和 dataTemplates - 添加数据存储模板选择器(mysql_agent、postgresql_agent 等) - 添加动态环境变量配置,根据模板的 env_info.required/optional 动态生成表单 - 添加 A2A 框架专用配置(Agent角色、Agent能力) - 更新表单验证和配置摘要
1341 lines
57 KiB
TypeScript
1341 lines
57 KiB
TypeScript
"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>
|
||
)
|
||
}
|