diff --git a/.memory/worklog.json b/.memory/worklog.json index fdae515..16c42f6 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -2102,6 +2102,13 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 18:24 (~3)", "files_changed": 2 + }, + { + "ts": "2026-05-13T18:29:59+08:00", + "type": "commit", + "message": "auto-save 2026-05-13 18:29 (~2)", + "hash": "bc458b6", + "files_changed": 2 } ] } diff --git a/web/components/nodes/index.tsx b/web/components/nodes/index.tsx index 222252c..f0665fb 100644 --- a/web/components/nodes/index.tsx +++ b/web/components/nodes/index.tsx @@ -599,9 +599,17 @@ const IMAGEGEN_WIDTH = 360 export function StoryboardNode({ data, selected }: any) { const d: NodeData = data const job = d?.job + const [hover, setHover] = useState<{ + rect: DOMRect + name: string + elementSrc: string + frameSrc: string + frameIdx: number + timestamp: number + } | null>(null) // 上方浮条 = 所有 frame 的 elements 已提取图("分镜头编排"的输入素材) - type ElPreview = { frameIdx: number; elementId: string; name: string; src: string; cid: string } + type ElPreview = { frameIdx: number; elementId: string; name: string; src: string; cid: string; frameSrc: string; timestamp: number } const elementCrops: ElPreview[] = job ? job.frames.flatMap((f) => (f.elements ?? []) @@ -611,7 +619,15 @@ export function StoryboardNode({ data, selected }: any) { const cid = (e.cutouts && e.cutouts.length > 0) ? e.cutouts[e.cutouts.length - 1] : (e.cutout_id ?? "") - return { frameIdx: f.index, elementId: e.id, name: e.name_zh, src, cid } + return { + frameIdx: f.index, + elementId: e.id, + name: e.name_zh, + src, + cid, + frameSrc: effectiveFrameUrl(job.id, f), + timestamp: f.timestamp, + } }) .filter((p) => p.src), ) @@ -637,6 +653,17 @@ export function StoryboardNode({ data, selected }: any) { key={key} className="group relative rounded-md border border-violet-300/50 transition shadow-lg hover:-translate-y-0.5 bg-white overflow-hidden" style={{ aspectRatio: aspect }} + onMouseEnter={(e) => { + setHover({ + rect: e.currentTarget.getBoundingClientRect(), + name: p.name, + elementSrc: p.src, + frameSrc: p.frameSrc, + frameIdx: p.frameIdx, + timestamp: p.timestamp, + }) + }} + onMouseLeave={() => setHover(null)} > )} - {/* hover 预览 — absolute 浮在缩略图上方 */} -
-
-
- -
-
- {p.name} - 分镜 {p.frameIdx + 1} -
-
-
) })} )} + {job && hover && (() => { + const w = 420 + const h = 300 + const gap = 12 + const centerX = hover.rect.left + hover.rect.width / 2 + const left = Math.max(12, Math.min(window.innerWidth - w - 12, centerX - w / 2)) + const top = hover.rect.top > h + gap + ? hover.rect.top - h - gap + : Math.min(window.innerHeight - h - 12, hover.rect.bottom + gap) + return createPortal( +
+
+
+
+
来源原帧
+
+ +
+
+
+
提取元素
+
+ +
+
+
+
+ {hover.name} + 分镜 {hover.frameIdx + 1} · {hover.timestamp.toFixed(2)}s +
+
+
, + document.body, + ) + })()} + }