mirror of
https://github.com/Fasthei/taiji-pda-v0.git
synced 2026-09-26 18:21:54 +00:00
feat: 增加Agent评估按钮和工具集编排功能
- 数据与工具页面:在"使用的模型"右侧添加Agent评估按钮 - 编排中心:将"创建工作流"改为下拉菜单,支持"工具集"和"工作流"两种编排方式 - 工具集功能:支持选择最多8个工具组合成工具集 - API客户端:添加工具集相关接口(getToolsets, createToolset, deleteToolset)
This commit is contained in:
+41
-24
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user