auto-save 2026-05-13 16:35 (~2)
This commit is contained in:
@@ -1901,6 +1901,25 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 3 项未提交变更 · 最近提交:auto-save 2026-05-13 16:23 (~6)",
|
||||
"files_changed": 3
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T16:30:04+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-13 16:28 (~3)",
|
||||
"hash": "f18aedf",
|
||||
"files_changed": 3
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T08:33:04Z",
|
||||
"type": "session-end",
|
||||
"message": "Claude 会话结束 · 持续 0 秒 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 16:28 (~3)",
|
||||
"files_changed": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T08:33:04Z",
|
||||
"type": "session-end",
|
||||
"message": "Claude 会话结束 · 持续 0 秒 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 16:28 (~3)",
|
||||
"files_changed": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 && (() => {
|
||||
|
||||
Reference in New Issue
Block a user