"use client"; import Link from "next/link"; import { useState, useEffect } from "react"; import { Dropdown, Modal } from "antd"; import { toast, Toaster } from "sonner"; import type { MenuProps } from "antd"; import { Copy, LogOut, Database, Github, Menu, Globe, X, Settings, ChevronDown, } from "lucide-react"; import DatabaseBackup from "./DatabaseBackup"; import { APP_VERSION } from "@/lib/version"; import { usePathname, useRouter } from "next/navigation"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { createRoot } from "react-dom/client"; import { motion, AnimatePresence } from "framer-motion"; import { useTranslation } from "react-i18next"; import { FiDatabase, FiUsers, FiBarChart2 } from "react-icons/fi"; export default function Header() { const { t, i18n } = useTranslation("common"); const pathname = usePathname(); const router = useRouter(); const [isMenuOpen, setIsMenuOpen] = useState(false); const [isBackupModalOpen, setIsBackupModalOpen] = useState(false); const [isCheckingUpdate, setIsCheckingUpdate] = useState(false); const [accessToken, setAccessToken] = useState(null); const handleLanguageChange = async (newLang: string) => { await i18n.changeLanguage(newLang); localStorage.setItem("language", newLang); }; const isTokenPage = pathname === "/token"; if (isTokenPage) { return (
{t("common.appName")}
); } const [apiKey, setApiKey] = useState(t("common.loading")); useEffect(() => { const token = localStorage.getItem("access_token"); setAccessToken(token); if (!token) { router.push("/token"); return; } fetch("/api/v1/config", { headers: { Authorization: `Bearer ${token}`, }, }) .then((res) => { if (!res.ok) { localStorage.removeItem("access_token"); router.push("/token"); return; } return res.json(); }) .then((data) => { if (data) { setApiKey(data.apiKey); } }) .catch(() => { setApiKey(t("common.error")); localStorage.removeItem("access_token"); router.push("/token"); }); }, [router, t]); const handleCopyApiKey = () => { const token = localStorage.getItem("access_token"); if (!token) { toast.error(t("header.messages.unauthorized")); return; } navigator.clipboard.writeText(apiKey); toast.success(t("header.messages.apiKeyCopied")); }; const handleLogout = () => { localStorage.removeItem("access_token"); window.location.href = "/token"; }; const checkUpdate = async () => { const token = localStorage.getItem("access_token"); if (!token) { toast.error(t("header.messages.unauthorized")); return; } setIsCheckingUpdate(true); try { const response = await fetch( "https://api.github.com/repos/variantconst/openwebui-monitor/releases/latest" ); const data = await response.json(); const latestVersion = data.tag_name; if (!latestVersion) { throw new Error(t("header.messages.getVersionFailed")); } const currentVer = APP_VERSION.replace(/^v/, ""); const latestVer = latestVersion.replace(/^v/, ""); if (currentVer === latestVer) { toast.success(`${t("header.messages.latestVersion")} v${APP_VERSION}`); } else { return new Promise((resolve) => { const dialog = document.createElement("div"); document.body.appendChild(dialog); const DialogComponent = () => { const [open, setOpen] = useState(true); const handleClose = () => { setOpen(false); document.body.removeChild(dialog); resolve(null); }; const handleUpdate = () => { window.open( "https://github.com/VariantConst/OpenWebUI-Monitor/releases/latest", "_blank" ); handleClose(); }; return (
{t("header.update.newVersion")}
{t("header.update.currentVersion")} v{APP_VERSION}
{t("header.update.latestVersion")} {latestVersion}
); }; createRoot(dialog).render(); }); } } catch (error) { toast.error(t("header.messages.updateCheckFailed")); console.error(t("header.messages.updateCheckFailed"), error); } finally { setIsCheckingUpdate(false); } }; const navigationItems = [ { path: "/models", icon: , label: t("home.features.models.title"), color: "from-blue-500/10 to-indigo-500/10", hoverColor: "group-hover:text-blue-600", }, { path: "/users", icon: , label: t("home.features.users.title"), color: "from-rose-500/10 to-pink-500/10", hoverColor: "group-hover:text-rose-600", }, { path: "/panel", icon: , label: t("home.features.stats.title"), color: "from-emerald-500/10 to-teal-500/10", hoverColor: "group-hover:text-emerald-600", }, ]; const settingsItems = [ { icon: , label: t("header.menu.copyApiKey"), onClick: handleCopyApiKey, color: "from-blue-500/20 to-indigo-500/20", }, { icon: , label: t("header.menu.dataBackup"), onClick: () => setIsBackupModalOpen(true), color: "from-rose-500/20 to-pink-500/20", }, { icon: , label: t("header.menu.checkUpdate"), onClick: checkUpdate, color: "from-emerald-500/20 to-teal-500/20", }, { icon: , label: t("header.menu.logout"), onClick: handleLogout, color: "from-orange-500/20 to-red-500/20", }, ]; const menuItems = [ ...(!isTokenPage ? navigationItems.map((item) => ({ ...item, onClick: () => router.push(item.path), })) : []), { icon: , label: t("header.menu.copyApiKey"), onClick: handleCopyApiKey, color: "from-blue-500/20 to-indigo-500/20", }, { icon: , label: t("header.menu.dataBackup"), onClick: () => setIsBackupModalOpen(true), color: "from-rose-500/20 to-pink-500/20", }, { icon: , label: t("header.menu.checkUpdate"), onClick: checkUpdate, color: "from-emerald-500/20 to-teal-500/20", }, { icon: , label: t("header.menu.logout"), onClick: handleLogout, color: "from-orange-500/20 to-red-500/20", }, ]; const actionItems = [ { icon: , label: i18n.language === "zh" ? "简体中文" : "English", onClick: () => handleLanguageChange(i18n.language === "zh" ? "en" : "zh"), color: "from-gray-100 to-gray-50", hoverColor: "group-hover:text-gray-900", }, { icon: , label: t("header.menu.settings"), onClick: () => setIsMenuOpen(true), color: "from-gray-100 to-gray-50", hoverColor: "group-hover:text-gray-900", }, ]; return ( <>
{t("common.appName")}
{!isTokenPage && (
{navigationItems.map((item) => (
{item.icon} {item.label}
))}
)}
{actionItems.map((item, index) => ( ))}
{isMenuOpen && ( <> setIsMenuOpen(false)} />

{t("header.menu.settings")}

{navigationItems.map((item, index) => ( { setIsMenuOpen(false); router.push(item.path); }} className="w-full group" >
{item.icon} {item.label}
))}
{settingsItems.map((item, index) => ( { setIsMenuOpen(false); item.onClick(); }} className="w-full group" >
{item.icon} {item.label}
))}
)}
setIsBackupModalOpen(false)} token={accessToken || undefined} /> ); }