mirror of
https://github.com/Fasthei/taiji-pda-v0.git
synced 2026-09-26 20:12:27 +00:00
- 数据与工具页面:在"使用的模型"右侧添加Agent评估按钮 - 编排中心:将"创建工作流"改为下拉菜单,支持"工具集"和"工作流"两种编排方式 - 工具集功能:支持选择最多8个工具组合成工具集 - API客户端:添加工具集相关接口(getToolsets, createToolset, deleteToolset)
646 lines
28 KiB
TypeScript
646 lines
28 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useEffect } from "react"
|
||
import { useRouter } from "next/navigation"
|
||
import { AuthGuard } from "@/components/auth-guard"
|
||
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 { 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"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
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"
|
||
|
||
export default function OrchestrationPage() {
|
||
const { t } = useLanguage()
|
||
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()
|
||
}, [router])
|
||
|
||
const loadData = async () => {
|
||
try {
|
||
setLoading(true)
|
||
// 并行加载Agent列表、工作流列表和工具集列表
|
||
const [agentsResult, workflowsResult, toolsetsResult] = await Promise.allSettled([
|
||
TaijiAPIClient.getPlatformAgents(),
|
||
TaijiAPIClient.getWorkflows(),
|
||
TaijiAPIClient.getToolsets(),
|
||
])
|
||
|
||
// 加载平台Agent列表
|
||
if (agentsResult.status === "fulfilled" && agentsResult.value?.success) {
|
||
const data = agentsResult.value.data?.data || []
|
||
setAvailableAgents(data)
|
||
}
|
||
|
||
// 加载工作流列表
|
||
if (workflowsResult.status === "fulfilled" && workflowsResult.value?.success) {
|
||
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 {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
// 加载可用工具列表(用于创建工具集)
|
||
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])
|
||
}
|
||
}
|
||
|
||
const removeNode = (index: number) => {
|
||
setWorkflowNodes(workflowNodes.filter((_, i) => i !== index))
|
||
}
|
||
|
||
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("编排中心", "Orchestration Hub")}</h1>
|
||
<p className="text-muted-foreground mt-1">
|
||
{t("创建和管理Agent工作流(最多3个节点)", "Create and manage agent workflows (max 3 nodes)")}
|
||
</p>
|
||
</div>
|
||
<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">
|
||
<Card>
|
||
<CardHeader className="pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||
{t("总工作流", "Total Workflows")}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">
|
||
{loading ? (
|
||
<span className="inline-block h-7 w-8 animate-pulse bg-muted rounded" />
|
||
) : (
|
||
workflows.length
|
||
)}
|
||
</div>
|
||
<p className="text-xs text-muted-foreground mt-1">{t("已创建", "Created")}</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader className="pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">{t("运行中", "Running")}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">
|
||
{loading ? (
|
||
<span className="inline-block h-7 w-4 animate-pulse bg-muted rounded" />
|
||
) : (
|
||
workflows.filter((w) => w.status === "running").length
|
||
)}
|
||
</div>
|
||
<p className="text-xs text-muted-foreground mt-1">{t("活跃工作流", "Active workflows")}</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader className="pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||
{t("最大节点数", "Max Nodes")}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">3</div>
|
||
<p className="text-xs text-muted-foreground mt-1">{t("每个工作流", "Per workflow")}</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader className="pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||
{t("可用Agent", "Available Agents")}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">
|
||
{loading ? (
|
||
<span className="inline-block h-7 w-8 animate-pulse bg-muted rounded" />
|
||
) : (
|
||
availableAgents.length
|
||
)}
|
||
</div>
|
||
<p className="text-xs text-muted-foreground mt-1">{t("平台和自定义", "Platform & Custom")}</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<div className="grid gap-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>{t("我的工具集", "My Toolsets")}</CardTitle>
|
||
<CardDescription>{t("管理和执行Agent工作流", "Manage and execute agent workflows")}</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{loading ? (
|
||
<div className="space-y-4">
|
||
{[1, 2].map((i) => (
|
||
<div key={i} className="h-20 animate-pulse bg-muted rounded-lg" />
|
||
))}
|
||
</div>
|
||
) : workflows.length === 0 ? (
|
||
<div className="text-center py-8 text-muted-foreground">
|
||
{t("暂无工作流", "No workflows yet")}
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{workflows.map((workflow) => (
|
||
<div
|
||
key={workflow.id}
|
||
className="flex items-center justify-between rounded-lg border border-border bg-card p-4"
|
||
>
|
||
<div className="flex items-center gap-4 flex-1">
|
||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||
<Network className="h-5 w-5 text-primary" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<h3 className="font-semibold">{workflow.name}</h3>
|
||
<Badge
|
||
className={
|
||
workflow.status === "running"
|
||
? "bg-green-500/10 text-green-500"
|
||
: "bg-gray-500/10 text-gray-500"
|
||
}
|
||
>
|
||
{workflow.status}
|
||
</Badge>
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">
|
||
{t("节点数", "Nodes")}: {workflow.nodes.length}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Button variant="outline" size="sm" className="gap-2 bg-transparent">
|
||
<Play className="h-4 w-4" />
|
||
{t("运行", "Run")}
|
||
</Button>
|
||
<Button variant="ghost" size="icon">
|
||
<Trash2 className="h-4 w-4 text-destructive" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<div className="grid gap-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>{t("工作流限制", "Workflow Limitations")}</CardTitle>
|
||
<CardDescription>{t("当前系统限制说明", "Current system limitations")}</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
<div className="flex items-center justify-between rounded-lg bg-muted/30 p-3">
|
||
<span className="text-sm font-medium">{t("最大Agent节点数", "Max Agent Nodes")}</span>
|
||
<Badge variant="outline">3</Badge>
|
||
</div>
|
||
<div className="flex items-center justify-between rounded-lg bg-muted/30 p-3">
|
||
<span className="text-sm font-medium">{t("支持的Agent类型", "Supported Agent Types")}</span>
|
||
<Badge variant="outline">{t("平台和自定义", "Platform & Custom")}</Badge>
|
||
</div>
|
||
<div className="flex items-center justify-between rounded-lg bg-muted/30 p-3">
|
||
<span className="text-sm font-medium">{t("并发执行", "Concurrent Execution")}</span>
|
||
<Badge variant="outline">{t("不支持", "Not Supported")}</Badge>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 创建工作流对话框 */}
|
||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||
<DialogContent className="max-w-3xl">
|
||
<DialogHeader>
|
||
<DialogTitle>{t("创建新工作流", "Create New Workflow")}</DialogTitle>
|
||
<DialogDescription>
|
||
{t("选择最多3个Agent来构建工作流", "Select up to 3 agents to build your workflow")}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label>{t("工作流名称", "Workflow Name")}</Label>
|
||
<Input
|
||
placeholder={t("输入工作流名称", "Enter workflow name")}
|
||
value={workflowName}
|
||
onChange={(e) => setWorkflowName(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label>{t("选择服务网关", "Select Service Gateway")}</Label>
|
||
<div className="grid grid-cols-3 gap-3">
|
||
{[
|
||
{ id: "mcp", name: "MCP", desc: t("模型上下文协议", "Model Context Protocol") },
|
||
{ id: "a2a", name: "A2A", desc: t("Agent到Agent通信", "Agent-to-Agent Communication") },
|
||
{ id: "api", name: "API", desc: t("标准REST API", "Standard REST API") },
|
||
].map((gateway) => (
|
||
<button
|
||
key={gateway.id}
|
||
onClick={() => setSelectedGateway(gateway.id)}
|
||
className={`p-4 rounded-lg border text-left transition-all ${
|
||
selectedGateway === gateway.id
|
||
? "border-primary bg-primary/5 shadow-sm"
|
||
: "border-border hover:border-primary/50 hover:bg-muted/50"
|
||
}`}
|
||
>
|
||
<div className="font-semibold mb-1">{gateway.name}</div>
|
||
<div className="text-xs text-muted-foreground">{gateway.desc}</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label>{t("选择Agent(最多3个)", "Select Agents (max 3)")}</Label>
|
||
{availableAgents.length === 0 ? (
|
||
<div className="text-center py-8 text-muted-foreground border border-dashed rounded-lg">
|
||
{t("暂无可用Agent", "No agents available")}
|
||
</div>
|
||
) : (
|
||
<div className="grid grid-cols-2 gap-2 max-h-64 overflow-y-auto p-1">
|
||
{availableAgents.map((agent) => (
|
||
<button
|
||
key={agent.id}
|
||
onClick={() => addNode(agent.id)}
|
||
disabled={workflowNodes.length >= 3 || workflowNodes.includes(agent.id)}
|
||
className="flex items-center gap-3 p-3 rounded-lg border border-border hover:border-primary hover:bg-primary/5 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-left"
|
||
>
|
||
<span className="text-2xl">{agent.icon}</span>
|
||
<div className="flex-1 min-w-0">
|
||
<p className="text-sm font-medium truncate">{agent.name}</p>
|
||
{agent.custom && (
|
||
<Badge variant="secondary" className="text-xs mt-1">
|
||
{t("自定义", "Custom")}
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label>{t("工作流结构", "Workflow Structure")}</Label>
|
||
<div className="rounded-lg border border-border bg-muted/30 p-4 min-h-[120px]">
|
||
{workflowNodes.length === 0 ? (
|
||
<p className="text-sm text-muted-foreground text-center py-8">
|
||
{t("请从上方选择Agent添加到工作流", "Select agents from above to add to workflow")}
|
||
</p>
|
||
) : (
|
||
<div className="flex items-center justify-center gap-2">
|
||
{workflowNodes.map((nodeId, index) => {
|
||
const agent = availableAgents.find((a) => a.id === nodeId)
|
||
return (
|
||
<div key={index} className="flex items-center gap-2">
|
||
<div className="relative group">
|
||
<div className="flex items-center gap-2 bg-primary/10 border border-primary/20 rounded-lg px-3 py-2">
|
||
<span className="text-xl">{agent?.icon}</span>
|
||
<span className="text-sm font-medium">{agent?.name}</span>
|
||
</div>
|
||
<button
|
||
onClick={() => removeNode(index)}
|
||
className="absolute -top-2 -right-2 h-5 w-5 rounded-full bg-destructive text-destructive-foreground opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-xs"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
{index < workflowNodes.length - 1 && <ArrowRight className="h-5 w-5 text-primary" />}
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
{t("已选择", "Selected")}: {workflowNodes.length}/3
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setShowCreateDialog(false)}>
|
||
{t("取消", "Cancel")}
|
||
</Button>
|
||
<Button
|
||
onClick={async () => {
|
||
if (workflowNodes.length === 0 || !workflowName || !selectedGateway) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
const result = await TaijiAPIClient.createWorkflow({
|
||
name: workflowName,
|
||
gateway: selectedGateway as "MCP" | "A2A" | "API",
|
||
nodes: workflowNodes.map((nodeId, index) => {
|
||
const agent = availableAgents.find((a) => a.id === nodeId)
|
||
return {
|
||
agentId: nodeId,
|
||
agentType: agent?.type === "custom" ? "custom" : "platform",
|
||
agentName: agent?.name || "",
|
||
order: index + 1,
|
||
}
|
||
}),
|
||
})
|
||
|
||
if (result?.success) {
|
||
toast({
|
||
title: t("创建成功", "Success"),
|
||
description: t("工作流已创建", "Workflow created"),
|
||
})
|
||
setShowCreateDialog(false)
|
||
setWorkflowNodes([])
|
||
setWorkflowName("")
|
||
setSelectedGateway("")
|
||
loadData()
|
||
} else {
|
||
throw new Error(result?.message || "Failed to create workflow")
|
||
}
|
||
} catch (error: any) {
|
||
toast({
|
||
title: t("创建失败", "Failed"),
|
||
description: error.message || t("无法创建工作流", "Failed to create workflow"),
|
||
variant: "destructive",
|
||
})
|
||
}
|
||
}}
|
||
disabled={workflowNodes.length === 0 || !workflowName || !selectedGateway}
|
||
>
|
||
<Save className="h-4 w-4 mr-2" />
|
||
{t("保存工作流", "Save Workflow")}
|
||
</Button>
|
||
</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>
|
||
)
|
||
}
|