feat: 根据后端接口更新自定义Agent创建功能

- 更新 API Client createCustomAgent 方法,添加 model、agentRole、agentCapabilities 参数
- 更新模板接口解析,支持新格式 frameworkTemplates 和 dataTemplates
- 添加数据存储模板选择器(mysql_agent、postgresql_agent 等)
- 添加动态环境变量配置,根据模板的 env_info.required/optional 动态生成表单
- 添加 A2A 框架专用配置(Agent角色、Agent能力)
- 更新表单验证和配置摘要
This commit is contained in:
zhanggangyong
2026-01-13 12:51:04 +00:00
parent bd3a72ce5e
commit 5ee8c4736f
2 changed files with 217 additions and 44 deletions
+195 -42
View File
@@ -61,6 +61,13 @@ export default function DataToolsPage() {
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)
@@ -82,12 +89,11 @@ export default function DataToolsPage() {
const loadFrameworkTemplates = async () => {
try {
const result = await TaijiAPIClient.getCustomAgentTemplates()
if (result?.success && result.data?.templates) {
// 文档返回的是字符串数组:["A2A", "langchain", "MCP"]
// 转换为对象数组以便显示
const templates = result.data.templates.map((template: string | any) => {
if (typeof template === 'string') {
// 如果是字符串,转换为对象
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框架',
@@ -98,11 +104,34 @@ export default function DataToolsPage() {
displayName: displayNames[template] || template,
description: ''
}
}
// 如果已经是对象,直接返回(兼容旧格式)
return template
})
setFrameworkTemplates(templates)
})
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)
@@ -350,8 +379,9 @@ export default function DataToolsPage() {
// 部署Agent(创建自定义Agent)
const handleDeployAgent = async () => {
if (!selectedFramework || !toolName) {
alert(t("请填写所有必填字段", "Please fill in all required fields"))
// 校验必填字段
if (!selectedApiDataTemplate || !toolName) {
alert(t("请选择数据存储模板并填写Agent名称", "Please select a data template and enter agent name"))
return
}
@@ -374,22 +404,40 @@ export default function DataToolsPage() {
}
}
// 使用创建自定义Agent接口 POST /api/user/custom-agents
// 根据文档:template是必填的模板名称,frameworkTemplate是框架类型(可选)
const frameworkName = selectedFramework.toUpperCase() // MCP/A2A/langchain
const templateName = `${frameworkName.toLowerCase()}-agent` // 生成模板名称,如 "mcp-agent"
// 框架类型(默认MCP)
const frameworkName = selectedFramework || "MCP"
const result = await TaijiAPIClient.createCustomAgent({
// 构建请求参数
const requestData: any = {
name: toolName,
template: templateName, // 必填:模板名称
template: selectedApiDataTemplate, // 必填:数据存储模板(如 mysql_agent)
frameworkTemplate: frameworkName, // 可选:框架类型(A2A/langchain/MCP)
description: toolName, // 可选:Agent描述
cpuRequest: cpuRequest, // 必填:CPU请求量(如"500m")
cpuRequest: cpuRequest, // 必填:CPU请求量
cpuLimit: cpuLimit, // 可选:CPU限制量
memoryRequest: memoryRequest, // 必填:内存请求量(如"1Gi")
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"))
@@ -398,6 +446,10 @@ export default function DataToolsPage() {
setSelectedFramework("")
setSelectedServiceGateway("")
setSelectedDataTemplate("")
setSelectedApiDataTemplate("")
setEnvConfig({})
setAgentRole("")
setAgentCapabilities("")
setToolName("")
setPodConfig({
agentCount: 1,
@@ -790,10 +842,92 @@ export default function DataToolsPage() {
<div className="space-y-4">
<div className="space-y-2">
<Label>{t("Agent框架模板", "Agent Framework Template")}</Label>
<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("选择框架模板", "Select framework template")} />
<SelectValue placeholder={t("选择框架类型(默认MCP)", "Select framework type (default MCP)")} />
</SelectTrigger>
<SelectContent>
{frameworkTemplates.length === 0 ? (
@@ -805,7 +939,7 @@ export default function DataToolsPage() {
<SelectItem key={template.name} value={template.name}>
<div className="flex items-center gap-2">
<Code2 className="h-4 w-4" />
{template.displayName || template.name} - {template.description || ""}
{template.displayName || template.name}
</div>
</SelectItem>
))
@@ -813,18 +947,35 @@ export default function DataToolsPage() {
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("根据选择的框架注册相应的工具", "Register tools according to selected framework")}
{t("选择Agent运行框架,默认为MCP", "Select agent runtime framework, defaults to MCP")}
</p>
</div>
<div className="space-y-2">
<Label>{t("工具名称", "Tool Name")}</Label>
<Input
placeholder={t("输入工具名称", "Enter tool name")}
value={toolName}
onChange={(e) => setToolName(e.target.value)}
/>
</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">
@@ -953,13 +1104,13 @@ export default function DataToolsPage() {
<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("框架", "Framework")}: {selectedFramework || t("未选择", "Not selected")}
{t("Agent名称", "Agent Name")}: {toolName || t("未填写", "Not filled")}
</div>
<div className="col-span-2">
{t("网关", "Gateway")}: {selectedServiceGateway || t("未选择", "Not selected")}
{t("数据存储模板", "Data Template")}: {selectedApiDataTemplate || t("未选择", "Not selected")}
</div>
<div>
{t("Agent数量", "Agent Count")}: {podConfig.agentCount}
<div className="col-span-2">
{t("框架", "Framework")}: {selectedFramework || "MCP"}
</div>
<div>
{t("CPU", "CPU")}: {podConfig.cpuCores} {t("核", "cores")}
@@ -967,12 +1118,14 @@ export default function DataToolsPage() {
<div>
{t("内存", "Memory")}: {podConfig.memoryGB}GB
</div>
<div>
{t("最大扩展", "Max Scale")}: {podConfig.maxAgents}
</div>
<div>
{t("模型", "Model")}: {podConfig.model}
</div>
{selectedFramework === "A2A" && agentRole && (
<div className="col-span-2">
{t("Agent角色", "Agent Role")}: {agentRole}
</div>
)}
</div>
</div>
</div>
@@ -982,7 +1135,7 @@ export default function DataToolsPage() {
<Button variant="outline" onClick={() => setShowNewToolDialog(false)} disabled={deploying}>
{t("取消", "Cancel")}
</Button>
<Button onClick={handleDeployAgent} disabled={deploying || !selectedFramework || !toolName}>
<Button onClick={handleDeployAgent} disabled={deploying || !selectedApiDataTemplate || !toolName}>
{deploying ? t("部署中...", "Deploying...") : t("部署Agent", "Deploy Agent")}
</Button>
</DialogFooter>
+22 -2
View File
@@ -685,20 +685,40 @@ export class TaijiAPIClient {
* 创建租户自定义的Agent,将使用渠道为该租户分配的CPU和内存资源配额
*
* 注意:后端实际路径为 /api/user/custom-agents(非 /api/user/agents/custom/create)
*
* @param data.name - Agent名称(小写字母、数字、连字符)
* @param data.template - 数据存储模板(如 "mysql_agent"、"postgresql_agent")
* @param data.frameworkTemplate - 框架类型("MCP" | "A2A" | "langchain"),默认 "MCP"
* @param data.description - Agent描述
* @param data.cpuRequest - CPU请求量(如 "500m")
* @param data.cpuLimit - CPU限制量
* @param data.memoryRequest - 内存请求量(如 "1Gi")
* @param data.memoryLimit - 内存限制量
* @param data.tools - 工具ID列表(UUID)
* @param data.model - 模型名称(如 "gpt-4")
* @param data.endpoint - 自定义终结点
* @param data.apiKey - API密钥
* @param data.envConfig - 环境变量配置(数据库连接等)
* @param data.agentRole - A2A框架专用:Agent角色(如 "data_analyzer")
* @param data.agentCapabilities - A2A框架专用:Agent能力列表
*/
static async createCustomAgent(data: {
name: string
template?: string
frameworkTemplate?: string
template: string // 必填:数据存储模板
frameworkTemplate?: string // 可选:框架类型
description?: string
cpuRequest: string
cpuLimit?: string
memoryRequest: string
memoryLimit?: string
tools?: string[]
model?: string // 新增:模型名称
endpoint?: string
apiKey?: string
envConfig?: Record<string, string>
// A2A 框架专用(新增)
agentRole?: string
agentCapabilities?: string[]
}) {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/custom-agents`, {
method: "POST",