auto-save 2026-05-14 07:45 (~6)

This commit is contained in:
2026-05-14 07:45:39 +08:00
parent c263af24fb
commit f1220fc957
6 changed files with 45 additions and 20 deletions

View File

@@ -1,19 +1,5 @@
{
"entries": [
{
"files_changed": 1,
"hash": "f8cd466",
"message": "auto-save 2026-05-12 21:50 (~1)",
"ts": "2026-05-12T21:50:16+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "e6ef193",
"message": "auto-save 2026-05-12 21:55 (~1)",
"ts": "2026-05-12T21:56:09+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "1fc0ae9",
@@ -3335,6 +3321,19 @@
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 2 项未提交变更 · 最近提交auto-save 2026-05-14 07:34 (~1)",
"files_changed": 2
},
{
"ts": "2026-05-14T07:40:07+08:00",
"type": "commit",
"message": "auto-save 2026-05-14 07:39 (~4)",
"hash": "c263af2",
"files_changed": 4
},
{
"ts": "2026-05-13T23:43:15Z",
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 2 项未提交变更 · 最近提交auto-save 2026-05-14 07:39 (~4)",
"files_changed": 2
}
]
}

View File

@@ -625,7 +625,7 @@ api/main.py
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>关键帧素材审核面板</span></div>
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、场景图、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考,产品融合页左侧改为 6 行镜头表产品图、白底人物图、人物图上的产品区域、场景图和描述词一一对应右侧承载当前镜头秒数、GPT Image 2 / Seedance 固定模型、AI 描述草稿、单条生成和批量排队。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 <code>cleanupFrame</code><code>addElement</code><code>generateSubjectAssets</code><code>generateSceneAsset</code><code>listProductLibrary</code><code>copyProductLibraryAsset</code><code>createProductFusionGuide</code></span></div>
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;按“原图/清洗、主体资产、场景图、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考,产品融合页左侧改为 6 行镜头表:产品图、白底人物图、人物图上的产品区域、场景图和描述词一一对应;产品融合槽位的“粘贴”优先使用应用内 <code>clipboard</code>,也支持选中槽位后 Cmd+V 粘贴系统图片。右侧承载当前镜头秒数、GPT Image 2 / Seedance 固定模型、AI 描述草稿、单条生成和批量排队。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 <code>cleanupFrame</code><code>addElement</code><code>generateSubjectAssets</code><code>generateSceneAsset</code><code>listProductLibrary</code><code>copyProductLibraryAsset</code><code>createProductFusionGuide</code><code>generateProductFusionDescriptions</code></span></div>
<div><strong>适合怎么描述</strong><span>“这一组关键帧如何共同生成一个统一主体包;某张关键帧的水印、去主体场景图、产品融合镜头组和质量风险应该如何审核”。</span></div>
</div>
<div class="flow-row">
@@ -765,6 +765,7 @@ SubjectAsset {
<tr><td>产品图库</td><td><code>GET /product-library/skg</code></td><td><code>listProductLibrary</code></td><td>读取内置 SKG 白底图库 manifest返回产品标题、品类、尺寸、白底评分和预览图 URL。</td></tr>
<tr><td>产品图入库到 job</td><td><code>POST /jobs/{id}/assets/product-library</code></td><td><code>copyProductLibraryAsset</code></td><td>把一个内置产品图库条目复制为当前 job 的普通 asset返回 <code>ImageRef(kind="asset")</code>,用于画面工作台产品融合和分镜产品参考组。</td></tr>
<tr><td>产品融合引导图</td><td><code>POST /jobs/{id}/product-fusion/guide</code></td><td><code>createProductFusionGuide</code></td><td>读取产品图和白底人物图,按用户在人物图上画出的 <code>product_region</code> 合成一张位置引导图;前端固定显示图片模型为 GPT Image 2返回普通 <code>asset</code> 作为 Seedance 首帧。</td></tr>
<tr><td>产品融合描述词</td><td><code>POST /jobs/{id}/product-fusion/descriptions</code></td><td><code>generateProductFusionDescriptions</code></td><td>为 6 行产品融合镜头生成动作描述草稿;有 LLM 配置时用 <code>REWRITE_MODEL</code> 生成 JSON无配置或失败时回退到本地镜头模板。</td></tr>
<tr><td>分镜保存</td><td><code>PUT /frames/{idx}/storyboard</code></td><td><code>updateStoryboard</code></td><td>保存 4 图槽、时长和改造说明。</td></tr>
<tr><td>生图</td><td><code>POST /frames/{idx}/generate</code></td><td><code>generateImage</code></td><td>基于关键帧或已选生成图做 image-to-image目前可用。</td></tr>
</tbody>
@@ -883,10 +884,22 @@ SubjectAsset {
<div class="body">
<p><strong>问题:</strong>只把产品图作为参考图无法解决尺寸和位置融合,模型不知道产品应该放在人物或场景里的哪个区域。</p>
<p><strong>改动:</strong><code>FrameLightbox</code> 的“产品融合”页改为 6 行镜头组。每行绑定产品图、白底人物图、手动画出的产品区域、场景图、描述词和视频秒数;图片槽支持上传和粘贴,产品图也可从内置 SKG 白底图库选用。右侧固定显示图片模型 <code>GPT Image 2</code> 和视频模型 <code>Seedance</code>,支持 AI 草拟 6 条动作描述、单条生成和批量排队。</p>
<p><strong>后端:</strong>新增 <code>POST /jobs/{job_id}/product-fusion/guide</code>。它把产品图按 <code>product_region</code> 合成到白底人物图上,生成普通 <code>asset</code> 引导图;前端再把引导图作为 Seedance 首帧,并把产品图、人物图、场景图作为参考图提交。</p>
<p><strong>后端:</strong>新增 <code>POST /jobs/{job_id}/product-fusion/guide</code><code>POST /jobs/{job_id}/product-fusion/descriptions</code>。前者把产品图按 <code>product_region</code> 合成到白底人物图上,生成普通 <code>asset</code> 引导图;后者用 LLM 或本地模板生成 6 条动作描述草稿。前端再把引导图作为 Seedance 首帧,并把产品图、人物图、场景图作为参考图提交。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>web/lib/api.ts</code><code>web/app/page.tsx</code><code>web/components/lightbox.tsx</code><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 产品融合槽位接入应用内剪贴板</h3>
<span class="tag violet">FrameLightbox</span>
<span class="tag blue">Clipboard</span>
</header>
<div class="body">
<p><strong>问题:</strong>卡片上的“复制”写入的是应用内 <code>ImageRef</code> 剪贴板,而产品融合槽位只监听系统剪贴板文件,导致复制后的素材无法在产品融合里粘贴。</p>
<p><strong>改动:</strong><code>NodeData</code><code>FrameLightbox</code> 新增 <code>clipboard</code> 传递链路;产品融合三类槽位的“粘贴”按钮优先使用应用内剪贴板,应用剪贴板为空时再提示可选中槽位后 Cmd+V 粘贴系统图片。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>web/components/dashboard.tsx</code><code>web/components/lightbox.tsx</code><code>docs/source-analysis.html</code></p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 增加产品融合和 SKG 内置白底图库</h3>

View File

@@ -650,11 +650,12 @@ export default function Home() {
if (typeof idx === "number") setStoryboardFrame(idx)
setWorkbenchOpen(true)
},
clipboard,
onCopyImage: handleCopyImage,
onGenerateProductFusionVideo: handleGenerateProductFusionVideo,
pinnedNodes,
onToggleNodePin: handleToggleNodePin,
}), [job, jobs, activeJobId, submitting, analyzing, frameTargets, frameCounts, frameQualities, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, framePanelDock, videoPanelJobId, videoPanelScale, videoPanelDock, handleSubmit, handleUpload, handleAnalyze, handleAnalyzeJob, handleFrameTargetChange, handleFrameCountChange, handleFrameQualityChange, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleAddManualFrame, handleAddManualFrameForJob, handleOpenVideoPanel, handleVideoPanelScaleChange, handleSwitchJob, setJob, handleDeleteJob, handleDeleteFrame, handleDeleteFrameForJob, handleDeleteGenerated, handleDeleteVideo, handleDeleteCutout, handleCopyImage, handleGenerateProductFusionVideo, pinnedNodes, handleToggleNodePin])
}), [job, jobs, activeJobId, submitting, analyzing, frameTargets, frameCounts, frameQualities, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, framePanelDock, videoPanelJobId, videoPanelScale, videoPanelDock, handleSubmit, handleUpload, handleAnalyze, handleAnalyzeJob, handleFrameTargetChange, handleFrameCountChange, handleFrameQualityChange, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleAddManualFrame, handleAddManualFrameForJob, handleOpenVideoPanel, handleVideoPanelScaleChange, handleSwitchJob, setJob, handleDeleteJob, handleDeleteFrame, handleDeleteFrameForJob, handleDeleteGenerated, handleDeleteVideo, handleDeleteCutout, clipboard, handleCopyImage, handleGenerateProductFusionVideo, pinnedNodes, handleToggleNodePin])
// 用 useNodesState 让 ReactFlow 自己管位置(避免轮询时重置 drag
const savedSizes = useMemo(() => loadNodeSizes(), [])

