diff --git a/.memory/worklog.json b/.memory/worklog.json index c880a01..08797d8 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -482,6 +482,13 @@ "message": "auto-save 2026-05-12 23:38 (~5)", "hash": "447f116", "files_changed": 5 + }, + { + "ts": "2026-05-12T23:44:18+08:00", + "type": "commit", + "message": "auto-save 2026-05-12 23:44 (~2)", + "hash": "494d990", + "files_changed": 2 } ] } diff --git a/web/components/lightbox.tsx b/web/components/lightbox.tsx index 6594b50..6ca7935 100644 --- a/web/components/lightbox.tsx +++ b/web/components/lightbox.tsx @@ -1,5 +1,6 @@ "use client" import { useEffect, useState } from "react" +import { createPortal } from "react-dom" import { X, ChevronLeft, ChevronRight, Check, Sparkles, Wand2, Loader2, Eye, RefreshCw, Copy } from "lucide-react" import { frameUrl, describeFrame, type KeyFrame, type Job } from "@/lib/api" import { toast } from "sonner" @@ -18,6 +19,8 @@ interface Props { export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate }: Props) { const [extractPrompt, setExtractPrompt] = useState("") const [describing, setDescribing] = useState(false) + const [mounted, setMounted] = useState(false) + useEffect(() => setMounted(true), []) useEffect(() => { if (activeIndex === null) return @@ -37,7 +40,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o return () => window.removeEventListener("keydown", onKey) }, [activeIndex, frames.length, onClose, onChange, onToggleSelect]) - if (activeIndex === null || !frames[activeIndex]) return null + if (activeIndex === null || !frames[activeIndex] || !mounted) return null const f = frames[activeIndex] const isSelected = selected.has(f.index) const desc = f.description @@ -59,68 +62,76 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o navigator.clipboard.writeText(text).then(() => toast.success("已复制")) } - return ( + return createPortal(
e.stopPropagation()} + className="fixed z-[100] rounded-2xl border border-white/15 bg-black/70 backdrop-blur-2xl overflow-hidden flex flex-col" + style={{ + top: 80, + right: 16, + width: 740, + maxHeight: "calc(100vh - 96px)", + boxShadow: "0 40px 100px -20px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.05)", + animation: "drawer-in 0.24s cubic-bezier(0.32, 0.72, 0, 1)", + }} > - - - {activeIndex > 0 && ( + {/* 顶部工具栏 — 切换 / 关闭 */} +
+
+ + + + 分镜 {String(f.index + 1).padStart(2, "0")} / {String(frames.length).padStart(2, "0")} + · + {f.timestamp.toFixed(2)}s + +
- )} - {activeIndex < frames.length - 1 && ( - - )} +
-
e.stopPropagation()} className="flex gap-4 max-w-[92vw] max-h-[92vh] items-start"> + {/* 主体 — 左大图 + 右识别面板 */} +
{/* 左侧大图 */} -
+
{`frame -
-
- {String(f.index + 1).padStart(2, "0")} / {String(frames.length).padStart(2, "0")} - · - {f.timestamp.toFixed(2)}s -
- -
+
{/* 右侧识别面板 */}
{/* 识别到的元素 */}
@@ -246,9 +257,10 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
-
- ←/→ 切换 · Space 选用 · ESC 关闭 +
+ ←/→ 切换 · Space 选用 · ESC 关闭 · 可拖侧画布看背后
-
+
, + document.body, ) }