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