View File

@@ -327,6 +327,7 @@ export const Dashboard = forwardRef<DashboardHandle, Props>(function Dashboard({
data.onCloseExpandedFrame()
setExpanded(new Set([key]))
}}
clipboard={data.clipboard}
onCopyImage={data.onCopyImage}
onGenerateProductFusionVideo={data.onGenerateProductFusionVideo}
/>

View File

@@ -21,6 +21,7 @@ interface Props {
onToggleSelect: (idx: number) => void
onJobUpdate?: (job: Job) => void
onSwitchPanel?: (key: string) => void
clipboard?: ImageRef | null
onCopyImage?: (ref: ImageRef) => void
onGenerateProductFusionVideo?: (frameIdx: number, shot: ProductFusionShot) => Promise<void> | void
embedded?: boolean
@@ -131,7 +132,7 @@ const normalizeFusionShots = (shots?: ProductFusionShot[] | null): ProductFusion
return base.map((item, i) => ({ ...item, ...(shots[i] ?? {}), id: shots[i]?.id || item.id }))
}
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, onSwitchPanel, onCopyImage, onGenerateProductFusionVideo, embedded = false }: Props) {
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, onSwitchPanel, clipboard, onCopyImage, onGenerateProductFusionVideo, embedded = false }: Props) {
const [describing, setDescribing] = useState(false)
const [cleaningFrameIds, setCleaningFrameIds] = useState<Set<number>>(new Set())
const [batchCleaning, setBatchCleaning] = useState(false)
@@ -982,10 +983,18 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
<button
type="button"
onClick={() => {
if (clipboard) {
assignFusionImage(slot, clipboard)
toast.success(`已粘贴到「${label}」:${clipboard.label || "剪贴板图片"}`)
return
}
setFusionUploadTarget(slot)
toast.message(`已选中「${label}」槽位,现在可 Cmd+V 粘贴图片`)
toast.message(`应用剪贴板为空;已选中「${label}」槽位,现在可 Cmd+V 粘贴系统图片`)
}}
className="rounded bg-white/10 px-1.5 py-0.5 text-[9px] text-white/60 hover:bg-white/18 hover:text-white"
className={`rounded px-1.5 py-0.5 text-[9px] hover:bg-white/18 hover:text-white ${
clipboard ? "bg-violet-500/60 text-white" : "bg-white/10 text-white/60"
}`}
title={clipboard ? `粘贴应用剪贴板:${clipboard.label || "图片"}` : "应用剪贴板为空;可先点槽位后 Cmd+V 粘贴系统图片"}
>
</button>

View File

@@ -72,6 +72,7 @@ export interface NodeData {
onDeleteCutout?: (frameIdx: number, elementId: string, cutoutId: string) => void // 删元素提取图
onOpenStoryboard?: (frameIdx: number) => void // 打开分镜头编排专属面板
onOpenWorkbench?: (frameIdx?: number) => void // 展开顶部分镜编排内嵌面板
clipboard?: ImageRef | null
onCopyImage?: (ref: ImageRef) => void // 复制图片到全局剪贴板(粘贴到分镜头编排插槽)
onGenerateProductFusionVideo?: (frameIdx: number, shot: ProductFusionShot) => Promise<void> | void
pinnedNodes?: Set<string> // 已钉住的节点 id 集合 — 钉住后位置 + 尺寸锁定
@@ -1957,6 +1958,7 @@ export function KeyframePanelNode({ data }: any) {
onChange={d.onExpandFrame}
onToggleSelect={d.onToggleFrame}
onJobUpdate={d.onJobUpdate}
clipboard={d.clipboard}
onCopyImage={d.onCopyImage}
onGenerateProductFusionVideo={d.onGenerateProductFusionVideo}
/>