auto-save 2026-05-13 13:48 (~3)

This commit is contained in:
2026-05-13 13:48:39 +08:00
parent 7e55b9b60d
commit 2d297ecdc1
3 changed files with 66 additions and 11 deletions

View File

@@ -1589,6 +1589,19 @@
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 1 项未提交变更 · 最近提交auto-save 2026-05-13 13:37 (~1)",
"files_changed": 1
},
{
"ts": "2026-05-13T13:43:08+08:00",
"type": "commit",
"message": "auto-save 2026-05-13 13:42 (+1, ~4)",
"hash": "7e55b9b",
"files_changed": 5
},
{
"ts": "2026-05-13T05:47:39Z",
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 3 项未提交变更 · 最近提交auto-save 2026-05-13 13:42 (+1, ~4)",
"files_changed": 3
}
]
}

View File

@@ -618,8 +618,8 @@ export function ImageGenNode({ data, selected }: any) {
style={{ aspectRatio: aspect }}
>
<button
onClick={(e) => { e.stopPropagation(); d.onOpenPanel?.("imagegen") }}
title={`${p.name} · 来自分镜 ${p.frameIdx + 1}`}
onClick={(e) => { e.stopPropagation(); d.onExpandFrame(p.frameIdx) }}
title={`${p.name} · 来自分镜 ${p.frameIdx + 1} · 点击进入该分镜精细调整`}
className="absolute inset-0 w-full h-full"
>
<img

View File

@@ -1,7 +1,8 @@
"use client"
import { useState } from "react"
import { useEffect, useRef, useState } from "react"
import { createPortal } from "react-dom"
import { LayoutGrid, ChevronDown, ChevronUp, Sparkle } from "lucide-react"
import { type Job, effectiveFrameUrl } from "@/lib/api"
import { type Job, type KeyFrame, effectiveFrameUrl } from "@/lib/api"
interface Props {
job: Job | null
@@ -11,6 +12,12 @@ interface Props {
export function StoryboardBar({ job, selectedFrames, onExpandFrame }: Props) {
const [collapsed, setCollapsed] = useState(false)
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
// hover preview state — portal 渲染到 body 避免被父级 overflow-x-auto clip
const [hover, setHover] = useState<{ frame: KeyFrame; seq: number; rect: DOMRect } | null>(null)
const btnRefs = useRef<Record<number, HTMLButtonElement | null>>({})
if (!job) return null
// 按时间序排已选用的分镜
@@ -63,28 +70,31 @@ export function StoryboardBar({ job, selectedFrames, onExpandFrame }: Props) {
return (
<button
key={f.index}
ref={(el) => { btnRefs.current[f.index] = el }}
onClick={() => onExpandFrame(f.index)}
title={`分镜 ${i + 1} · ${f.timestamp.toFixed(2)}s${cleaned ? " · 已清洗" : ""} · ${elementCount}/${totalElCount} 元素 · 点击编辑`}
className="group relative shrink-0 rounded-md border border-white/15 hover:border-violet-300/60 overflow-hidden transition shadow-lg hover:-translate-y-0.5"
onMouseEnter={() => {
const el = btnRefs.current[f.index]
if (el) setHover({ frame: f, seq: i + 1, rect: el.getBoundingClientRect() })
}}
onMouseLeave={() => setHover(null)}
title={`分镜 ${i + 1} · ${f.timestamp.toFixed(2)}s${cleaned ? " · 已清洗" : ""} · ${elementCount}/${totalElCount} 元素 · 点击进入精细调整`}
className="relative shrink-0 rounded-md border border-white/15 hover:border-violet-300/60 transition shadow-lg hover:-translate-y-0.5"
style={{ width: 88, aspectRatio: aspect }}
>
<img
src={effectiveFrameUrl(job.id, f)}
alt={`frame ${f.index}`}
className="absolute inset-0 w-full h-full object-cover"
className="absolute inset-0 w-full h-full object-cover rounded-md"
/>
{/* 左上:序号 */}
<div className="absolute top-1 left-1 text-[9.5px] font-bold text-white bg-violet-500/85 backdrop-blur px-1.5 py-0.5 rounded">
#{i + 1}
</div>
{/* 右上:清洗标记 */}
{cleaned && (
<div className="absolute top-1 right-1 text-[9px] text-white bg-cyan-500/85 backdrop-blur px-1 py-0.5 rounded font-bold" title="已清洗">
</div>
)}
{/* 底部:时间 + 元素数 */}
<div className="absolute bottom-0 right-0 left-0 px-1.5 py-0.5 text-[9px] font-mono text-white bg-gradient-to-t from-black/85 to-transparent flex items-center justify-between">
<div className="absolute bottom-0 right-0 left-0 px-1.5 py-0.5 text-[9px] font-mono text-white bg-gradient-to-t from-black/85 to-transparent flex items-center justify-between rounded-b-md">
<span>{f.timestamp.toFixed(1)}s</span>
{totalElCount > 0 && (
<span className="inline-flex items-center gap-0.5">
@@ -99,6 +109,38 @@ export function StoryboardBar({ job, selectedFrames, onExpandFrame }: Props) {
</div>
)
)}
{/* Hover 大图预览 · portal 到 body 避免父容器 overflow clip */}
{mounted && hover && createPortal(
<div
className="fixed z-[120] pointer-events-none"
style={{
top: hover.rect.bottom + 8,
left: Math.max(12, Math.min(window.innerWidth - 380, hover.rect.left + hover.rect.width / 2 - 180)),
animation: "drawer-in 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
<div className="rounded-2xl overflow-hidden border border-white/25 bg-black" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<img
src={effectiveFrameUrl(job.id, hover.frame)}
alt={`preview ${hover.frame.index}`}
className="block"
style={{
width: 360,
maxWidth: "min(560px, 70vw)",
height: "auto",
maxHeight: "70vh",
objectFit: "contain",
}}
/>
<div className="flex items-center justify-between px-3 py-2 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12.5px] font-medium"> {hover.seq} · {hover.frame.timestamp.toFixed(2)}s</span>
<span className="text-white/60 text-[11px] font-mono"></span>
</div>
</div>
</div>,
document.body,
)}
</div>
)
}