mirror of
https://github.com/Fasthei/taiji-pda-v0.git
synced 2026-09-26 21:13:25 +00:00
- 删除资源管理中的假数据,改为从API获取 - 添加删除Agent和模型供应商功能 - 完善创建渠道、资源管理、审批申请等功能的API集成 - 修复计费统计功能,接入后端API - 添加缺失API接口清单文档 - 优化API响应格式处理,支持多种响应结构 - 修复审批申请对话框,添加供应商申请审批功能
287 lines
12 KiB
TypeScript
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 />
|
|
-H "Authorization: Bearer {apiKey.substring(0, 20)}..." \\
|
|
<br />
|
|
-H "Content-Type: application/json" \\
|
|
<br />
|
|
-d '{"{"}"model": "gpt-4", "messages": [...]{"}"}'
|
|
</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button onClick={() => setShowApiKeyDialog(false)}>{t("关闭", "Close")}</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
)
|
|
}
|