Files
taiji-pda-v0/components/dashboard-layout.tsx
T
xiaohei 23c0b4e930 feat: 完善超级管理员控制台API集成
- 删除资源管理中的假数据,改为从API获取
- 添加删除Agent和模型供应商功能
- 完善创建渠道、资源管理、审批申请等功能的API集成
- 修复计费统计功能,接入后端API
- 添加缺失API接口清单文档
- 优化API响应格式处理,支持多种响应结构
- 修复审批申请对话框,添加供应商申请审批功能
2025-12-25 09:31:26 +00:00

287 lines
12 KiB
TypeScript

"use client"
import type React from "react"
import { useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { useLanguage } from "@/contexts/language-context"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import {
LayoutDashboard,
Database,
GitBranch,
Boxes,
Network,
CreditCard,
ChevronLeft,
Bell,
Settings,
Activity,
Zap,
Globe,
LogOut,
User,
Key,
Copy,
RefreshCw,
} from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const navigationItems = [
{ nameKey: { zh: "概览", en: "Overview" }, href: "/", icon: LayoutDashboard },
{ nameKey: { zh: "服务网关", en: "Service Gateway" }, href: "/model-gateway", icon: GitBranch },
{ nameKey: { zh: "数据与工具", en: "Data & Tools" }, href: "/data-tools", icon: Database },
{ nameKey: { zh: "代理工厂", en: "Agent Factory" }, href: "/agent-factory", icon: Boxes },
{ nameKey: { zh: "编排中心", en: "Orchestration Hub" }, href: "/orchestration", icon: Network },
{ nameKey: { zh: "计费与资源", en: "Billing & Resources" }, href: "/billing", icon: CreditCard },
]
export function DashboardLayout({ children }: { children: React.ReactNode }) {
const [collapsed, setCollapsed] = useState(false)
const pathname = usePathname()
const { language, setLanguage, t } = useLanguage()
const [showApiKeyDialog, setShowApiKeyDialog] = useState(false)
// 使用固定的初始值,避免服务器端和客户端不一致
const [apiKey, setApiKey] = useState("sk_live_1234567890abcdefghijklmnopqrstuvwxyz")
const [serviceEndpoint, setServiceEndpoint] = useState("https://api.taiji-ai.com/v1")
const copyToClipboard = (text: string) => {
if (typeof window !== "undefined" && navigator.clipboard) {
navigator.clipboard.writeText(text)
}
}
const regenerateApiKey = () => {
// 只在客户端执行,确保服务器端和客户端一致
if (typeof window !== "undefined") {
const newKey = `sk_live_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`
setApiKey(newKey)
}
}
return (
<div className="flex h-screen bg-background">
{/* Sidebar */}
<aside
className={cn(
"flex flex-col border-r border-border bg-card transition-all duration-300",
collapsed ? "w-16" : "w-64",
)}
>
{/* Logo */}
<div className="flex h-16 items-center justify-between border-b border-border px-4">
{!collapsed && (
<Link href="/" className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary">
<Zap className="h-5 w-5 text-primary-foreground" />
</div>
<span className="text-lg font-semibold">Taiji AI</span>
</Link>
)}
<Button
variant="ghost"
size="icon"
onClick={() => setCollapsed(!collapsed)}
className={cn("h-8 w-8", collapsed && "mx-auto")}
>
<ChevronLeft className={cn("h-4 w-4 transition-transform", collapsed && "rotate-180")} />
</Button>
</div>
{/* Navigation */}
<nav className="flex-1 space-y-1 p-2 overflow-y-auto">
{navigationItems.map((item) => {
const isActive = pathname === item.href
return (
<Link key={item.href} href={item.href}>
<div
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
)}
>
<item.icon className="h-5 w-5 shrink-0" />
{!collapsed && <span>{t(item.nameKey.zh, item.nameKey.en)}</span>}
</div>
</Link>
)
})}
</nav>
{/* Footer */}
<div className="border-t border-border p-2">
<Button variant="ghost" className={cn("w-full justify-start", collapsed && "justify-center px-0")}>
<Settings className="h-5 w-5 shrink-0" />
{!collapsed && <span className="ml-3">{t("设置", "Settings")}</span>}
</Button>
</div>
</aside>
{/* Main Content */}
<div className="flex flex-1 flex-col overflow-hidden">
{/* Top Bar */}
<header className="flex h-16 items-center justify-between border-b border-border bg-card px-6">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Activity className="h-4 w-4 text-green-500" />
<span className="text-sm text-muted-foreground">{t("API 延迟", "API Latency")}: 45ms</span>
</div>
<div className="h-4 w-px bg-border" />
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500" />
<span className="text-sm text-muted-foreground">{t("所有系统正常运行", "All Systems Operational")}</span>
</div>
</div>
<div className="flex items-center gap-3">
{/* Language Switcher */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="gap-2">
<Globe className="h-4 w-4" />
<span className="hidden sm:inline">{language === "zh" ? "中文" : "English"}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setLanguage("zh")}>中文</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLanguage("en")}>English</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Badge variant="outline" className="gap-2">
<Zap className="h-3 w-3" />
<span className="font-mono">2,487 EU</span>
</Badge>
<Button variant="ghost" size="icon" className="relative">
<Bell className="h-5 w-5" />
<span className="absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" />
</Button>
{/* User Menu with Logout */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="rounded-full">
<Avatar className="h-8 w-8">
<AvatarFallback className="bg-primary text-primary-foreground text-xs">AD</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem>
<User className="mr-2 h-4 w-4" />
{t("个人资料", "Profile")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setShowApiKeyDialog(true)}>
<Key className="mr-2 h-4 w-4" />
{t("密钥管理", "API Keys")}
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
{t("设置", "Settings")}
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/login" className="flex items-center">
<LogOut className="mr-2 h-4 w-4" />
{t("退出登录", "Sign Out")}
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
{/* Page Content */}
<main className="flex-1 overflow-y-auto p-6">{children}</main>
</div>
<Dialog open={showApiKeyDialog} onOpenChange={setShowApiKeyDialog}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>{t("密钥管理", "API Key Management")}</DialogTitle>
<DialogDescription>
{t("查看和管理您的API密钥和服务终结点", "View and manage your API keys and service endpoints")}
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
{/* Service Endpoint */}
<div className="space-y-2">
<Label>{t("服务终结点", "Service Endpoint")}</Label>
<div className="flex gap-2">
<Input value={serviceEndpoint} readOnly className="font-mono text-sm" />
<Button
variant="outline"
size="icon"
onClick={() => copyToClipboard(serviceEndpoint)}
title={t("复制", "Copy")}
>
<Copy className="h-4 w-4" />
</Button>
</div>
<p className="text-xs text-muted-foreground">
{t("使用此终结点访问Taiji AI API服务", "Use this endpoint to access Taiji AI API services")}
</p>
</div>
{/* API Key */}
<div className="space-y-2">
<Label>{t("API密钥", "API Key")}</Label>
<div className="flex gap-2">
<Input value={apiKey} readOnly className="font-mono text-sm" type="password" />
<Button variant="outline" size="icon" onClick={() => copyToClipboard(apiKey)} title={t("复制", "Copy")}>
<Copy className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" onClick={regenerateApiKey} title={t("重新生成", "Regenerate")}>
<RefreshCw className="h-4 w-4" />
</Button>
</div>
<p className="text-xs text-muted-foreground">
{t(
"请妥善保管您的API密钥。重新生成后,旧密钥将立即失效。",
"Keep your API key secure. Regenerating will immediately invalidate the old key.",
)}
</p>
</div>
{/* Usage Example */}
<div className="space-y-2">
<Label>{t("使用示例", "Usage Example")}</Label>
<div className="rounded-md bg-muted p-3">
<code className="text-xs">
curl -X POST {serviceEndpoint}/chat/completions \\
<br />
&nbsp;&nbsp;-H "Authorization: Bearer {apiKey.substring(0, 20)}..." \\
<br />
&nbsp;&nbsp;-H "Content-Type: application/json" \\
<br />
&nbsp;&nbsp;-d '{"{"}"model": "gpt-4", "messages": [...]{"}"}'
</code>
</div>
</div>
</div>
<DialogFooter>
<Button onClick={() => setShowApiKeyDialog(false)}>{t("关闭", "Close")}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}