auto-save 2026-05-13 16:35 (~2)

This commit is contained in:
2026-05-13 16:35:33 +08:00
parent f18aedf59c
commit 5e3d40e2d8
2 changed files with 21 additions and 93 deletions

View File

@@ -1,9 +1,8 @@
"use client"
import { useEffect, useRef, useState } from "react"
import { createPortal } from "react-dom"
import { LayoutGrid, ChevronDown, ChevronUp, Sparkle, X } from "lucide-react"
import { type Job, type KeyFrame, cutoutUrl, effectiveFrameUrl, hasCutout, removeStoryboardImage } from "@/lib/api"
import { toast } from "sonner"
import { LayoutGrid, ChevronDown, ChevronUp, Sparkle } from "lucide-react"
import { type Job, type KeyFrame, effectiveFrameUrl, hasCutout } from "@/lib/api"
interface Props {
job: Job | null
@@ -42,19 +41,6 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
? frames.findIndex((f) => f.index === focusFrame.index) + 1
: 0
// 用户已"上推"到分镜头编排区的图片
const pushedImages = job.storyboard_images ?? []
const frameSeqByIdx: Record<number, number> = {}
frames.forEach((f, i) => { frameSeqByIdx[f.index] = i + 1 })
const handleRemovePushed = async (refId: string) => {
try {
const updated = await removeStoryboardImage(job.id, refId)
onJobUpdate?.(updated)
} catch (e) {
toast.error("移除失败:" + (e instanceof Error ? e.message : String(e)))
}
}
return (
<div className="relative z-20 flex-shrink-0 border-b border-white/5 bg-black/30 backdrop-blur-xl">
@@ -164,83 +150,6 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
)
)}
{/* 已推送到分镜头编排区的图片 */}
{!collapsed && (
<div className="border-t border-white/10 px-4 py-2 bg-black/10">
<div className="text-[10px] text-white/45 mb-1.5 inline-flex items-center gap-1">
<Sparkle className="h-2.5 w-2.5 text-violet-300" />
· {pushedImages.length}
<span className="text-white/30 ml-1"> </span>
</div>
{pushedImages.length === 0 ? (
<div className="rounded-md border border-dashed border-white/15 p-2.5 text-[10.5px] text-white/35">
· / /
</div>
) : (
<div className="flex gap-1.5 overflow-x-auto pb-1">
{pushedImages.map((p) => {
const seq = frameSeqByIdx[p.frame_idx] ?? p.frame_idx + 1
const url = p.kind === "keyframe"
? effectiveFrameUrl(job.id, { index: p.frame_idx, cleaned_applied: false })
: (p.element_id && p.cutout_id
? (p.cutout_id === p.element_id
? cutoutUrl(job.id, p.frame_idx, p.element_id) // legacy
: cutoutUrl(job.id, p.frame_idx, p.element_id, p.cutout_id))
: "")
const isFocusFrame = focusedFrame === p.frame_idx
return (
<div
key={p.ref_id}
className={`group/p relative shrink-0 rounded-md overflow-hidden border bg-white transition hover:-translate-y-0.5 ${
isFocusFrame
? "border-violet-300 ring-2 ring-violet-300/60"
: "border-white/15 hover:border-violet-300/50"
}`}
style={{ width: 88, height: 88 }}
>
<button
onClick={() => onFocusFrame(p.frame_idx)}
onMouseEnter={(ev) => {
if (!url) return
setHover({
src: url,
topLabel: p.label || (p.kind === "keyframe" ? "关键帧" : "元素"),
subLabel: `分镜 #${seq}`,
rect: (ev.currentTarget as HTMLElement).getBoundingClientRect(),
})
}}
onMouseLeave={() => setHover(null)}
title={`${p.label || p.kind} · 来自分镜 #${seq} · 点击聚焦该分镜`}
className="absolute inset-0 w-full h-full"
>
{url && <img src={url} alt={p.label} className="absolute inset-0 w-full h-full object-contain" />}
<div className="absolute top-0.5 left-0.5 text-[8.5px] font-bold text-white bg-violet-500/85 backdrop-blur px-1 py-0.5 rounded leading-none">
#{seq}
</div>
{p.kind === "keyframe" && (
<div className="absolute top-0.5 right-5 text-[8.5px] text-white bg-amber-500/85 backdrop-blur px-1 py-0.5 rounded leading-none">
KF
</div>
)}
<div className="absolute bottom-0 left-0 right-0 px-1 py-0.5 text-[8.5px] text-white bg-black/70 truncate leading-tight">
{p.label || (p.kind === "keyframe" ? "关键帧" : "元素")}
</div>
</button>
{/* 右上移除按钮hover 显示) */}
<button
onClick={(ev) => { ev.stopPropagation(); handleRemovePushed(p.ref_id) }}
title="从分镜头编排移除"
className="absolute top-0.5 right-0.5 h-4 w-4 rounded-sm bg-black/70 text-white/80 hover:bg-rose-500 hover:text-white inline-flex items-center justify-center opacity-0 group-hover/p:opacity-100 transition"
>
<X className="h-2.5 w-2.5" />
</button>
</div>
)
})}
</div>
)}
</div>
)}
{/* Hover 预览 · 浮在缩略图正下方bar 在顶部 fixed下方是 DAG 画布区) */}
{mounted && hover && (() => {