diff --git a/.memory/worklog.json b/.memory/worklog.json index 0fef4c2..844d322 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,26 +1,5 @@ { "entries": [ - { - "files_changed": 3, - "hash": "ca0d6f1", - "message": "auto-save 2026-05-12 20:04 (~3)", - "ts": "2026-05-12T20:04:48+08:00", - "type": "commit" - }, - { - "files_changed": 3, - "hash": "138d68d", - "message": "auto-save 2026-05-12 20:10 (~3)", - "ts": "2026-05-12T20:10:22+08:00", - "type": "commit" - }, - { - "files_changed": 1, - "hash": "ae2b4bc", - "message": "auto-save 2026-05-12 21:21 (~1)", - "ts": "2026-05-12T21:21:43+08:00", - "type": "commit" - }, { "files_changed": 1, "hash": "d2d232a", @@ -3339,6 +3318,25 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 07:17 (~1)", "files_changed": 1 + }, + { + "ts": "2026-05-14T07:23:13+08:00", + "type": "commit", + "message": "auto-save 2026-05-14 07:23 (~4)", + "hash": "a6773a8", + "files_changed": 4 + }, + { + "ts": "2026-05-13T23:23:14Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 07:23 (~4)", + "files_changed": 1 + }, + { + "ts": "2026-05-13T23:28:52Z", + "type": "session-heartbeat", + "message": "Codex 会话活跃 · 最近命令:codex · 6 项未提交变更 · 最近提交:auto-save 2026-05-14 07:23 (~4)", + "files_changed": 6 } ] } diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 3052190..f7d3d27 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -556,8 +556,8 @@
3

清洗水印

对关键帧做全图或区域清洗,必要时应用为当前参考图。

4

主体识别

识别场景和主体候选,只是候选,不应锁死。

5

素材准备

清洗关键帧,把多张关键帧作为同一主体的参考,先重绘六张标准站立主体资产图,再按关键帧生成多个去主体、相似或换风格场景图。

-
6

分镜改造

把参考主体、场景、动作和 SKG 产品放入分镜结构;SKG 产品可从内置白底图库直接加入产品参考组。

-
7

生成视频

用分镜 4 图槽、改造目标和时长调用 Seedance / Kling / Veo 3 生视频 API,结果回写到画面工作台节点。

+
6

分镜改造

把参考主体、场景、动作和 SKG 产品放入分镜结构;产品融合使用 6 行镜头组,每行绑定产品图、白底人物图、产品区域、场景图、描述词和秒数。

+
7

生成视频

普通分镜可调用 Seedance / Kling / Veo 3;产品融合固定用 GPT Image 2 生成位置引导图,再用 Seedance 按秒数生成视频,结果回写到画面工作台节点。

8

合成成品

片段、字幕、配音、转场合成最终 mp4。当前未实现。

@@ -571,7 +571,7 @@ web/app/page.tsx产品工作台主状态:jobs、activeJobId、selectedFrames、clipboard、ReactFlow 节点和边;负责打开/找回画布工作面板。 web/components/nodes/index.tsxDAG 节点定义:Input、VisualLab、Audio、Compose,以及画布工作面板 KeyframePanel / VideoFramePanel;旧 Keyframe/Storyboard/VideoGen 组件保留但不再挂主画布。 - web/components/lightbox.tsx关键帧素材准备面板:清洗、统一主体候选、参考帧网格、六张主体重绘图、每帧去主体场景图、产品融合和审核。 + web/components/lightbox.tsx关键帧素材准备面板:清洗、统一主体候选、参考帧网格、六张主体重绘图、每帧去主体场景图、6 行产品融合镜头组和审核。 web/components/product-library-picker.tsxSKG 内置白底产品图库选择器:搜索、品类筛选、预览尺寸,并把库内图片复制为当前 job 的 assetweb/components/storyboard-bar.tsx顶部分镜编排条:展示选入编排的关键帧,并作为唯一分镜导航。 web/components/storyboard-workbench.tsx顶部分镜编排条下方的明细区:4 图槽、改造目标、时长、自动保存。 @@ -625,8 +625,8 @@ api/main.py
你看到的区域关键帧素材审核面板
-
主要源码FrameLightbox;按“原图/清洗、主体资产、场景图、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考,产品融合页左侧接入内置 SKG 白底图库;右侧承载当前页操作、状态和结果。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt,再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 cleanupFrameaddElementgenerateSubjectAssetsgenerateSceneAssetlistProductLibrarycopyProductLibraryAsset
-
适合怎么描述“这一组关键帧如何共同生成一个统一主体包;某张关键帧的水印、去主体场景图、SKG 产品融合参考和质量风险应该如何审核”。
+
主要源码FrameLightbox;按“原图/清洗、主体资产、场景图、产品融合、审核”五个页签组织;左侧只放主图/框选画布,但主体资产页左侧改为全部已清洗/已选参考帧网格,场景图页左侧显示全部关键帧并可勾选场景参考,产品融合页左侧改为 6 行镜头表:产品图、白底人物图、人物图上的产品区域、场景图和描述词一一对应;右侧承载当前镜头秒数、GPT Image 2 / Seedance 固定模型、AI 描述草稿、单条生成和批量排队。主体资产页只确认一个统一主体,后端按参考重绘六张纯背景、占满画面的标准站立主体图;场景图依赖主体资产,右侧通过地点、生成方式、风格和参考要素拼出可编辑 prompt,再按当前关键帧生成去主体原场景、相似新场景或同构换风格。相关接口包括 cleanupFrameaddElementgenerateSubjectAssetsgenerateSceneAssetlistProductLibrarycopyProductLibraryAssetcreateProductFusionGuide
+
适合怎么描述“这一组关键帧如何共同生成一个统一主体包;某张关键帧的水印、去主体场景图、产品融合镜头组和质量风险应该如何审核”。
你看到的区域顶部分镜头编排下拉面板
@@ -706,6 +706,22 @@ SubjectAsset { white_score, source_path, tags[] +} +
+
+

ProductFusionShot

+

产品融合镜头组的单行数据。每个关键帧最多 6 行,产品图、人物图、产品区域、场景图、动作描述和秒数一一对应;生成时先创建融合引导图,再提交 Seedance。

+
ProductFusionShot {
+  id,
+  product_image,
+  person_image,
+  product_region: { x, y, w, h },
+  scene_image,
+  action_text,
+  duration,
+  image_model: gpt-image-2,
+  video_model: seedance,
+  guide_image
 }
@@ -748,6 +764,7 @@ SubjectAsset { 场景资产POST /frames/{idx}/scene-assetgenerateSceneAsset在统一主体资产之后,按当前关键帧生成去主体背景板;请求包含 scene_modescene_stylepromptsource_frame_indices,可用左侧选择的参考帧 + 右侧关键词生成原场景补背景、相似新场景或同构换风格,保留历史版本用于人工审核。 产品图库GET /product-library/skglistProductLibrary读取内置 SKG 白底图库 manifest,返回产品标题、品类、尺寸、白底评分和预览图 URL。 产品图入库到 jobPOST /jobs/{id}/assets/product-librarycopyProductLibraryAsset把一个内置产品图库条目复制为当前 job 的普通 asset,返回 ImageRef(kind="asset"),用于画面工作台产品融合和分镜产品参考组。 + 产品融合引导图POST /jobs/{id}/product-fusion/guidecreateProductFusionGuide读取产品图和白底人物图,按用户在人物图上画出的 product_region 合成一张位置引导图;前端固定显示图片模型为 GPT Image 2,返回普通 asset 作为 Seedance 首帧。 分镜保存PUT /frames/{idx}/storyboardupdateStoryboard保存 4 图槽、时长和改造说明。 生图POST /frames/{idx}/generategenerateImage基于关键帧或已选生成图做 image-to-image,目前可用。 @@ -857,6 +874,19 @@ SubjectAsset {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-14 · 产品融合改为 6 行区域约束镜头组

+ FrameLightbox + Seedance +
+
+

问题:只把产品图作为参考图无法解决尺寸和位置融合,模型不知道产品应该放在人物或场景里的哪个区域。

+

改动:FrameLightbox 的“产品融合”页改为 6 行镜头组。每行绑定产品图、白底人物图、手动画出的产品区域、场景图、描述词和视频秒数;图片槽支持上传和粘贴,产品图也可从内置 SKG 白底图库选用。右侧固定显示图片模型 GPT Image 2 和视频模型 Seedance,支持 AI 草拟 6 条动作描述、单条生成和批量排队。

+

后端:新增 POST /jobs/{job_id}/product-fusion/guide。它把产品图按 product_region 合成到白底人物图上,生成普通 asset 引导图;前端再把该引导图作为 Seedance 首帧,并把产品图、人物图、场景图作为参考图提交。

+

影响:api/main.pyweb/lib/api.tsweb/app/page.tsxweb/components/lightbox.tsxweb/components/nodes/index.tsxweb/components/dashboard.tsxdocs/source-analysis.html

+
+

2026-05-14 · 增加产品融合和 SKG 内置白底图库

diff --git a/web/app/page.tsx b/web/app/page.tsx index e524400..76f6910 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -18,8 +18,8 @@ import { import { ThemeToggle } from "@/components/theme-toggle" import { addManualFrame, analyzeJob, createJob, getJob, listJobs, uploadJob, deleteJob, deleteFrame, deleteGeneratedImage, - deleteGeneratedVideo, deleteCutout, generateStoryboardVideo, - type Job, type ImageRef, type StoryboardScene, type FrameExtractMode, type FrameExtractQuality, type FrameExtractTarget, + deleteGeneratedVideo, deleteCutout, generateStoryboardVideo, createProductFusionGuide, + type Job, type ImageRef, type ProductFusionShot, type StoryboardScene, type FrameExtractMode, type FrameExtractQuality, type FrameExtractTarget, } from "@/lib/api" const NODE_TYPES = { @@ -443,6 +443,60 @@ export default function Home() { } }, [job, selectedFrames, setJob]) + const handleGenerateProductFusionVideo = useCallback(async (frameIdx: number, shot: ProductFusionShot) => { + if (!job) return + const frame = job.frames.find((f) => f.index === frameIdx) + if (!frame) return + if (!shot.product_image || !shot.person_image || !shot.scene_image || !shot.product_region || !shot.action_text?.trim()) { + toast.error("产品融合镜头缺少产品图、人物图、区域、场景图或描述词") + return + } + const duration = shot.duration && shot.duration > 0 ? shot.duration : 5 + const labelOf = (ref?: ImageRef | null, fallback = "未提供") => ref?.label || fallback + try { + toast.info(`生成融合引导图 · GPT Image 2 位置约束 · 镜头 ${shot.id || ""}`) + const guideRef = await createProductFusionGuide(job.id, { + ...shot, + image_model: "gpt-image-2", + video_model: "seedance", + }) + const region = shot.product_region + const prompt = [ + `竖屏 9:16,${duration.toFixed(1)} 秒,Seedance 产品融合视频。`, + "图片模型固定为 GPT Image 2:已根据白底人物图和手动画框生成产品融合引导图;引导图是产品尺寸、位置、贴合关系和起始构图的最高优先级参考。", + "视频模型固定为 Seedance:生成单镜头连续视频,不跳切,不换主体,不改变产品身份。", + `产品区域:x=${region.x.toFixed(3)}, y=${region.y.toFixed(3)}, w=${region.w.toFixed(3)}, h=${region.h.toFixed(3)}。产品只能在这个框对应的人物/身体/手部区域内融合,不能漂移到其他位置,也不能明显超出框架。`, + `产品图:${labelOf(shot.product_image, "SKG 白底产品图")}。严格保持 SKG 产品外观、颜色、材质、U 形结构、按摩触点、按键和比例。`, + `白底人物图:${labelOf(shot.person_image, "人物姿态参考")}。人物姿态、手部接触点和产品佩戴关系以这张图为准。`, + `场景图:${labelOf(shot.scene_image, "场景参考")}。背景、空间、光线和气氛以这张图为准,但不要改变产品框内位置。`, + `动作描述:${shot.action_text.trim()}`, + "融合要求:产品必须按引导图位置自然贴合人物或手部,尺寸可信,透视一致,边缘清晰,不能悬浮、穿帮、融化、扭曲或变成其他物体。", + "场景要求:把白底人物姿态自然放入场景图的环境中,光线方向和阴影要统一,背景不要出现水印、平台 UI、字幕或竞品包装。", + "商业质感:真实拍摄感、干净高级、产品清楚可辨、人物动作自然、镜头稳定。", + "禁止:文字、水印、随机品牌、非 SKG 产品、医学治疗承诺、夸张病症、恐怖元素、产品位置漂移、产品超过指定融合区域。", + ].join("\n") + const updated = await generateStoryboardVideo(job.id, frameIdx, { + prompt, + duration, + first_image: guideRef, + last_image: null, + product_images: [shot.product_image, shot.person_image, shot.scene_image].filter(Boolean) as ImageRef[], + subject_image: shot.person_image, + scene_image: shot.scene_image, + product_image: shot.product_image, + action_image: guideRef, + source_ref: null, + model: "seedance", + size: "720x1280", + }) + setJob(updated) + void navigator.clipboard?.writeText(prompt).catch(() => {}) + toast.success("产品融合视频已进入 Video Gen 队列") + } catch (e) { + toast.error("产品融合生成失败:" + (e instanceof Error ? e.message : String(e))) + } + }, [job, setJob]) + // 启动恢复:URL ?job=xxx,yyy 优先;否则从后端拉全部历史(按 mtime 倒序,最新放末尾) useEffect(() => { const params = new URLSearchParams(window.location.search) @@ -597,9 +651,10 @@ export default function Home() { setWorkbenchOpen(true) }, 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, 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, handleCopyImage, handleGenerateProductFusionVideo, pinnedNodes, handleToggleNodePin]) // 用 useNodesState 让 ReactFlow 自己管位置(避免轮询时重置 drag) const savedSizes = useMemo(() => loadNodeSizes(), []) diff --git a/web/components/dashboard.tsx b/web/components/dashboard.tsx index 4b74e92..279a76a 100644 --- a/web/components/dashboard.tsx +++ b/web/components/dashboard.tsx @@ -328,6 +328,7 @@ export const Dashboard = forwardRef(function Dashboard({ setExpanded(new Set([key])) }} onCopyImage={data.onCopyImage} + onGenerateProductFusionVideo={data.onGenerateProductFusionVideo} /> ) : ( renderSection(t.key) diff --git a/web/components/lightbox.tsx b/web/components/lightbox.tsx index 6640979..4650dbe 100644 --- a/web/components/lightbox.tsx +++ b/web/components/lightbox.tsx @@ -971,13 +971,25 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
{label} - +
+ + +
))} @@ -1380,30 +1392,82 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o )} {activeTab === "product" && ( -
-
产品融合目标
-
- {`frame -
- 分镜 {f.index + 1} - {f.timestamp.toFixed(2)}s + <> +
+
+
镜头 {activeFusionShot + 1} 设置
+ {fusionSaving ? "保存中" : "自动保存"}
-
-
-
-
图库来源
-
桌面 SKG 产品图 · gallery 白底筛选
+
+
+
图片模型
+
GPT Image 2
+
+
+
视频模型
+
Seedance
+
-
-
使用方式
-
复制产品图后,在画面工作台加入 SKG 产品参考组。
+ +