feat: 增加Agent评估按钮和工具集编排功能

- 数据与工具页面:在"使用的模型"右侧添加Agent评估按钮
- 编排中心:将"创建工作流"改为下拉菜单,支持"工具集"和"工作流"两种编排方式
- 工具集功能:支持选择最多8个工具组合成工具集
- API客户端:添加工具集相关接口(getToolsets, createToolset, deleteToolset)
This commit is contained in:
zhanggangyong
2026-01-22 10:23:38 +00:00
parent a7299b1fa9
commit 127045e7b1
3 changed files with 321 additions and 32 deletions
+41 -24
View File
@@ -1083,31 +1083,48 @@ export default function DataToolsPage() {
</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="__loading__" disabled>
{t("加载中...", "Loading...")}
</SelectItem>
) : (
availableModels.map((model) => (
<SelectItem key={model.id} value={model.id || model.name}>
<div className="flex flex-col">
<span>{model.name}</span>
{model.description && (
<span className="text-xs text-muted-foreground">{model.description}</span>
)}
</div>
<div className="grid grid-cols-2 gap-4">
<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="__loading__" disabled>
{t("加载中...", "Loading...")}
</SelectItem>
))
)}
</SelectContent>
</Select>
) : (
availableModels.map((model) => (
<SelectItem key={model.id} value={model.id || model.name}>
<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="space-y-2">
<Label>{t("Agent评估", "Agent Evaluation")}</Label>
<Button
variant="outline"
className="w-full justify-start gap-2"
onClick={() => alert(t("Agent评估功能即将上线", "Agent Evaluation feature coming soon"))}
>
<Bot className="h-4 w-4" />
{t("配置评估", "Configure Evaluation")}
</Button>
<p className="text-xs text-muted-foreground">
{t("评估Agent的性能和准确性", "Evaluate agent performance and accuracy")}
</p>
</div>
</div>
<div className="rounded-lg bg-muted p-4 text-sm">
+234 -8
View File
@@ -7,7 +7,8 @@ import { DashboardLayout } from "@/components/dashboard-layout"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Network, Plus, Play, Save, Trash2, ArrowRight } from "lucide-react"
import { Checkbox } from "@/components/ui/checkbox"
import { Network, Plus, Play, Save, Trash2, ArrowRight, Wrench, Workflow, ChevronDown } from "lucide-react"
import { useLanguage } from "@/hooks/useLanguage"
import { TaijiAPIClient } from "@/lib/api-client"
import { useToast } from "@/hooks/use-toast"
@@ -19,6 +20,12 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
@@ -27,12 +34,20 @@ export default function OrchestrationPage() {
const { toast } = useToast()
const router = useRouter()
const [showCreateDialog, setShowCreateDialog] = useState(false)
const [showToolsetDialog, setShowToolsetDialog] = useState(false)
const [workflowNodes, setWorkflowNodes] = useState<string[]>([])
const [workflowName, setWorkflowName] = useState("")
const [selectedGateway, setSelectedGateway] = useState<string>("")
const [loading, setLoading] = useState(true)
const [availableAgents, setAvailableAgents] = useState<any[]>([])
const [workflows, setWorkflows] = useState<any[]>([])
// 工具集相关状态
const [toolsetName, setToolsetName] = useState("")
const [selectedTools, setSelectedTools] = useState<string[]>([])
const [availableTools, setAvailableTools] = useState<any[]>([])
const [toolsLoading, setToolsLoading] = useState(false)
const [toolsets, setToolsets] = useState<any[]>([])
useEffect(() => {
loadData()
@@ -41,10 +56,11 @@ export default function OrchestrationPage() {
const loadData = async () => {
try {
setLoading(true)
// 并行加载Agent列表和工作流列表
const [agentsResult, workflowsResult] = await Promise.allSettled([
// 并行加载Agent列表、工作流列表和工具集列表
const [agentsResult, workflowsResult, toolsetsResult] = await Promise.allSettled([
TaijiAPIClient.getPlatformAgents(),
TaijiAPIClient.getWorkflows(),
TaijiAPIClient.getToolsets(),
])
// 加载平台Agent列表
@@ -58,6 +74,12 @@ export default function OrchestrationPage() {
const data = workflowsResult.value.data?.data || workflowsResult.value.data || []
setWorkflows(data)
}
// 加载工具集列表
if (toolsetsResult.status === "fulfilled" && toolsetsResult.value?.success) {
const data = toolsetsResult.value.data?.data || toolsetsResult.value.data || []
setToolsets(data)
}
} catch (error) {
console.error("Failed to load data:", error)
} finally {
@@ -65,6 +87,73 @@ export default function OrchestrationPage() {
}
}
// 加载可用工具列表(用于创建工具集)
const loadAvailableTools = async () => {
try {
setToolsLoading(true)
const result = await TaijiAPIClient.getUserTools()
if (result?.success && result.data?.tools) {
setAvailableTools(result.data.tools)
} else if (Array.isArray(result)) {
setAvailableTools(result)
}
} catch (error) {
console.error("Failed to load tools:", error)
} finally {
setToolsLoading(false)
}
}
// 打开工具集对话框
const openToolsetDialog = () => {
setShowToolsetDialog(true)
setToolsetName("")
setSelectedTools([])
loadAvailableTools()
}
// 切换工具选择
const toggleToolSelection = (toolId: string) => {
if (selectedTools.includes(toolId)) {
setSelectedTools(selectedTools.filter(id => id !== toolId))
} else if (selectedTools.length < 8) {
setSelectedTools([...selectedTools, toolId])
}
}
// 保存工具集
const handleSaveToolset = async () => {
if (!toolsetName || selectedTools.length === 0) {
return
}
try {
const result = await TaijiAPIClient.createToolset({
name: toolsetName,
tools: selectedTools,
})
if (result?.success) {
toast({
title: t("创建成功", "Success"),
description: t("工具集已创建", "Toolset created"),
})
setShowToolsetDialog(false)
setToolsetName("")
setSelectedTools([])
loadData()
} else {
throw new Error(result?.message || "Failed to create toolset")
}
} catch (error: any) {
toast({
title: t("创建失败", "Failed"),
description: error.message || t("无法创建工具集", "Failed to create toolset"),
variant: "destructive",
})
}
}
const addNode = (agentId: string) => {
if (workflowNodes.length < 3 && !workflowNodes.includes(agentId)) {
setWorkflowNodes([...workflowNodes, agentId])
@@ -86,10 +175,25 @@ export default function OrchestrationPage() {
{t("创建和管理Agent工作流(最多3个节点)", "Create and manage agent workflows (max 3 nodes)")}
</p>
</div>
<Button onClick={() => setShowCreateDialog(true)} className="gap-2">
<Plus className="h-4 w-4" />
{t("创建工作流", "Create Workflow")}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2">
<Plus className="h-4 w-4" />
{t("编排", "Orchestrate")}
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={openToolsetDialog} className="gap-2 cursor-pointer">
<Wrench className="h-4 w-4" />
{t("工具集", "Toolset")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setShowCreateDialog(true)} className="gap-2 cursor-pointer">
<Workflow className="h-4 w-4" />
{t("工作流", "Workflow")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="grid gap-4 md:grid-cols-4">
@@ -161,7 +265,7 @@ export default function OrchestrationPage() {
<div className="grid gap-4">
<Card>
<CardHeader>
<CardTitle>{t("我的工作流", "My Workflows")}</CardTitle>
<CardTitle>{t("我的工具集", "My Toolsets")}</CardTitle>
<CardDescription>{t("管理和执行Agent工作流", "Manage and execute agent workflows")}</CardDescription>
</CardHeader>
<CardContent>
@@ -412,6 +516,128 @@ export default function OrchestrationPage() {
</DialogFooter>
</DialogContent>
</Dialog>
{/* 创建工具集对话框 */}
<Dialog open={showToolsetDialog} onOpenChange={setShowToolsetDialog}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>{t("创建工具集", "Create Toolset")}</DialogTitle>
<DialogDescription>
{t("选择最多8个工具来构建工具集", "Select up to 8 tools to build your toolset")}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>{t("工具集名称", "Toolset Name")}</Label>
<Input
placeholder={t("输入工具集名称", "Enter toolset name")}
value={toolsetName}
onChange={(e) => setToolsetName(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label>{t("选择工具(最多8个)", "Select Tools (max 8)")}</Label>
{toolsLoading ? (
<div className="text-center py-8 text-muted-foreground border border-dashed rounded-lg">
{t("加载中...", "Loading...")}
</div>
) : availableTools.length === 0 ? (
<div className="text-center py-8 text-muted-foreground border border-dashed rounded-lg">
{t("暂无可用工具,请先在数据与工具页面创建工具", "No tools available, please create tools in Data & Tools page first")}
</div>
) : (
<div className="grid grid-cols-2 gap-3 max-h-80 overflow-y-auto p-1">
{availableTools.map((tool) => {
const isSelected = selectedTools.includes(tool.id || tool.name)
const isDisabled = !isSelected && selectedTools.length >= 8
return (
<div
key={tool.id || tool.name}
onClick={() => !isDisabled && toggleToolSelection(tool.id || tool.name)}
className={`flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-all ${
isSelected
? "border-primary bg-primary/5 shadow-sm"
: isDisabled
? "border-border opacity-50 cursor-not-allowed"
: "border-border hover:border-primary/50 hover:bg-muted/50"
}`}
>
<Checkbox
checked={isSelected}
disabled={isDisabled}
className="mt-1"
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<Wrench className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium truncate">{tool.name}</p>
</div>
{tool.description && (
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
{tool.description}
</p>
)}
{tool.template && (
<Badge variant="secondary" className="text-xs mt-2">
{tool.template}
</Badge>
)}
</div>
</div>
)
})}
</div>
)}
<p className="text-xs text-muted-foreground">
{t("已选择", "Selected")}: {selectedTools.length}/8
</p>
</div>
{selectedTools.length > 0 && (
<div className="space-y-2">
<Label>{t("已选工具", "Selected Tools")}</Label>
<div className="rounded-lg border border-border bg-muted/30 p-4">
<div className="flex flex-wrap gap-2">
{selectedTools.map((toolId) => {
const tool = availableTools.find((t) => (t.id || t.name) === toolId)
return (
<div
key={toolId}
className="flex items-center gap-2 bg-primary/10 border border-primary/20 rounded-lg px-3 py-1.5 group"
>
<Wrench className="h-3 w-3 text-primary" />
<span className="text-sm font-medium">{tool?.name || toolId}</span>
<button
onClick={() => toggleToolSelection(toolId)}
className="h-4 w-4 rounded-full bg-destructive/80 text-destructive-foreground opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-xs hover:bg-destructive"
>
×
</button>
</div>
)
})}
</div>
</div>
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowToolsetDialog(false)}>
{t("取消", "Cancel")}
</Button>
<Button
onClick={handleSaveToolset}
disabled={selectedTools.length === 0 || !toolsetName}
>
<Save className="h-4 w-4 mr-2" />
{t("保存工具集", "Save Toolset")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</DashboardLayout>
</AuthGuard>
+46
View File
@@ -974,6 +974,52 @@ export class TaijiAPIClient {
return handleResponse(response)
}
// ==================== 工具集 API ====================
/**
* 获取工具集列表
* GET /api/user/toolsets/list
*/
static async getToolsets() {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/toolsets/list`, {
headers: buildHeaders(),
})
return handleResponse(response)
}
/**
* 创建工具集
* POST /api/user/toolsets/create
*
* @param data - 工具集数据
*/
static async createToolset(data: {
name: string
description?: string
tools: string[]
}) {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/toolsets/create`, {
method: "POST",
headers: buildHeaders(),
body: JSON.stringify(data),
})
return handleResponse(response)
}
/**
* 删除工具集
* DELETE /api/user/toolsets/{toolset_id}
*
* @param toolsetId - 工具集ID
*/
static async deleteToolset(toolsetId: string) {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/toolsets/${toolsetId}`, {
method: "DELETE",
headers: buildHeaders(),
})
return handleResponse(response)
}
/**
* 获取计费仪表板综合数据
* GET /api/user/dashboard/billing-overview