"use client"; import { Award, BarChart3, Box, Briefcase, Building2, Clapperboard, Crown, Edit, ExternalLink, Images, LayoutGrid, Medal, PlayCircle, Settings, Sparkles, Trophy, Wand2, Workflow, X, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { toast } from "sonner"; import { ImageAnnotator } from "@/components/image-annotator"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { VideoPlayer } from "@/components/video-player"; import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry"; type RankingItem = { rank: number; userId: string; userName: string; department: string; featuredCount: number; }; type CustomLink = { id: string; name: string; url: string; order: number; toolboxGroup?: number; // 1 或 2 }; type TutorialVideo = { id: string; title: string; videoUrl: string; coverUrl: string | null; description: string | null; order: number; }; type TopNavbarProps = { siteName?: string; siteLogo?: string; siteLogoDark?: string; isAdmin?: boolean; settingsHref?: string | null; customLinks?: CustomLink[]; toolbox1Name?: string; toolbox1Enabled?: boolean; toolbox2Name?: string; toolbox2Enabled?: boolean; tutorialVideos?: TutorialVideo[]; tutorialEnabled?: boolean; rankingEnabled?: boolean; productPlanningEnabled?: boolean; agentEnabled?: boolean; videoCanvasEnabled?: boolean; }; export function TopNavbar({ siteName = "AI Chatbot", siteLogo, siteLogoDark, isAdmin = false, settingsHref = null, customLinks = [], toolbox1Name = "工具箱", toolbox1Enabled = true, toolbox2Name = "工具箱2", toolbox2Enabled = false, tutorialVideos = [], tutorialEnabled = false, rankingEnabled = true, productPlanningEnabled = true, agentEnabled = true, videoCanvasEnabled = true, }: TopNavbarProps) { const router = useRouter(); const { resolvedTheme } = useTheme(); const [ranking, setRanking] = useState([]); const [rankingOpen, setRankingOpen] = useState(false); const [tutorialMenuOpen, setTutorialMenuOpen] = useState(false); const [showTutorialModal, setShowTutorialModal] = useState(false); const [currentTutorialVideo, setCurrentTutorialVideo] = useState(null); const [showImageEditor, setShowImageEditor] = useState(false); const [editorImageUrl, setEditorImageUrl] = useState(""); // 移除 fileInputRef,不再需要外部上传 // 当下拉菜单打开时获取数据 useEffect(() => { if (!rankingOpen) { return; } const loadRanking = async () => { try { const res = await fetch("/api/featured/ranking"); const data = await res.json(); if (data.ranking) { setRanking(data.ranking); } } catch (error) { console.error("Failed to fetch ranking:", error); } }; loadRanking(); }, [rankingOpen]); // 获取排名图标 const getRankIcon = (rank: number) => { switch (rank) { case 1: return ; case 2: return ; case 3: return ; default: return ( {rank} ); } }; // 决定显示哪个 LOGO(浅色用普通,深色用暗色) const displayLogo = resolvedTheme === "dark" ? siteLogoDark && siteLogoDark.trim() !== "" ? siteLogoDark : siteLogo : siteLogo && siteLogo.trim() !== "" ? siteLogo : siteLogoDark; const shouldInvertLogo = resolvedTheme === "dark" && (!siteLogoDark || siteLogoDark.trim() === "") && Boolean(siteLogo && siteLogo.trim() !== ""); const getShortLabel = (label: string) => { const trimmed = label.trim(); if (!trimmed) { return ""; } if (trimmed === "AI精选") { return "精选"; } if (trimmed === "功能教程") { return "教程"; } if (trimmed === "精选榜") { return "榜单"; } if (trimmed === "图片编辑") { return "编辑"; } if (trimmed === "风格化") { return "风格"; } if (trimmed === "视频画布") { return "视频"; } if (trimmed === "无限画布") { return "画布"; } if (trimmed === "我的图册") { return "图册"; } if (trimmed === "商品策划") { return "策划"; } if (trimmed === "AI-CAD") { return "CAD"; } if (trimmed === "建筑编辑") { return "建筑"; } if (trimmed === "系统设置") { return "设置"; } if (trimmed === "工具箱") { return "工具"; } if (trimmed === "工具箱2") { return "工具"; } return Array.from(trimmed).slice(0, 2).join(""); }; const navItemBaseClass = "flex h-12 w-12 flex-col items-center justify-center gap-1 rounded-xl text-gray-800 hover:bg-black/5 transition-colors dark:text-white dark:hover:bg-white/10"; const navLabelClass = "text-[10px] leading-none font-medium text-gray-700 dark:text-white/90"; // 打开图片编辑器 - 直接打开,不触发上传 const handleOpenImageEditor = () => { setEditorImageUrl(""); // 确保初始为空 setShowImageEditor(true); }; // 处理图片编辑完成 const handleImageEditorConfirm = async (annotatedImageUrl: string) => { // 如果仍然是 base64 格式,先上传到服务器(ImageAnnotator 应该已经处理了,这里作为保护) let finalUrl = annotatedImageUrl; if (annotatedImageUrl.startsWith("data:")) { try { toast.info("正在上传图片..."); const response = await fetch(annotatedImageUrl); const blob = await response.blob(); const formData = new FormData(); formData.append("file", blob, `edited-${Date.now()}.png`); const uploadResponse = await fetchUploadWithRetry(formData); if (uploadResponse.ok) { const result = await uploadResponse.json(); finalUrl = result.url; } else { toast.error("上传失败"); return; } } catch (error) { console.error("上传图片失败:", error); toast.error("上传失败"); return; } } // 将编辑后的图片存储到 sessionStorage,以便在聊天页面中使用 try { sessionStorage.setItem("pending-edited-image", finalUrl); } catch (error) { // sessionStorage不可用(如隐私模式),使用URL参数作为备选方案 console.warn("sessionStorage不可用,尝试使用URL参数:", error); // 如果sessionStorage不可用,我们可以将图片数据编码到URL中 // 但由于数据可能很大,这里只记录警告,让用户手动添加图片 // 或者可以显示提示信息 } // 导航到新聊天页面(无论sessionStorage是否成功) router.push("/"); setShowImageEditor(false); setEditorImageUrl(""); }; // 取消图片编辑 const handleImageEditorCancel = () => { setShowImageEditor(false); setEditorImageUrl(""); }; const navContent = ( ); // 使用 Portal 将视频弹窗渲染到 body,确保它在最顶层且不受 nav 的限制 const videoModal = showTutorialModal && currentTutorialVideo && typeof document !== "undefined" ? createPortal(
{ setShowTutorialModal(false); setCurrentTutorialVideo(null); }} style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, display: "flex", alignItems: "center", justifyContent: "center", margin: 0, padding: "20px", }} >
e.stopPropagation()} style={{ maxHeight: "calc(100vh - 80px)" }} > {/* 标题栏 - 包含标题和关闭按钮 */}
{/* 占位符保持标题居中 */}

{currentTutorialVideo.title}

{/* 视频播放器 */} {currentTutorialVideo.videoUrl.includes("youtube.com") || currentTutorialVideo.videoUrl.includes("youtu.be") ? (