"use client"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Settings, ExternalLink, Briefcase, Images, Sparkles, Trophy, Building2, Crown, Medal, Award, Edit, LayoutGrid, PlayCircle, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTheme } from "next-themes"; import { useState, useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ImageAnnotator } from "@/components/image-annotator"; import { VideoPlayer } from "@/components/video-player"; import { toast } from "sonner"; 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; customLinks?: CustomLink[]; toolbox1Name?: string; toolbox1Enabled?: boolean; toolbox2Name?: string; toolbox2Enabled?: boolean; tutorialVideos?: TutorialVideo[]; tutorialEnabled?: boolean; rankingEnabled?: boolean; }; export function TopNavbar({ siteName = "AI Chatbot", siteLogo, siteLogoDark, isAdmin = false, customLinks = [], toolbox1Name = "工具箱", toolbox1Enabled = true, toolbox2Name = "工具箱2", toolbox2Enabled = false, tutorialVideos = [], tutorialEnabled = false, rankingEnabled = true, }: TopNavbarProps) { const router = useRouter(); const { resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); 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(() => { setMounted(true); }, []); // 获取精选榜单数据 const fetchRanking = 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); } }; // 当下拉菜单打开时获取数据 useEffect(() => { if (rankingOpen) { fetchRanking(); } }, [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 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 === "工具箱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 fetch("/api/files/upload", { method: "POST", body: 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); }} >
e.stopPropagation()} > {/* 标题栏 - 包含标题和关闭按钮 */}
{/* 占位符保持标题居中 */}

{currentTutorialVideo.title}

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