diff --git a/.memory/worklog.json b/.memory/worklog.json index e626138..e9f5f07 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1722,6 +1722,19 @@ "message": "auto-save 2026-05-13 14:49 (~5)", "hash": "ffffb1e", "files_changed": 5 + }, + { + "ts": "2026-05-13T14:55:04+08:00", + "type": "commit", + "message": "auto-save 2026-05-13 14:54 (~2)", + "hash": "7a5c07b", + "files_changed": 2 + }, + { + "ts": "2026-05-13T06:57:39Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 14:54 (~2)", + "files_changed": 2 } ] } diff --git a/web/components/storyboard-bar.tsx b/web/components/storyboard-bar.tsx index 102a3c9..1e79628 100644 --- a/web/components/storyboard-bar.tsx +++ b/web/components/storyboard-bar.tsx @@ -1,9 +1,8 @@ "use client" import { useEffect, useRef, useState } from "react" import { createPortal } from "react-dom" -import { LayoutGrid, ChevronDown, ChevronUp, Sparkle, X, Loader2, Check } from "lucide-react" -import { type Job, type KeyFrame, type StoryboardScene, effectiveFrameUrl, hasCutout, representativeCutoutUrl, updateStoryboard } from "@/lib/api" -import { toast } from "sonner" +import { LayoutGrid, ChevronDown, ChevronUp, Sparkle, X } from "lucide-react" +import { type Job, type KeyFrame, cutoutUrl, effectiveFrameUrl, hasCutout } from "@/lib/api" interface Props { job: Job | null @@ -13,48 +12,13 @@ interface Props { onJobUpdate?: (j: Job) => void } -const emptyScene = (): StoryboardScene => ({ - subject: "", product: "", scene: "", action: "", duration: 0, reference_ids: [], -}) - -export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame, onJobUpdate }: Props) { +export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame }: Props) { const [collapsed, setCollapsed] = useState(false) const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) const [hover, setHover] = useState<{ frame: KeyFrame; seq: number; rect: DOMRect } | null>(null) const btnRefs = useRef>({}) - // 表单 state:每次切到新 focus frame 加载该帧的 storyboard - const [form, setForm] = useState(emptyScene()) - const [saving, setSaving] = useState(false) - const [savedTick, setSavedTick] = useState(0) - const saveTimer = useRef | null>(null) - - useEffect(() => { - if (!job || focusedFrame === null) return - const f = job.frames.find((x) => x.index === focusedFrame) - setForm(f?.storyboard ? { ...emptyScene(), ...f.storyboard } : emptyScene()) - }, [focusedFrame, job?.id]) - - // 自动保存:表单变化 600ms 后调 API - const queueSave = (next: StoryboardScene) => { - setForm(next) - if (!job || focusedFrame === null) return - if (saveTimer.current) clearTimeout(saveTimer.current) - saveTimer.current = setTimeout(async () => { - setSaving(true) - try { - const updated = await updateStoryboard(job.id, focusedFrame, next) - onJobUpdate?.(updated) - setSavedTick((t) => t + 1) - } catch (e) { - toast.error("保存失败:" + (e instanceof Error ? e.message : String(e))) - } finally { - setSaving(false) - } - }, 600) - } - if (!job) return null const frames = job.frames @@ -75,7 +39,6 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame, ? job.frames.filter((f) => selectedFrames.has(f.index) && f.timestamp <= focusFrame.timestamp).length : 0 const focusElements = focusFrame?.elements ?? [] - const focusCutCount = focusElements.filter((e) => hasCutout(e)).length return (
@@ -235,135 +198,6 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame, ) })()} - {/* 旧 form 面板已暂时移除(用户要求只展示提取图) */} - {false && focusFrame && ( -
- {/* 占位防止 saving / form / FieldX 等变量未使用报错 */} - {saving}{savedTick}{form.subject}{focusCutCount} - {}} /> - {}} /> - {}} /> -
- )} - - {/* 旧 form 面板代码(保留 fallback 渲染) */} - {false && focusFrame && !collapsed && ( -
-
-
- {`frame -
- {focusFrame.cleaned_applied ? "✨ 已清洗版" : "原图"} · 分镜 {focusSeq} · {focusFrame.timestamp.toFixed(2)}s -
-
- - {/* 右:编排表单 */} -
- {/* 保存状态 */} -
-
- - 分镜编排参数 -
- - {saving ? (<>保存中…) - : savedTick > 0 ? (<>已自动保存) - : "字段变更自动保存"} - -
- - {/* 5 个字段 — 2×2 grid + 跨列的 action */} -
- queueSave({ ...form, subject: v })} - /> - queueSave({ ...form, product: v })} - /> - queueSave({ ...form, scene: v })} - /> - queueSave({ ...form, duration: v })} - /> -
- queueSave({ ...form, action: v })} - rows={2} - /> - - {/* 参考图区 — 多选该分镜已提取元素 */} -
-
- 参考图(多选) - - 选用 {form.reference_ids.length} / 可选 {focusElements.filter((e) => hasCutout(e)).length} - -
- {focusElements.filter((e) => hasCutout(e)).length === 0 ? ( -
- 该分镜暂无可选参考图 · 到关键帧节点画框「AI 提取」后会出现 -
- ) : ( -
- {focusElements.filter((e) => hasCutout(e)).map((e) => { - const src = representativeCutoutUrl(job.id, focusFrame.index, e) - const checked = form.reference_ids.includes(e.id) - return ( - - ) - })} -
- )} -
-
-
-
- )} {/* Hover 大图预览 · 浮在缩略图下方(不挡其他界面) */} {mounted && hover && (() => { @@ -403,49 +237,3 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame, ) } -function FieldText({ label, value, onChange, placeholder }: { label: string; value: string; onChange: (v: string) => void; placeholder?: string }) { - return ( - - ) -} - -function FieldNum({ label, value, onChange, placeholder }: { label: string; value: number; onChange: (v: number) => void; placeholder?: string }) { - return ( - - ) -} - -function FieldTextarea({ label, value, onChange, placeholder, rows = 2 }: { label: string; value: string; onChange: (v: string) => void; placeholder?: string; rows?: number }) { - return ( -