feat: 服务网关页面添加折叠功能展示Agent接口

- 为MCP、A2A、API三种网关类型添加折叠/展开按钮
- 移除固定的网关端点,每个Agent使用独立URL
- 展开后显示Agent接口列表(等待后端接口对接)
- 移除原有的"查看接口"弹窗
This commit is contained in:
zhanggangyong
2026-01-14 06:21:35 +00:00
parent 8b01653b68
commit 531dfddcda
+142 -134
View File
@@ -5,7 +5,7 @@ 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 { GitBranch, Activity, Code2, Boxes, Workflow, Plus, Upload } from "lucide-react"
import { GitBranch, Activity, Code2, Boxes, Workflow, Plus, Upload, ChevronDown, ChevronRight, Copy, ExternalLink } from "lucide-react"
import { Progress } from "@/components/ui/progress"
import { useLanguage } from "@/hooks/useLanguage"
import { TaijiAPIClient } from "@/lib/api-client"
@@ -24,6 +24,7 @@ import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
export default function ModelGatewayPage() {
const { t } = useLanguage()
@@ -38,8 +39,12 @@ export default function ModelGatewayPage() {
const [gatewayAPIs, setGatewayAPIs] = useState<any[]>([])
const [monitoring, setMonitoring] = useState<any>(null)
const [stats, setStats] = useState({ availableGateways: 0, endpoints: 0, avgLatency: 0, requestsToday: 0 })
const [showGatewayDialog, setShowGatewayDialog] = useState(false)
const [selectedGateway, setSelectedGateway] = useState<any>(null)
const [expandedGateways, setExpandedGateways] = useState<Record<string, boolean>>({})
const [gatewayAgents, setGatewayAgents] = useState<Record<string, any[]>>({
MCP: [],
A2A: [],
API: [],
})
useEffect(() => {
loadGatewayData()
@@ -125,26 +130,38 @@ export default function ModelGatewayPage() {
fullName: "Model Context Protocol",
icon: Code2,
description: "适用于需要上下文理解的复杂对话场景",
endpoint: "agentmcp.taijiaicloud.com",
},
{
name: "A2A",
fullName: "Agent-to-Agent Protocol",
icon: Boxes,
description: "适用于多Agent协同工作的场景",
endpoint: "agenta2a.taijiaicloud.com",
},
{
name: "API",
fullName: "Standard API Gateway",
icon: Workflow,
description: "标准REST API,适用于简单的请求响应场景",
endpoint: "agentapi.taijiaicloud.com",
},
]
const providersToUse = providers.length > 0 ? providers : []
const toggleGatewayExpand = (gatewayName: string) => {
setExpandedGateways(prev => ({
...prev,
[gatewayName]: !prev[gatewayName]
}))
}
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
toast({
title: t("已复制", "Copied"),
description: t("内容已复制到剪贴板", "Content copied to clipboard"),
})
}
const handleCreateAPI = async (apiData: { name: string; method: "json" | "url"; content: string }) => {
try {
const result = await TaijiAPIClient.createGatewayAPI(apiData.name, apiData.method, apiData.content)
@@ -277,37 +294,130 @@ export default function ModelGatewayPage() {
<CardContent className="space-y-4">
{gatewayTypes.map((gateway) => {
const Icon = gateway.icon
const isExpanded = expandedGateways[gateway.name] || false
const agents = gatewayAgents[gateway.name] || []
return (
<div
<Collapsible
key={gateway.name}
className="flex items-center justify-between rounded-lg border border-border bg-card p-4"
open={isExpanded}
onOpenChange={() => toggleGatewayExpand(gateway.name)}
>
<div className="flex items-center gap-4 flex-1">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<Icon className="h-6 w-6 text-primary" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-lg">{gateway.name}</h3>
<Badge className="bg-green-500/10 text-green-500 text-xs">active</Badge>
<div className="rounded-lg border border-border bg-card overflow-hidden">
{/* 主卡片区域 */}
<CollapsibleTrigger asChild>
<div className="flex items-center justify-between p-4 cursor-pointer hover:bg-muted/50 transition-colors">
<div className="flex items-center gap-4 flex-1">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<Icon className="h-6 w-6 text-primary" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-lg">{gateway.name}</h3>
<Badge className="bg-green-500/10 text-green-500 text-xs">active</Badge>
{agents.length > 0 && (
<Badge variant="outline" className="text-xs">
{agents.length} {t("个Agent", "Agents")}
</Badge>
)}
</div>
<p className="text-sm text-muted-foreground mb-1">{gateway.fullName}</p>
<p className="text-xs text-muted-foreground">{gateway.description}</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" className="gap-1">
{isExpanded ? (
<>
<ChevronDown className="h-4 w-4" />
{t("收起", "Collapse")}
</>
) : (
<>
<ChevronRight className="h-4 w-4" />
{t("展开接口", "Expand APIs")}
</>
)}
</Button>
</div>
</div>
<p className="text-sm text-muted-foreground mb-1">{gateway.fullName}</p>
<p className="text-xs text-muted-foreground">{gateway.description}</p>
</div>
</CollapsibleTrigger>
{/* 折叠内容区域 - Agent 接口列表 */}
<CollapsibleContent>
<div className="border-t border-border bg-muted/30 p-4">
{/* Agent 接口列表 */}
<div className="space-y-3">
{agents.length === 0 ? (
<div className="text-center py-6 text-muted-foreground bg-background rounded-md border border-dashed">
<p className="text-sm">
{t("暂无已部署的 Agent", "No deployed agents yet")}
</p>
<p className="text-xs mt-1">
{t("部署 Agent 后,接口信息将显示在这里", "API information will appear here after deploying agents")}
</p>
</div>
) : (
<div className="space-y-2">
{agents.map((agent: any, index: number) => (
<div
key={agent.id || index}
className="flex items-center justify-between p-3 rounded-md bg-background border border-border hover:border-primary/50 transition-colors"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-sm">{agent.name}</span>
<Badge
variant="outline"
className={`text-xs ${
agent.status === 'running'
? 'bg-green-500/10 text-green-500 border-green-500/20'
: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20'
}`}
>
{agent.status || 'unknown'}
</Badge>
</div>
<code className="text-xs text-primary font-mono break-all">
{agent.endpoint || agent.url || t("等待后端返回", "Waiting for backend")}
</code>
</div>
<div className="flex items-center gap-1 ml-2 flex-shrink-0">
<Button
variant="ghost"
size="sm"
className="h-7 px-2"
onClick={(e) => {
e.stopPropagation()
if (agent.endpoint || agent.url) {
copyToClipboard(agent.endpoint || agent.url)
}
}}
disabled={!agent.endpoint && !agent.url}
>
<Copy className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-7 px-2"
onClick={(e) => {
e.stopPropagation()
// TODO: 跳转到 Agent 详情页
}}
>
<ExternalLink className="h-3.5 w-3.5" />
</Button>
</div>
</div>
))}
</div>
)}
</div>
</div>
</CollapsibleContent>
</div>
<div className="flex items-center gap-6">
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedGateway(gateway)
setShowGatewayDialog(true)
}}
>
{t("查看接口", "View API")}
</Button>
</div>
</div>
</Collapsible>
)
})}
</CardContent>
@@ -504,108 +614,6 @@ export default function ModelGatewayPage() {
</DialogContent>
</Dialog>
{/* Gateway API Details Dialog */}
<Dialog open={showGatewayDialog} onOpenChange={setShowGatewayDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{selectedGateway?.name} {t("接口说明", "API Documentation")}</DialogTitle>
<DialogDescription>
{selectedGateway?.fullName} - {selectedGateway?.description}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* 接口地址 */}
<div className="space-y-2">
<Label>{t("接口地址", "Endpoint URL")}</Label>
<div className="rounded-md bg-muted p-3 font-mono text-sm">
https://{selectedGateway?.endpoint}
</div>
</div>
{/* 使用示例 */}
<div className="space-y-2">
<Label>{t("使用示例", "Usage Example")}</Label>
<div className="rounded-md bg-muted p-3">
<code className="text-xs whitespace-pre-wrap">
{selectedGateway?.name === "MCP" && (
<>
{`curl -X POST https://${selectedGateway?.endpoint}/v1/context \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"model": "gpt-4",
"context": "Your context here",
"messages": [
{"role": "user", "content": "Hello"}
]
}'`}
</>
)}
{selectedGateway?.name === "A2A" && (
<>
{`curl -X POST https://${selectedGateway?.endpoint}/v1/agent/invoke \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"source_agent": "agent-001",
"target_agent": "agent-002",
"task": "process_data",
"payload": {"data": "example"}
}'`}
</>
)}
{selectedGateway?.name === "API" && (
<>
{`curl -X POST https://${selectedGateway?.endpoint}/v1/chat/completions \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "Hello"}
]
}'`}
</>
)}
</code>
</div>
</div>
{/* 功能说明 */}
<div className="space-y-2">
<Label>{t("功能说明", "Features")}</Label>
<div className="text-sm text-muted-foreground space-y-1">
{selectedGateway?.name === "MCP" && (
<ul className="list-disc list-inside space-y-1">
<li>{t("支持上下文管理和会话状态保持", "Supports context management and session state persistence")}</li>
<li>{t("适用于复杂对话场景", "Suitable for complex dialogue scenarios")}</li>
<li>{t("提供模型上下文协议标准接口", "Provides Model Context Protocol standard interface")}</li>
</ul>
)}
{selectedGateway?.name === "A2A" && (
<ul className="list-disc list-inside space-y-1">
<li>{t("支持Agent间通信和任务分发", "Supports inter-agent communication and task distribution")}</li>
<li>{t("适用于多Agent协同工作场景", "Suitable for multi-agent collaboration scenarios")}</li>
<li>{t("提供Agent到Agent协议标准接口", "Provides Agent-to-Agent Protocol standard interface")}</li>
</ul>
)}
{selectedGateway?.name === "API" && (
<ul className="list-disc list-inside space-y-1">
<li>{t("兼容OpenAI API格式", "Compatible with OpenAI API format")}</li>
<li>{t("适用于简单的请求响应场景", "Suitable for simple request-response scenarios")}</li>
<li>{t("提供标准REST API接口", "Provides standard REST API interface")}</li>
</ul>
)}
</div>
</div>
</div>
<DialogFooter>
<Button onClick={() => setShowGatewayDialog(false)}>{t("关闭", "Close")}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DashboardLayout>
</AuthGuard>
)