From ba202e43b7887fe1b2f9f67f680b2f0ad531cefc Mon Sep 17 00:00:00 2001 From: kang Date: Mon, 18 May 2026 00:12:58 +0800 Subject: [PATCH] auto-save 2026-05-18 00:12 (~3) --- .memory/worklog.json | 26 ++++++------ docs/source-analysis.html | 19 +++++++-- web/components/ad-recreation-board.tsx | 59 +++++++++++++++++++------- 3 files changed, 73 insertions(+), 31 deletions(-) diff --git a/.memory/worklog.json b/.memory/worklog.json index 4858c1b..e97aa39 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,18 +1,5 @@ { "entries": [ - { - "files_changed": 4, - "message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-15 15:10 (~2)", - "ts": "2026-05-15T07:14:46Z", - "type": "session-heartbeat" - }, - { - "files_changed": 4, - "hash": "7ee9ea2", - "message": "auto-save 2026-05-15 15:15 (~4)", - "ts": "2026-05-15T15:15:47+08:00", - "type": "commit" - }, { "files_changed": 10, "hash": "f7cc49a", @@ -3258,6 +3245,19 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 分支 main · 1 项未提交变更 · 最近提交:auto-save 2026-05-17 23:56 (~2)", "files_changed": 1 + }, + { + "ts": "2026-05-18T00:07:36+08:00", + "type": "commit", + "message": "auto-save 2026-05-18 00:07 (~3)", + "hash": "745f72d", + "files_changed": 3 + }, + { + "ts": "2026-05-17T16:08:31Z", + "type": "session-heartbeat", + "message": "Codex 会话活跃 · 最近命令:codex · 分支 main · 2 项未提交变更 · 最近提交:auto-save 2026-05-18 00:07 (~3)", + "files_changed": 2 } ] } diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 6a8bd3f..9139e22 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -589,7 +589,7 @@ web/next.config.mjsNext.js 构建配置:静态导出、图片不走优化、禁用开发环境左下角 Next Dev Indicator,并移除 Next 16 已不支持的 eslint 顶层配置,避免本地 dev 出现配置 Issue 提示。 web/app/globals.css全局主题变量、登录页视觉样式、ReactFlow 样式引用,以及本地开发态 nextjs-portal 遮挡隐藏规则。 web/app/page.tsx产品工作台主状态:jobs、activeJobId、生成任务状态;主渲染为全屏素材输入列 + 信息流广告复刻工作表;“开始”编排状态只负责在下载完成后自动触发 triggerTranscribe,不再默认触发抽帧、Vision 扫描或分镜初稿保存;底部吸附音频条不再从主界面渲染。 - web/components/ad-recreation-board.tsx信息流广告复刻工作表:左侧素材输入只负责链接/上传和任务切换,不再重复放横版原视频预览;右侧展示视频下载状态、默认折叠的文案依据,以及源视频工作区。音频解析结果改成默认折叠的辅助信息,展开后同一行看讲话人/节奏/背景音;主工作区左侧是按 9:16 显示的竖版原视频播放器,播放器内覆盖“当前点抽帧”,按当前播放秒数手动补参考帧;右侧上方是音频波形 / 切点参考,下方是逐句时间轴;下一行铺开“关键帧 / 相似主体”。音频波形用参考图式的连续灰色包络显示响度、停顿和密集爆点,顶部同时显示当前播放秒数、总时长和鼠标指针停点秒数。视频播放时通过 requestAnimationFrame 平滑驱动波形播放线,同时同步高亮并滚动当前句;点击音频波形或字幕行会跳转原视频时间。关键帧区的主入口是“自动抽帧 12 张”,一键按动作峰值目标重新抽取 12 张源视频参考帧,优先抓手势、表情变化、节奏点和镜头变化,缩略图按竖版完整比例显示不裁切并用更多列紧凑铺开,鼠标停留会通过固定浮层放大展示完整帧。“生成 6 视图”放在相似主体白底视图区,不和抽参考按钮平齐;如果用户没有勾选帧,默认把全部关键帧作为主体参考,勾选后只传已选帧;生成区可在“透明骨架 / 普通真人”之间切换,并可填写统一主体方向,例如年轻女性、更运动、更高级。前端调用 generateSubjectAssets 时按主体类型传 subject_style=transparent_humansource_actor,均使用 reconstruction_mode=similar;后端会把这些帧视为同一个主体的证据,并锁定同一性别表现、年龄段、体型、材质、风格和视觉身份,避免六视图出现男女性别、老少年龄或样式混杂。音频结果下方是信息流复刻分镜工作台:顶部产品参考区是“同一产品素材池”,不限量上传产品图,不做不同产品身份判断;上传原图推荐长边 1200-2000px、短边至少 600px,但后端会统一生成最长边 1600px、JPEG 92 的 AI 工作副本,并回显尺寸、自动转换和风险标注;上传后按“套在脖子上的 U 形肩颈按摩仪”进行同一产品批量识别,左/右按佩戴者身体左右、上/下按佩戴方向,额外标注内外侧、开口方向、局部结构点、背景类型、用途标签、生成风险和备注,用户只检查备注,鼠标悬停通过固定浮层显示大图预览,能盖过滚动容器和分镜框架;缺视角补图失败时保留重试入口。脚本区在分镜行上方提供“作者想法”和“整片改写”,每行新口播文案可直接编辑并可单段 AI 改写,分镜时间和原内容列压缩为窄摘要列,把横向空间留给新口播、画面规划和视频候选;生成本条视频时使用当前编辑后的新口播文案。每条音频分镜纵向排列,行内从左到右串起原内容、新口播文案、画面规划/产品融入和 6 个候选视频槽;候选视频槽在宽屏下一排显示 6 个竖版预览,避免前面空旷、后面拥挤。单条生成会从全局选中关键帧或 12 张关键帧中取最贴近本句时间点的参考帧。单条生成会从产品素材池按分镜角色、视角优先级、用途标签、置信度和风险自动挑选最多 6 张相关产品图,不会把全部产品图提交给生视频模型,然后把产品坐标系、视角标注、方向、结构点和风险写入 Seedance 提示。ModelTrace 会在音频解析、产品识别/补图、相似主体 6 视图、脚本改写和单条生视频入口旁直接展示模型名;点击后用固定浮层展示模型链路、输入输出和回退逻辑。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。 + web/components/ad-recreation-board.tsx信息流广告复刻工作表:左侧素材输入只负责链接/上传和任务切换,不再重复放横版原视频预览;右侧展示视频下载状态、默认折叠的文案依据,以及源视频工作区。音频解析结果改成默认折叠的辅助信息,展开后同一行看讲话人/节奏/背景音;主工作区左侧是按 9:16 显示的竖版原视频播放器,播放器内覆盖“当前点抽帧”,按当前播放秒数手动补参考帧;右侧上方是音频波形 / 切点参考,下方是逐句时间轴;下一行铺开“关键帧 / 相似主体”。音频波形用参考图式的连续灰色包络显示响度、停顿和密集爆点,顶部同时显示当前播放秒数、总时长和鼠标指针停点秒数。视频播放时通过 requestAnimationFrame 平滑驱动波形播放线,同时同步高亮并滚动当前句;点击音频波形或字幕行会跳转原视频时间。关键帧区的主入口是“自动抽帧 12 张”,一键按动作峰值目标重新抽取 12 张源视频参考帧,优先抓手势、表情变化、节奏点和镜头变化,缩略图按竖版完整比例显示不裁切并用更多列紧凑铺开,鼠标停留会通过固定浮层放大展示完整帧。“生成 6 视图”放在相似主体白底视图区,不和抽参考按钮平齐;如果用户没有勾选帧,默认把全部关键帧作为主体参考,勾选后只传已选帧;生成区可在“透明骨架 / 普通真人”之间切换,并可填写统一主体方向,例如年轻女性、更运动、更高级。前端调用 generateSubjectAssets 时按主体类型传 subject_style=transparent_humansource_actor,均使用 reconstruction_mode=similar;后端会把这些帧视为同一个主体的证据,并锁定同一性别表现、年龄段、体型、材质、风格和视觉身份,避免六视图出现男女性别、老少年龄或样式混杂。音频结果下方是信息流复刻分镜工作台:顶部产品参考区是“同一产品素材池”,不限量上传产品图,不做不同产品身份判断;上传原图推荐长边 1200-2000px、短边至少 600px,但后端会统一生成最长边 1600px、JPEG 92 的 AI 工作副本,并回显尺寸、自动转换和风险标注;上传后按“套在脖子上的 U 形肩颈按摩仪”进行同一产品批量识别,左/右按佩戴者身体左右、上/下按佩戴方向,额外标注内外侧、开口方向、局部结构点、背景类型、用途标签、生成风险和备注,用户只检查备注,鼠标悬停通过固定浮层显示大图预览,能盖过滚动容器和分镜框架;缺视角补图失败时保留重试入口。脚本区在分镜行上方提供“作者想法”和“整片改写”,每行新口播文案可直接编辑并可单段 AI 改写,分镜时间和原内容列压缩为窄摘要列,把横向空间留给新口播、画面规划和视频候选;生成本条视频时使用当前编辑后的新口播文案。每条音频分镜纵向排列,行内从左到右串起原内容、新口播文案、画面规划/产品融入和 6 个候选视频槽;候选视频槽在宽屏下一排显示 6 个竖版预览,避免前面空旷、后面拥挤。单条生成会从全局选中关键帧或 12 张关键帧中取最贴近本句时间点的参考帧。单条生成会从产品素材池按分镜角色、视角优先级、用途标签、置信度和风险自动挑选最多 6 张相关产品图,不会把全部产品图提交给生视频模型,然后把产品坐标系、视角标注、方向、结构点和风险写入 Seedance 提示。ModelTrace 会在音频解析、产品识别/补图、相似主体 6 视图、脚本改写和单条生视频入口旁直接展示模型名;其中相似主体 6 视图显示主体专用 gpt-image-2 / gpt-image-1.5 链路;点击后用固定浮层展示模型链路、输入输出和回退逻辑。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。 web/app/login/page.tsx生产登录页:访问账号/访问密钥表单、保持登录、错误/成功状态;当前只在原版 Digital Oasis 动态背景上叠加一个组合登录框,桌面端左侧是动态角色,右侧是图标化登录表单;面板左上角展示官网 SKG 字标和中文“营销内容工作台”系统标识。 web/app/login/layout.tsx登录路由专属 layout:覆盖全站默认网页标题和描述为空,避免 /login 继承工作台 metadata 后在页面源码里继续出现登录界面文字以外的文案。 web/components/login/oasis-canvas.tsx登录页全屏动态视觉层:用 iframe 直接承载下载包 web/public/oasis-source/index.html 的原 WebGPU / Three.js 草场源码;父级登录页只覆盖自己的文案和表单,并在捕获阶段把全局鼠标坐标同时用原生事件和 postMessage 转发给 iframe,避免登录面板或输入框遮挡时草地失去鼠标响应。 @@ -870,7 +870,7 @@ ProductRefStateItem { 网页登录POST /auth/loginGET /auth/checkPOST /auth/logoutweb/app/login/page.tsx、Nginx auth_request登录页提交账号密码到 /api/auth/login,后端设置 HttpOnly 会话 Cookie;生产 Nginx 对工作台和 /api//auth/check 做统一校验,未登录页面跳 /login/,API 返回 JSON 401。 - 运行配置 / 模型标注GET /healthgetRuntimeHealthModelTrace返回 models:ASR、本机 ASR、ASR fallback、翻译、改写、Vision、图像、图像 fallback、MiniMax TTS、视频别名和视频服务商。前端所有当前主路径里会调用模型的按钮旁显示模型名,点击弹出小窗口查看模型链路和输入输出逻辑;不返回 API Key 或敏感凭证。 + 运行配置 / 模型标注GET /healthgetRuntimeHealthModelTrace返回 models:ASR、本机 ASR、ASR fallback、翻译、改写、Vision、通用图像模型、主体 6 视图 GPT 图像模型、MiniMax TTS、视频别名和视频服务商。前端所有当前主路径里会调用模型的按钮旁显示模型名,点击弹出小窗口查看模型链路和输入输出逻辑;不返回 API Key 或敏感凭证。 历史列表GET /jobslistJobs所有 job 精简列表(id/url/status/thumbnail/mtime…),按 state.json mtime 倒序。前端 URL 无 ?job= 时拉它回填全部历史;带 limit 可截断。 创建任务POST /jobscreateJob提交 TK 链接,后台开始下载;前端“开始”队列会在 downloaded 后自动触发音频解析。 上传视频POST /jobs/uploaduploadJob保存 source.mp4,然后同样进入下载完成状态;当前上传后也加入第一步队列,下载完成后自动解析音频。 @@ -888,7 +888,7 @@ ProductRefStateItem { 应用清洗POST /cleanup/applyapplyCleanedFrame物理覆盖 frames/{idx}.jpg,并备份原图。 元素增改删POST/PATCH/DELETE /elementsaddElement/updateElement/deleteElement让用户修正 Vision 错误,避免候选结果锁死。 元素提取POST /elements/{element_id}/cutoutcutoutElement调用图像模型生成独立白底素材图,每次累积一张 cutout。 - 主体资产包POST /elements/{element_id}/subject-assetsgenerateSubjectAssets根据参考帧重新绘制一个统一主体资产包;前端默认把全部关键帧作为 source_frame_indices,如果用户手动选择了关键帧则只传已选帧,后端拼参考板。当前源视频工作区支持 subject_style=transparent_humansubject_style=source_actor 两种相似主体:透明骨架人会保持透明/半透明皮肤包裹可见白色骨架,普通真人会保持正常广告演员方向。两种模式都使用 reconstruction_mode=similar,最多读取 12 张参考帧,生成 6 张白底视图;后端会加身份锁定约束,统一性别表现、年龄段、体型、材质、风格和视觉身份,避免六视图混成不同人物。 + 主体资产包POST /elements/{element_id}/subject-assetsgenerateSubjectAssets根据参考帧重新绘制一个统一主体资产包;前端默认把全部关键帧作为 source_frame_indices,如果用户手动选择了关键帧则只传已选帧,后端拼参考板。当前源视频工作区支持 subject_style=transparent_humansubject_style=source_actor 两种相似主体:透明骨架人会保持透明/半透明皮肤包裹可见白色骨架,普通真人会保持正常广告演员方向。两种模式都使用 reconstruction_mode=similar,最多读取 12 张参考帧,生成 6 张白底视图;后端使用 SUBJECT_ASSET_IMAGE_MODELS,默认 gpt-image-2 / gpt-image-1.5,不再沿用通用 Gemini 图片模型;后端会加身份锁定约束,统一性别表现、年龄段、体型、材质、风格和视觉身份,避免六视图混成不同人物。 首尾帧资产POST /frames/{idx}/scene-assetgenerateSceneAsset同一接口兼容旧场景图和新首尾帧;新流程传 asset_role=first_frame/last_frame,后端走文字生图,参考帧只用于理解透明骨架人形象、比例、机位和光线,生成结果仍保存在 scene_assets 并自动填入产品融合镜头。 产品图库GET /product-library/skglistProductLibrary读取内置 SKG 白底图库 manifest,返回产品标题、品类、尺寸、白底评分和预览图 URL。 产品图入库到 jobPOST /jobs/{id}/assetsPOST /jobs/{id}/assets/product-libraryuploadStoryboardAssetcopyProductLibraryAsset上传产品图或把内置产品图库条目复制为当前 job 的普通 asset。后端统一生成最长边 1600px、JPEG 92 的 AI 工作副本,透明底铺白,过大/过小图片会在 ImageRef.asset_meta 里返回转换动作和风险;黑底/白底背景本身不强行转换。注意该接口只写图片文件,产品素材池列表另由 PUT /jobs/{id}/product-refs 持久化。 @@ -1004,6 +1004,19 @@ ProductRefStateItem {

变更记录

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

+
+
+

2026-05-18 · 6 视图改用 GPT 图片模型并修复前端展示

+ UI + Workflow + API +
+
+

问题:“相似透明骨架主体”实际已经在后端生成并写入 job,但前端的相似主体识别只匹配“相似主体 / similar subject”等窄名称,无法命中“相似透明骨架主体 / similar transparent skeleton humanoid subject”,所以界面仍显示 0/6,重复点击还会新增重复元素。同时 6 视图模型链路仍跟随通用 IMAGE_MODEL,默认显示 Gemini 图片模型,不符合主体资产必须走 GPT 图片模型的要求。

+

改动:api/main.py 新增 GPT_IMAGE_MODELSUBJECT_ASSET_IMAGE_MODELSUBJECT_ASSET_IMAGE_MODELSgenerateSubjectAssets 默认使用 gpt-image-2 / gpt-image-1.5GET /health 返回 subject_imagesubject_image_fallbacksweb/components/ad-recreation-board.tsx 扩大相似主体元素匹配规则,并在优先帧找不到时回退到全局关键帧里查找已生成主体资产,避免图片被当前选择集隐藏;模型标注改为显示主体专用 GPT 图片链路。

+

影响:api/main.pyweb/lib/api.tsweb/components/ad-recreation-board.tsxdocs/source-analysis.html。后续主体 6 视图不应复用通用补图模型链,必须走主体资产专用 GPT 图片模型配置。

+
+

2026-05-17 · 模型调用入口增加模型名和链路说明

diff --git a/web/components/ad-recreation-board.tsx b/web/components/ad-recreation-board.tsx index 5dbed82..8c55d18 100644 --- a/web/components/ad-recreation-board.tsx +++ b/web/components/ad-recreation-board.tsx @@ -260,8 +260,42 @@ function closestFrameForTime(frames: KeyFrame[], time: number) { } function isSimilarActorElement(element: KeyElement) { - const name = `${element.name_zh || ""} ${element.name_en || ""}`.toLowerCase() - return name.includes("相似主角") || name.includes("相似主体") || name.includes("similar ad actor") || name.includes("similar actor") || name.includes("similar subject") + const zh = element.name_zh || "" + const en = (element.name_en || "").toLowerCase() + const combined = `${zh} ${en}`.toLowerCase() + const zhSimilarSubject = zh.includes("相似") && (zh.includes("主体") || zh.includes("主角") || zh.includes("人物")) + const enSimilarSubject = en.includes("similar") && (en.includes("subject") || en.includes("actor") || en.includes("humanoid") || en.includes("character")) + return ( + zhSimilarSubject + || enSimilarSubject + || combined.includes("相似主角") + || combined.includes("相似主体") + || combined.includes("similar ad actor") + || combined.includes("similar actor") + || combined.includes("similar subject") + || combined.includes("transparent skeleton humanoid subject") + ) +} + +function findSimilarActorSource(preferredFrames: KeyFrame[], allFrames: KeyFrame[]) { + const pools = [preferredFrames, allFrames] + const seen = new Set() + for (const needsAssets of [true, false]) { + for (const pool of pools) { + const frames = [...pool].filter((frame) => { + if (seen.has(frame.index)) return false + seen.add(frame.index) + return true + }).reverse() + for (const frame of frames) { + const elements = [...(frame.elements || [])].reverse() + const element = elements.find((item) => isSimilarActorElement(item) && (!needsAssets || !!item.subject_assets?.length)) + if (element) return { frame, element } + } + } + seen.clear() + } + return null } function buildSimilarSubjectPrompt(subjectStyle: SubjectStyleMode, direction: string) { @@ -306,6 +340,10 @@ function imageModelChain(models?: RuntimeModels) { return modelList(models?.image_fallbacks?.length ? models.image_fallbacks : [models?.image, "gemini-3.1-flash-image-preview", "gemini-2.5-flash-image"]) } +function subjectImageModelChain(models?: RuntimeModels) { + return modelList(models?.subject_image_fallbacks?.length ? models.subject_image_fallbacks : [models?.subject_image, "gpt-image-2", "gpt-image-1.5"]) +} + function resolveVideoModelLabel(models: RuntimeModels | undefined, model: string) { const concrete = models?.video_aliases?.[model] || (model === models?.video ? models.video : "") return concrete && concrete !== model ? `${model} -> ${concrete}` : modelValue(concrete || model) @@ -340,11 +378,11 @@ function productModelTrace(models?: RuntimeModels): ModelTraceSpec { function similarSubjectModelTrace(models: RuntimeModels | undefined, subjectStyle: SubjectStyleMode): ModelTraceSpec { return { title: subjectStyle === "transparent_human" ? "相似透明骨架主体" : "相似普通真人主体", - model: imageModelChain(models), + model: subjectImageModelChain(models), chain: [ "参考帧策略:未勾选时使用全部关键帧,勾选后只使用已选关键帧", `主体类型:${subjectStyle === "transparent_human" ? "透明/半透明皮肤包裹可见白色骨架" : "普通商业广告真人"}`, - `图像生成:${imageModelChain(models)} 逐张生成正、背、左、右、左前 45、右前 45`, + `图像生成:${subjectImageModelChain(models)} 逐张生成正、背、左、右、左前 45、右前 45`, "身份锁定:六张必须是同一个主体,性别表现、年龄段、体型、材质和风格保持一致", ], note: "这是生成类似主体,不是复制、抠出或复刻源视频人物身份。", @@ -1411,17 +1449,8 @@ function SourceReferenceBuildPanel({ [frames, selectedReferenceFrames], ) const actorSource = useMemo(() => { - const pool = subjectReferenceFrames - for (const frame of pool) { - const element = frame.elements?.find(isSimilarActorElement) - if (element?.subject_assets?.length) return { frame, element } - } - for (const frame of pool) { - const element = frame.elements?.find(isSimilarActorElement) - if (element) return { frame, element } - } - return null - }, [subjectReferenceFrames]) + return findSimilarActorSource(subjectReferenceFrames, frames) + }, [frames, subjectReferenceFrames]) const actorAssets = actorSource?.element.subject_assets ?? [] const previewFrame = framePreview ? frames.find((frame) => frame.index === framePreview.index) ?? null : null const referenceCountLabel = selectedReferenceFrames.length