'use client'; import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { PACK_LABELS, PACK_ORDER, PACK_TEMPLATES } from '@/lib/templates'; import type { GenSession, PackKind } from '@/lib/types'; import ResultGrid from './ResultGrid'; export type ProjectGalleryDrawerProps = { session: GenSession; activeNav: string; onAction: (imageId: string, action: 'select' | 'reject' | 'reset') => void; }; type GalleryItem = { id: string; url: string; title: string; description?: string; status?: string; aspectRatio?: string; mediaType?: 'image' | 'video'; downloadName?: string; }; type GalleryPanel = { mode: 'pack' | 'empty' | 'project' | 'video'; kind?: PackKind; label: string; description: string; total: number; images: GalleryItem[]; }; function statusClass(status?: string) { if (status === 'selected') return 'ring-[#e6f578]/80 shadow-[0_0_24px_-12px_rgba(230,245,120,0.9)]'; if (status === 'rejected') return 'opacity-45 grayscale ring-white/10'; return 'ring-white/12 hover:ring-white/28'; } function aspectCss(aspectRatio?: string) { if (!aspectRatio) return '1 / 1'; if (aspectRatio === 'long') return '1 / 3'; return aspectRatio.replace(':', ' / '); } function activeKindFromNav(activeNav: string): PackKind | null { const kind = PACK_ORDER.find(item => activeNav === `pack-${item}`); return kind ?? null; } function galleryForPanel(session: GenSession, activeNav: string): GalleryPanel { const kind = activeKindFromNav(activeNav); const selectedImages = session.images.filter(image => image.status === 'selected'); const primaryImage = selectedImages[0] ?? session.images[0] ?? null; if (kind) { const pack = session.packs?.find(item => item.kind === kind && (!primaryImage || item.sourceImageId === primaryImage.id)) ?? session.packs?.find(item => item.kind === kind); const images: GalleryItem[] = pack?.assets.map(asset => ({ id: asset.id, url: asset.url, title: asset.title, description: asset.description, status: asset.status, aspectRatio: asset.aspectRatio, })) ?? []; return { mode: 'pack' as const, kind, label: PACK_LABELS[kind], description: `${PACK_LABELS[kind]}当前区块图片`, total: PACK_TEMPLATES[kind].length, images, }; } if (activeNav === 'pack-text') { return { mode: 'empty' as const, label: '文字', description: '文字区块暂无图片素材', total: 0, images: [] as GalleryItem[] }; } if (activeNav === 'pack-video') { const videos: GalleryItem[] = (session.videoTasks ?? []) .filter(task => !/_part[12]$/.test(task.templateId)) .filter(task => task.videoUrl) .map(task => ({ id: task.templateId, url: task.videoUrl!, title: task.title, description: `${task.ratio} · ${task.duration}s · ${task.status}`, status: task.status, aspectRatio: task.ratio, mediaType: 'video' as const, downloadName: `${session.id}_${task.templateId}.mp4`, })); return { mode: 'video' as const, label: '视频', description: '当前项目所有视频成片', total: videos.length, images: videos, }; } return { mode: 'project', label: '项目', description: session.characterSpec?.name || session.prompt || '当前项目', total: session.images.length, images: session.images.map((image, index) => ({ id: image.id, url: image.url, title: `主图库 ${index + 1}`, description: undefined, status: image.status, aspectRatio: '1:1', })), }; } export default function ProjectGalleryDrawer({ session, activeNav, onAction }: ProjectGalleryDrawerProps) { const [open, setOpen] = useState(false); const [mounted, setMounted] = useState(false); const [preview, setPreview] = useState(null); const gallery = galleryForPanel(session, activeNav); const downloadHref = gallery.kind && gallery.images.length ? `/api/packs/download?sessionId=${encodeURIComponent(session.id)}&kind=${encodeURIComponent(gallery.kind)}` : null; useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!open) return; function handleKey(event: KeyboardEvent) { if (event.key === 'Escape') setOpen(false); } window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [open]); const drawer = ( <>
setOpen(false)} /> ); const centerPreview = preview ? ( ) : null; return ( <> {mounted ? createPortal(drawer, document.body) : null} {mounted && centerPreview ? createPortal(centerPreview, document.body) : null} ); }