auto-save 2026-05-13 20:01 (~6)
This commit is contained in:
@@ -2255,6 +2255,19 @@
|
||||
"message": "auto-save 2026-05-13 19:50 (~4)",
|
||||
"hash": "a471f89",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T19:56:22+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-13 19:56 (~4)",
|
||||
"hash": "28de936",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T11:59:29Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 6 项未提交变更 · 最近提交:auto-save 2026-05-13 19:56 (~4)",
|
||||
"files_changed": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -573,7 +573,7 @@
|
||||
<tr><td><code>web/components/nodes/index.tsx</code></td><td>DAG 节点定义:Input、Keyframe、ASR、Translate、Rewrite、Storyboard、VideoGen、Compose。</td></tr>
|
||||
<tr><td><code>web/components/lightbox.tsx</code></td><td>镜头拆解和元素提取的主工作面板:清洗、识别、元素编辑、区域提取、抠图。</td></tr>
|
||||
<tr><td><code>web/components/storyboard-bar.tsx</code></td><td>顶部已选分镜条:展示选入编排的关键帧,点击进入工作台。</td></tr>
|
||||
<tr><td><code>web/components/storyboard-workbench.tsx</code></td><td>全屏分镜编排工作台:4 图槽、改造目标、时长、自动保存。</td></tr>
|
||||
<tr><td><code>web/components/storyboard-workbench.tsx</code></td><td>顶部分镜编排内嵌面板:4 图槽、改造目标、时长、自动保存。</td></tr>
|
||||
<tr><td><code>web/lib/api.ts</code></td><td>前端类型和 API client,是前后端数据契约镜像。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -636,7 +636,7 @@ api/main.py
|
||||
<div><strong>适合怎么描述</strong><span>“选好的分镜如何按时间序组织,以及如何进入具体分镜编排”。</span></div>
|
||||
</div>
|
||||
<div class="flow-row">
|
||||
<div><strong>你看到的区域</strong><span>分镜头编排工作台</span></div>
|
||||
<div><strong>你看到的区域</strong><span>顶部分镜头编排下拉面板</span></div>
|
||||
<div><strong>主要源码</strong><span><code>StoryboardWorkbench</code>;保存到 <code>frame.storyboard</code>;接口 <code>PUT /storyboard</code>。</span></div>
|
||||
<div><strong>适合怎么描述</strong><span>“每个分镜需要哪些图片槽、哪些改造说明,如何为视频生成做准备”。</span></div>
|
||||
</div>
|
||||
@@ -813,7 +813,7 @@ api/main.py
|
||||
</div>
|
||||
<div class="todo-item">
|
||||
<h3>改分镜工作台</h3>
|
||||
<p>“我在全屏分镜编排工作台,每个分镜需要哪些槽位、字段如何命名、保存后如何传给后续生成视频。”</p>
|
||||
<p>“我在顶部分镜头编排下拉面板,每个分镜需要哪些槽位、字段如何命名、保存后如何传给后续生成视频。”</p>
|
||||
</div>
|
||||
<div class="todo-item">
|
||||
<h3>改数据/接口</h3>
|
||||
@@ -830,6 +830,18 @@ api/main.py
|
||||
<h2>变更记录</h2>
|
||||
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||
<div class="changelog">
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-13 · 分镜头编排工作台改为内嵌下拉</h3>
|
||||
<span class="tag violet">StoryboardWorkbench</span>
|
||||
<span class="tag violet">StoryboardBar</span>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>元素改造节点等入口仍会打开 <code>fixed inset-0</code> 的全屏 <code>StoryboardWorkbench</code>,用户感觉像跳转页面。</p>
|
||||
<p><strong>改动:</strong>移除 <code>StoryboardWorkbench</code> 的 portal 全屏承载方式,改为渲染在顶部分镜栏下方;所有“打开编排”入口只展开这个内嵌区域。</p>
|
||||
<p><strong>影响:</strong><code>web/components/storyboard-workbench.tsx</code>、<code>web/components/storyboard-bar.tsx</code>、<code>web/app/page.tsx</code>、<code>web/components/nodes/index.tsx</code>。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-13 · 钉住面板停靠到分镜头编排边缘</h3>
|
||||
@@ -849,8 +861,8 @@ api/main.py
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>顶部 <code>StoryboardBar</code> 的“进入编排”和分镜缩略图点击会打开全屏 <code>StoryboardWorkbench</code>,打断当前画布流程。</p>
|
||||
<p><strong>改动:</strong>顶部按钮改为“展开编排”,只下拉展示当前分镜列表;缩略图点击只聚焦该分镜,不再触发全屏跳转。</p>
|
||||
<p><strong>影响:</strong><code>web/components/storyboard-bar.tsx</code>、<code>web/app/page.tsx</code>;全屏工作台仍保留给其他明确入口。</p>
|
||||
<p><strong>改动:</strong>顶部按钮改为“展开编排”,只下拉展示当前分镜列表;缩略图点击只聚焦该分镜,不再触发全屏跳转。后续已把工作台整体改成内嵌下拉,见上方最新记录。</p>
|
||||
<p><strong>影响:</strong><code>web/components/storyboard-bar.tsx</code>、<code>web/app/page.tsx</code>。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
|
||||
@@ -408,12 +408,27 @@ export default function Home() {
|
||||
|
||||
{/* 右区:顶部 storyboard bar + DAG 节点流图 */}
|
||||
<section className="relative flex-1 min-h-0 flex flex-col">
|
||||
<StoryboardBar
|
||||
job={job}
|
||||
selectedFrames={selectedFrames}
|
||||
focusedFrame={storyboardFrame}
|
||||
onFocusFrame={setStoryboardFrame}
|
||||
/>
|
||||
<div data-storyboard-dock="true" className="relative z-20 flex-shrink-0">
|
||||
<StoryboardBar
|
||||
job={job}
|
||||
selectedFrames={selectedFrames}
|
||||
focusedFrame={storyboardFrame}
|
||||
onFocusFrame={setStoryboardFrame}
|
||||
onOpenWorkbench={(idx?: number) => {
|
||||
if (typeof idx === "number") setStoryboardFrame(idx)
|
||||
setWorkbenchOpen(true)
|
||||
}}
|
||||
/>
|
||||
<StoryboardWorkbench
|
||||
job={job}
|
||||
selectedFrames={selectedFrames}
|
||||
open={workbenchOpen}
|
||||
onClose={() => setWorkbenchOpen(false)}
|
||||
onJobUpdate={setJob as any}
|
||||
clipboard={clipboard}
|
||||
focusedFrame={storyboardFrame}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative flex-1 min-h-0">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
@@ -446,17 +461,6 @@ export default function Home() {
|
||||
onAddFrame={handleAddManualFrame}
|
||||
/>
|
||||
|
||||
{/* 分镜头编排工作台 — 全屏覆盖 DAG */}
|
||||
<StoryboardWorkbench
|
||||
job={job}
|
||||
selectedFrames={selectedFrames}
|
||||
open={workbenchOpen}
|
||||
onClose={() => setWorkbenchOpen(false)}
|
||||
onJobUpdate={setJob as any}
|
||||
clipboard={clipboard}
|
||||
focusedFrame={storyboardFrame}
|
||||
/>
|
||||
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -37,8 +37,8 @@ export interface NodeData {
|
||||
onDeleteFrame?: (idx: number) => void // 删整张关键帧
|
||||
onDeleteGenerated?: (frameIdx: number, genId: string) => void // 删单张生成图
|
||||
onOpenStoryboard?: (frameIdx: number) => void // 打开分镜头编排专属面板
|
||||
onOpenWorkbench?: (frameIdx?: number) => void // 打开全屏分镜编排工作台
|
||||
onCopyImage?: (ref: ImageRef) => void // 复制图片到全局剪贴板(粘贴到分镜头编排工作台插槽)
|
||||
onOpenWorkbench?: (frameIdx?: number) => void // 展开顶部分镜编排内嵌面板
|
||||
onCopyImage?: (ref: ImageRef) => void // 复制图片到全局剪贴板(粘贴到分镜头编排插槽)
|
||||
}
|
||||
|
||||
/* ---- 状态映射工具 ---- */
|
||||
@@ -522,8 +522,9 @@ export function KeyframePanelNode({ data }: any) {
|
||||
|
||||
const getStoryboardDockTop = () => {
|
||||
if (typeof window === "undefined") return 64
|
||||
const dock = document.querySelector<HTMLElement>('[data-storyboard-dock="true"]')
|
||||
const bar = document.querySelector<HTMLElement>('[data-storyboard-bar="true"]')
|
||||
const bottom = bar?.getBoundingClientRect().bottom ?? 52
|
||||
const bottom = (dock ?? bar)?.getBoundingClientRect().bottom ?? 52
|
||||
return Math.max(56, Math.min(window.innerHeight - 120, bottom + 10))
|
||||
}
|
||||
|
||||
@@ -535,7 +536,8 @@ export function KeyframePanelNode({ data }: any) {
|
||||
}
|
||||
|
||||
syncDock()
|
||||
const bar = document.querySelector<HTMLElement>('[data-storyboard-bar="true"]')
|
||||
const bar = document.querySelector<HTMLElement>('[data-storyboard-dock="true"]')
|
||||
?? document.querySelector<HTMLElement>('[data-storyboard-bar="true"]')
|
||||
let observer: ResizeObserver | null = null
|
||||
if (bar && "ResizeObserver" in window) {
|
||||
observer = new ResizeObserver(syncDock)
|
||||
|
||||
@@ -9,9 +9,10 @@ interface Props {
|
||||
selectedFrames: Set<number>
|
||||
focusedFrame: number | null
|
||||
onFocusFrame: (idx: number | null) => void
|
||||
onOpenWorkbench?: (frameIdx?: number) => void
|
||||
}
|
||||
|
||||
export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame }: Props) {
|
||||
export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame, onOpenWorkbench }: Props) {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
useEffect(() => setMounted(true), [])
|
||||
@@ -64,8 +65,10 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame
|
||||
<button
|
||||
onClick={() => {
|
||||
if (frames.length === 0) return
|
||||
if (focusedFrame === null) onFocusFrame(frames[0].index)
|
||||
const nextFrame = focusedFrame ?? frames[0].index
|
||||
if (focusedFrame === null) onFocusFrame(nextFrame)
|
||||
setCollapsed(false)
|
||||
onOpenWorkbench?.(nextFrame)
|
||||
}}
|
||||
disabled={frames.length === 0}
|
||||
className="text-[11px] px-2.5 py-1 rounded-md bg-gradient-to-r from-violet-500 to-pink-500 hover:from-violet-400 hover:to-pink-400 text-white inline-flex items-center gap-1 disabled:opacity-40 disabled:cursor-not-allowed font-medium shadow"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client"
|
||||
import { useEffect, useState, useRef, type ReactNode } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import { X, LayoutGrid, Loader2, Check, Wand2 } from "lucide-react"
|
||||
import {
|
||||
type Job, type StoryboardScene, type ImageRef,
|
||||
@@ -94,9 +93,9 @@ export function StoryboardWorkbench({ job, selectedFrames, open, onClose, onJobU
|
||||
|
||||
const aspect = job.height > 0 ? `${job.width}/${job.height}` : "9/16"
|
||||
|
||||
return createPortal(
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[200] bg-black/92 backdrop-blur-xl flex flex-col"
|
||||
className="relative z-20 h-[min(680px,calc(100vh-170px))] flex-shrink-0 border-b border-white/10 bg-black/90 backdrop-blur-xl flex flex-col shadow-2xl"
|
||||
style={{ animation: "drawer-in 0.2s cubic-bezier(0.32, 0.72, 0, 1)" }}
|
||||
>
|
||||
{/* Header */}
|
||||
@@ -117,9 +116,9 @@ export function StoryboardWorkbench({ job, selectedFrames, open, onClose, onJobU
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="h-8 px-3 rounded-md bg-white/10 hover:bg-white/20 text-white inline-flex items-center gap-1.5 text-[12px]"
|
||||
title="返回 DAG (Esc)"
|
||||
title="收起编排 (Esc)"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" /> 返回 DAG
|
||||
<X className="h-3.5 w-3.5" /> 收起
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -338,10 +337,9 @@ export function StoryboardWorkbench({ job, selectedFrames, open, onClose, onJobU
|
||||
|
||||
{/* 底部快捷 */}
|
||||
<footer className="border-t border-white/10 px-5 py-1.5 text-[10px] text-white/40 font-mono text-center bg-black/40">
|
||||
ESC 返回 DAG · 字段变更自动保存
|
||||
ESC 收起编排 · 字段变更自动保存
|
||||
</footer>
|
||||
</div>,
|
||||
document.body,
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user