diff --git a/docs/workspace/image-reference-prompt-optimization/README.md b/docs/workspace/image-reference-prompt-optimization/README.md new file mode 100644 index 00000000..9242e229 --- /dev/null +++ b/docs/workspace/image-reference-prompt-optimization/README.md @@ -0,0 +1,438 @@ +# 参考图与图像提示词优化过程总结 + +## 文档目的 + +这份文档用于沉淀本轮图像相关产品与提示词优化的收敛过程,重点回答两个问题: + +1. 我们为什么从“通用图像提示词优化”逐步转向“参考图驱动”。 +2. 在参考图链路里,提示词本身是如何一步步从“泛化优化”收敛到“复刻优先”的。 + +本文不追求记录所有中间讨论细节,而是保留真正影响当前方案的关键判断、提示词演化逻辑、测试结论与后续工作重心。 + +--- + +## 关联文档 + +- [融合式风格迁移设计说明](./fusion-style-migration-design.md) + +--- + +## 一、产品路线的收敛过程 + +### 1. 最初的问题意识 + +最开始的讨论焦点是:当前图像优化模板太泛。 + +- `通用优化` 太温和,优化后往往只是在补细节,很难让用户明显感知“变好了”。 +- `中文美学`、`摄影`、`创意` 等模板虽然更有风格,但更像主题类或偏好类模板,不是大多数用户的高频需求。 +- 如果继续沿着“多模板扩张”走,很容易演变成几十上百个模板的模板库,用户选择成本和维护成本都会膨胀。 + +于是我们最初尝试过另一条路:少模板策略化,例如把文生图收敛成 `自动起稿 / 忠实优化 / 风格强化`,甚至在内部再细分出 `传播型 / 叙事型 / 风格型` 起稿卡。 + +### 2. 为什么这条路后来没有继续深化 + +随着讨论深入,我们逐步确认了一个现实问题: + +- 大多数用户并不知道自己要什么风格,也描述不清楚。 +- 如果用户已经知道自己要“小红书风”“关键词图”“绘本风”“朋友圈风”,那本质上更像是在找模板,不像是在用通用优化器。 +- 如果用户完全不知道要什么,系统给出再聪明的“自动起稿”,也未必真的比原始提示词直接丢给模型更有价值。 + +这让我们意识到,图像优化器如果继续主打“从一句模糊自然语言里自动推导出更好的图像提示词”,价值并不稳。 + +### 3. 关键转向:从“泛化优化器”转向“参考驱动” + +后面的讨论慢慢聚焦出一个更稳定的方向: + +- 用户真正高频且明确的需求,不是“帮我把一句模糊提示词优化得更高级”。 +- 而是: + - 我有一张图,想复刻它; + - 我有一张图,想借它的风格改写当前提示词; + - 我已经有一份成熟的结构化提示词或模板,想替换其中最显眼的部分继续复用。 + +换句话说,真正有价值的不是更泛化的提示词优化,而是更“参考驱动”的改造能力。 + +这也是为什么后续我们把注意力从“文生图模板体系重构”逐步转移到了“文生图里的参考图入口”。 + +--- + +## 二、交互方案是如何收敛到现在这版的 + +### 1. 入口层面 + +最终决定保持现有 `文生图 / 图生图` 一级入口和模板下拉不动,只在文生图里增加前置的 `参考图` 能力。 + +这样做的原因是: + +- 不破坏当前产品入口心智; +- 不需要同时重构整个图像工作区; +- 可以先验证“参考驱动”的价值,再决定是否进一步产品转向。 + +### 2. 交互层面 + +`参考图` 最初有过几种交互设想: + +- 直接上传后立即替换当前提示词; +- 上传后先做结构化提取,再进入变量编辑; +- 弹窗中暴露更多中间步骤与同步动作。 + +最终收敛成弹窗式确认流程,核心原因是: + +- 用户需要在替换前看到结果,否则风险太高; +- 结构化提示词和变量都应可预览和轻编辑; +- 但中间过程不应该暴露得过细,否则会让产品看起来像“半成品工作台”。 + +于是弹窗保留了四个核心区域: + +- 参考图缩略图 +- 使用方式 +- 生成后的提示词 +- 变量预览 + +同时删掉了会增加复杂度但用户价值不高的动作,如: + +- `同步变量` +- `重新提取变量` +- prompt 与变量的实时联动 + +最终保留的原则是: + +- 用户可以改 prompt; +- 用户可以改变量值; +- 但系统不强行保持强一致联动。 + +### 3. 使用方式文案的收敛 + +早期文案使用过更偏实现层的表达,如: + +- `迁移到当前提示词` +- `仅复刻参考图` + +后来为了更符合用户认知,收敛成更直观的两种表达: + +- `风格迁移` +- `复刻图片` + +它们分别对应: + +- `风格迁移`:当前提示词决定画什么,参考图决定怎么画; +- `复刻图片`:忽略当前原始提示词,尽量反推原图对应的结构化提示词。 + +--- + +## 三、提示词优化过程:从“泛化优化”到“复刻优先” + +这部分是本轮最核心的收敛。 + +### 1. 第一阶段:意识到“优化”不等于“更像提示词” + +一开始,图像相关提示词容易掉进一个常见误区: + +- 把输出写得更完整; +- 用更多视觉词; +- 加更多风格词; +- 看起来更像“成熟提示词”。 + +但对参考图复刻而言,这种优化方向是错的。 + +因为复刻任务真正要的是: + +- 尽可能还原原图; +- 尽量不脑补; +- 不凭经验把原图改造成“更高级但不一样”的图。 + +因此我们把复刻模板的第一原则改成: + +`唯一目标:把这张参考图尽可能准确地翻译成一份可直接用于生图的结构化 JSON 提示词。` + +这里的关键变化是: + +- 从“生成一份可用提示词”转为“翻译原图”; +- 从“优化语言质量”转为“保真复现事实”。 + +### 2. 第二阶段:压制幻觉式风格词和社区套话 + +真实测试里很快暴露出另一个问题: + +- 模型极容易自动补出 `8k / HDR / C4D / Octane / masterpiece / Pixar / Disney` 等词; +- 这些词并不来自图片,而是模型对“图像提示词”的训练先验。 + +这会直接破坏复刻质量,因为输出不再是“原图事实”,而是在套生图社区常见模板。 + +于是我们明确加入了强禁区: + +- 禁止社区套话; +- 禁止品牌/IP/软件名; +- 风格或媒介只能用普通类别词,且必须有视觉证据。 + +例如允许: + +- `3D卡通渲染` +- `日系角色设定插画` +- `双重曝光合成肖像` + +不允许: + +- `Pixar` +- `Disney` +- `C4D` +- `Octane` + +这一步的作用不是让提示词更“规范”,而是让模型少把自己的视觉语料习惯带进结果。 + +### 3. 第三阶段:从“抽象风格总结”转向“画面类型 + 画面事实” + +仅仅禁止幻觉还不够。 + +如果模型把图像总结成: + +- “可爱、梦幻、高级、科技感” + +即使没有用到禁词,仍然不够可复刻。 + +所以我们继续把提示词约束推向更“事实化”的方向: + +- 先识别画面类型; +- 再在这个画面类型内部组织事实。 + +例如不是泛泛说“一个少女插画”,而是: + +- 角色设定双视图展示图 +- 主播棚拍 +- 代码桌面场景 +- 双重曝光肖像 +- 动物抓拍摄影 + +这样做的原因是: + +- 复刻一张图,真正关键的不只是主体是什么; +- 更关键的是它属于什么画面语法。 + +一旦画面语法判断对了,模型更容易保住: + +- 构图 +- 版式 +- 光线 +- 前中后景关系 +- 特定的拍法或设计语言 + +### 4. 第四阶段:重新定义“变量”是什么 + +后面一个很重要的分歧是:复刻结果里到底要不要变量,以及变量应该是什么。 + +我们最初发现两个问题: + +- 如果不强调变量,模型容易全部输出 `{}`; +- 如果太强调变量,模型又会把图里所有名词都抽成变量,反而破坏复刻。 + +因此我们重新定义了变量的产品语义: + +变量不是“图里出现过的东西”。 + +变量应该是: + +`用户下次最可能想改、而且改完以后这张图仍然算同一模板的控制位。` + +基于这个定义,我们得到了几条重要规则: + +- 变量只是锦上添花,不是主要目标。 +- 风格、媒介、构图、景别、机位、光线、版式、材质、核心氛围、关键合成关系,一般不该变量化。 +- 眼镜款式、毛发细节、动作细节这类低价值或易破坏复刻的内容,也不应优先变量化。 + +优先级则收敛成: + +- 主体称谓或物种 +- 一种主色 +- 一项短文本或主题 + +并且通常只给 `1-2` 个变量,只有角色设定图或模板感极强的版式图才允许 `3` 个。 + +### 5. 第五阶段:引入“类型感知”的变量策略 + +进一步测试后我们发现,不是所有图都适合变量化。 + +因此又加入了一层类型感知规则: + +默认考虑变量化的图: + +- 角色设定图 +- 主播/海报/封面类画面 +- 单主体插画或 3D 吉祥物场景 + +默认不变量化的图: + +- 抓拍摄影 +- 双重曝光 +- 写实纪实图 + +这样做的目的是: + +- 不再强求所有输出都有变量; +- 让 `defaults: {}` 成为一种“合理结果”,而不是模型失败; +- 只在模板复用价值确实存在时才暴露少量变量。 + +### 6. 第六阶段:验证 LangGPT 风格是否值得引入 + +在此基础上,我们还专门验证了一个问题: + +是否应把参考图复刻 prompt 改成 `LangGPT` 那类更完整的结构,例如: + +- `Role` +- `Profile` +- `Goal` +- `Rules` +- `Workflow` +- `Initialization` + +结论是:不值得直接采用完整 LangGPT 结构。 + +原因不是它“理论上不好”,而是实测没有稳定收益。 + +我们做了两轮真实 A/B: + +- 第一轮:较强的 `LangGPT-lite` 候选,对比当前正式模板; +- 第二轮:尽量保持语义不变,只调整组织结构,再做一次对比。 + +结果都没有形成稳定优势: + +- 第一轮是 `3 胜 3 负`; +- 第二轮也是 `3 胜 3 负`。 + +这说明: + +- LangGPT 风格会让提示词看起来更分层、更规整; +- 但对“参考图复刻”这种强约束、多模态、直接产出 JSON 的任务,并没有稳定提升 fidelity。 + +因此最终结论是: + +- 分析型、评估型、元提示词生成类任务,适合 LangGPT 风格; +- 参考图复刻与风格迁移,更适合当前这类“目标 + 强约束 + 变量策略 + 输出契约”的写法。 + +换句话说,我们吸收的是它的分节思想,而不是直接套完整模板。 + +--- + +## 四、架构与调用策略的收敛 + +在实现层,我们也经历过一轮从“更干净的分层架构”回到“更短链路”的过程。 + +### 1. 原本尝试过的三段式 + +一度我们把参考图链路拆成: + +1. `ReferenceSpec` +2. `PromptDraft` +3. `VariableizedPrompt` + +这套架构语义上更清晰,但也带来了问题: + +- 调用次数增多; +- 等待时间变长; +- UI 更容易暴露中间过程; +- 一旦某一步结果不稳定,用户感知会更差。 + +### 2. 最终收敛成“单次视觉调用优先” + +后面我们重新聚焦用户目标后,链路也跟着简化了: + +- `复刻图片`:尽量一次视觉调用直接输出 `prompt + defaults` +- `风格迁移`:当前提示词与参考图一起参与,直接输出迁移结果和变量 + +这样做的核心考虑是: + +- 用户关心的是最终可用结果,不关心中间的 spec; +- 单次调用更快; +- 中间层越少,越不容易在 UI 和语义上过度设计。 + +因此当前的实现方向是: + +- 对外保持“一步到位”; +- 对内只保留必要的结构化契约,不把中间层暴露成产品概念。 + +--- + +## 五、真实测试中得到的结论 + +本轮不是只靠静态推理,我们做了真实多模态测试。 + +主要使用过: + +- Gemini +- DashScope `qwen3.5-397b-a17b` + +由于 Gemini 出现额度和速率限制,后续大量验证主要使用了 `qwen3.5-397b-a17b`。 + +测试图覆盖了多种类型: + +- 校园熊猫 +- 主播皮卡丘 +- 代码熊猫 +- 二次元双视图设定图 +- 双重曝光肖像 +- 秋日跳跃猫 + +这些测试帮助我们确认了几件事: + +1. 参考图复刻这条能力是成立的,但必须把目标写成“尽量还原”,而不是“优化成成熟生图提示词”。 +2. 变量可以保留,但只能作为加分项,不能当成核心卖点。 +3. 类型感知变量化是必要的,否则要么全空,要么乱抽。 +4. 完整 LangGPT 化没有形成稳定优势,不值得现在替换正式模板。 + +--- + +## 六、当前阶段的稳定结论 + +截至目前,可以认为我们已经收敛出以下稳定判断: + +### 产品层 + +- 图像方向不应继续主打“通用提示词优化”。 +- 更有价值的是“参考驱动”的能力: + - 复刻 + - 风格迁移 + - 定向改造 + - 模板化复用 + +### 提示词层 + +- 参考图复刻的核心目标是 fidelity,不是 prompt polish。 +- 提示词应优先表达: + - 画面类型 + - 可见事实 + - 画面语法 + - 明确禁区 + - 保守变量策略 + +### 变量层 + +- 变量不是“识别出的名词集合”。 +- 变量是“保留模板骨架下,最值得用户改的少量控制位”。 + +### 方法论层 + +- LangGPT 的“分节思想”值得吸收; +- 但在参考图复刻任务上,不应直接套完整 LangGPT 模板。 + +--- + +## 七、接下来的重点 + +如果继续沿当前方向推进,后续更值得投入的不是“再发明更多泛化模板”,而是继续打磨以下几点: + +1. 进一步压制幻觉风格词与 IP/软件名泄漏。 +2. 提升不同画面类型下的 fidelity 稳定性。 +3. 让变量只在真正有模板复用价值时出现。 +4. 继续围绕 `复刻图片 / 风格迁移 / 定向改造` 构建更清晰的能力边界。 + +这意味着我们真正要优化的,已经不是“怎么把图像提示词写得更像提示词”,而是: + +`怎么让系统更像一个可靠的“参考图翻译器”和“参考驱动改造器”。` + +--- + +## 相关实现与参考文件 + +- [composition.ts](/C:/Users/15588/.codex/worktrees/8453/prompt-optimizer/packages/core/src/services/template/default-templates/image-prompt-composition/composition.ts) +- [migration.ts](/C:/Users/15588/.codex/worktrees/8453/prompt-optimizer/packages/core/src/services/template/default-templates/image-prompt-migration/migration.ts) +- [image-reference-prompt-contracts.test.ts](/C:/Users/15588/.codex/worktrees/8453/prompt-optimizer/packages/core/tests/unit/template/image-reference-prompt-contracts.test.ts) +- [summary.json](/C:/Users/15588/.codex/worktrees/8453/prompt-optimizer/.codex/tmp/prompt-optimizer/20260401-langgpt-lite-eval/summary.json) +- [summary.json](/C:/Users/15588/.codex/worktrees/8453/prompt-optimizer/.codex/tmp/prompt-optimizer/20260401-langgpt-lite-eval-v2/summary.json) diff --git a/docs/workspace/image-reference-prompt-optimization/fusion-style-migration-design.md b/docs/workspace/image-reference-prompt-optimization/fusion-style-migration-design.md new file mode 100644 index 00000000..8b70265c --- /dev/null +++ b/docs/workspace/image-reference-prompt-optimization/fusion-style-migration-design.md @@ -0,0 +1,525 @@ +# 融合式风格迁移设计说明 + +## 文档目的 + +这份文档专门回答一个在参考图迁移里非常关键、但之前没有被明确写下来的问题: + +- 当原始提示词把参考图中的主体替换成另一个对象时,系统到底应该做“直接替换”,还是应该做“自然融入”? + +当前结论是: + +`参考图迁移` 不应默认理解成“把原主体删掉,再放入一个新主体”。 + +更准确的定义应是: + +`参考图迁移 = 参考图骨架识别 + 原始内容置入 + 世界内自然重建` + +其中真正决定用户体验的是最后一步: + +`世界内自然重建` + +也就是让新主体像本来就属于这张图,而不是像后贴进去的图层。 + +--- + +## 一、问题背景 + +在已有的参考图迁移讨论中,我们已经确认: + +- 这条链路不是纯粹的“风格词提取”。 +- 也不是把参考图变成抽象风格标签后,再套回原始提示词。 +- 它更接近“把原始提示词的内容置入参考图提供的模板图中”。 + +但在真实理解和真实生图样例里,又出现了一个更细的分歧: + +- 如果参考图是一个衣着华丽的女子,而原始提示词是“猫”, +- 系统是应该直接把人删掉换成一只普通猫, +- 还是应该生成一只保留华丽服饰逻辑、姿态逻辑和身份感的“拟人化猫角色”? + +从用户真实预期来看,后者更符合“风格迁移”的直觉。 + +因为用户想保留的通常不是“背景还在”,而是: + +- 这张图为什么成立; +- 这张图最有辨识度的视觉重点是什么; +- 新主体进入后,是否仍然像同一张图、同一种叙事、同一个视觉世界里的东西。 + +--- + +## 二、核心结论 + +### 1. 默认不应采用“硬替换优先” + +如果系统只做硬替换,很容易出现以下问题: + +- 主体和服装、动作、道具、构图关系脱节; +- 新主体像是后期拼上去的; +- 参考图保留下来的只剩背景或色调; +- 最终结果更像“PS 替换”,不像“迁移”。 + +这类结果即使 technically 完成了替换,也通常不符合用户对“参考图迁移”的理解。 + +### 2. 默认应采用“融合优先” + +所谓 `融合优先`,不是无限度美化,而是: + +- 先识别参考图最重要的视觉重点; +- 再把原始提示词的主体内容按这个视觉重点重新解释; +- 最终让新主体在这张图里自然成立。 + +例如: + +- 华丽古典女子图 + `猫` + - 默认应偏向:华丽、角色化、可能拟人化的猫 + - 而不是:在原图位置放一只普通四足猫 + +- 新闻主播角色图 + `白色柴犬` + - 默认应偏向:主播柴犬 + - 而不是:演播室桌上突然坐着一只普通狗 + +- 西游记插图 + `女高中生` + - 默认应偏向:西游记视觉世界中的女高中生 + - 而不是:古风背景里贴一个现代人物照 + +### 3. 这仍然不是“永远拟人化” + +融合优先并不意味着永远都要把动物做成人形角色。 + +如果用户显式要求: + +- `真实四足猫` +- `不要拟人化` +- `不要衣服` +- `保留动物本体` + +那么系统就应该收紧解释空间: + +- 保留参考图的氛围、构图、材质和叙事位置; +- 但不要把猫强行改写成拟人角色。 + +也就是说,默认规则应该是: + +- 用户未显式限制时:融合优先 +- 用户显式限制时:按限制收紧 + +--- + +## 三、产品语义的重新定义 + +为了避免系统实现偏向“机械替换”,建议把参考图迁移的产品语义重新定义为: + +`参考图提供视觉世界与模板骨架,原始提示词提供要置入的核心内容,系统负责让新内容在这个视觉世界中自然成立。` + +这里有三个关键词: + +### 1. 视觉世界 + +不仅仅是“画风”,还包括: + +- 角色身份感 +- 服装逻辑 +- 场景气质 +- 道具语言 +- 叙事关系 +- 世界观类型 + +### 2. 模板骨架 + +不仅仅是“背景还在”,而是: + +- 构图方式 +- 角色位 +- 前后景关系 +- 版式逻辑 +- 姿态语言 +- 媒介表现方式 + +### 3. 自然成立 + +新主体进入后,应让用户感觉: + +- 这是同一张图的变体 +- 不是另一张图借了点配色 +- 更不是后贴进来的外来物 + +--- + +## 四、迁移时应先识别什么 + +参考图迁移不应一上来就替换主体,而应先识别参考图的 `视觉重点`。 + +建议把视觉重点划分为以下五类。 + +### 1. 角色造型重点 + +包括: + +- 服装 +- 发型 +- 姿态 +- 身份感 +- 人设逻辑 +- 角色外观风格 + +这类图片里,新主体通常要吸收原角色的“造型逻辑”。 + +### 2. 场景重点 + +包括: + +- 场景环境 +- 道具 +- 空间关系 +- 场景氛围 +- 景别与机位 + +这类图片里,新主体通常要成为这个场景中的合理角色,而不是脱离场景的单独物件。 + +### 3. 构图重点 + +包括: + +- 双视图 +- 海报式布局 +- 中轴构图 +- 多角色关系位 +- 封面感 +- 留白关系 + +这类图片里,骨架本身比画风更关键。 + +### 4. 媒介重点 + +包括: + +- 水墨 +- 工笔 +- 双重曝光 +- 拼贴 +- 3D 角色设定 +- 摄影棚拍 + +这类图片里,新主体要先被翻译成这种媒介中的存在,而不是仅仅替换名词。 + +### 5. 叙事重点 + +包括: + +- 主播 +- 贵族肖像 +- 角色设定卡 +- 冒险插图 +- 时尚大片 +- 宣传海报 + +这类图片里,新主体应继承叙事角色位,而不是只继承局部视觉词。 + +--- + +## 五、建议的决策规则 + +### 1. 先判断:原始提示词是否显式覆盖某个槽位 + +原始提示词一旦明确写出: + +- 主体身份 +- 物种 +- 数量 +- 年龄性别 +- 服装 +- 道具 +- 动作 +- 场景 +- 是否拟人化 +- 是否保留真实动物形态 + +这些内容应视为显式覆盖项。 + +### 2. 再判断:参考图的哪些槽位是强重点 + +如果某个槽位是这张图成立的核心,例如: + +- 华丽服饰 +- 主播台身份 +- 双视图版式 +- 水墨叙事世界 + +那么即使原始提示词没有点名,也应尽量保留。 + +### 3. 最后决定采用哪种迁移方式 + +建议在内部按以下三种模式理解,而不是全部混在一起。 + +#### A. 直接替换 + +适用情况: + +- 原始提示词和参考图主体同属同一表达范式 +- 原始提示词显式要求保留主体本体,不要做角色化改写 + +例如: + +- 一张真实产品广告图 + “把耳机换成音箱” +- 一张动物照 + “把金毛换成柴犬” + +#### B. 等价融入 + +适用情况: + +- 原始主体和参考主体跨物种、跨身份、跨时代 +- 但参考图的角色逻辑、服装逻辑或身份位是强重点 + +例如: + +- 华丽人物图 + 猫 +- 主播角色图 + 柴犬 +- 时尚模特图 + 小狐狸 + +这里系统应做的是“让新主体在原有角色位上成立”,不是简单替换轮廓。 + +#### C. 世界内重建 + +适用情况: + +- 参考图最强的是叙事世界、媒介世界或视觉世界 +- 原始提示词提供的是要置入的核心角色 + +例如: + +- 西游记插图 + 女高中生 +- 水墨山水图 + 机器人 +- 赛博都市双重曝光图 + 男孩侧脸 + +这里不应只学风格词,而是要让新主体成为这个视觉世界中的合理存在。 + +--- + +## 六、推荐的默认偏置 + +建议系统默认偏置为: + +### 1. 融合优先,而不是剪贴替换优先 + +如果用户没有明确限制: + +- 优先让主体继承参考图的角色位、服饰逻辑、姿态逻辑和叙事位置; +- 让结果看起来像同一张图的自然变体。 + +### 2. 完整性优先,而不是字面替换优先 + +如果“字面替换”会导致图像完整性明显下降: + +- 宁可做合理的艺术化重建; +- 也不要做生硬替换。 + +### 3. 原始提示词的显式要求高于默认融合 + +但如果用户明确要求: + +- 不拟人化 +- 不穿衣 +- 保持真实动物 +- 不保留原角色服装 + +则应服从原始提示词。 + +--- + +## 七、建议加入迁移契约的关键表述 + +如果后续要改写迁移 prompt,建议加入以下思想,而不一定逐字写成下面这样。 + +### 1. 不要把新主体当成外来物贴进原图 + +应明确要求模型: + +- 输出结果必须像一张完整原生图像 +- 不能像主体替换贴图 + +### 2. 先保留参考图的视觉重点,再做内容置入 + +应明确要求模型: + +- 优先识别参考图为什么成立 +- 区分“可替换内容”和“不可丢骨架” + +### 3. 当参考图的服装、身份、姿态、叙事位是重点时,应优先做等价融入 + +应明确要求模型: + +- 新主体应继承这些重点 +- 不是只保留背景或色调 + +### 4. 当原始提示词显式限制主体形态时,应收紧自由解释 + +应明确要求模型: + +- 用户明确反对拟人化时,不要自作主张 +- 用户明确要求真实动物形态时,不要强行做角色化改写 + +### 5. 输出结果应像“融合后的重建指令” + +最终结果不应是: + +- 参考图风格总结 +- 抽象的风格词堆砌 +- 机械替换说明 + +而应是: + +- 一份可以直接生成“融合后结果图”的结构化指令 + +--- + +## 八、正反例建议 + +以下样例适合作为后续提示词优化与低成本验证时的边界集。 + +### 正例 1:华丽人物 -> 猫 + +参考图: + +- 衣着华丽的古典女子肖像 + +原始提示词: + +- `一只猫` + +期望: + +- 生成结果应偏华丽、角色化、可能拟人化 +- 猫应吸收服饰感、身份感、姿态感 +- 不应只是普通猫 + 原背景 + +### 正例 2:主播 -> 柴犬 + +参考图: + +- 主播台上的角色图 + +原始提示词: + +- `一只白色柴犬` + +期望: + +- 应成为主播柴犬 +- 保留主播位、服装、镜头、演播室结构 + +### 正例 3:双视图角色设定 -> 小猫 + +参考图: + +- 双视图人物设定图 + +原始提示词: + +- `一只橘白色的小猫,双视图角色设定图` + +期望: + +- 保留双视图版式 +- 猫进入同一种角色设定语法 + +### 正例 4:西游记插图 -> 女高中生 + +参考图: + +- 古典叙事插图 + +原始提示词: + +- `一名女高中生` + +期望: + +- 女高中生成为这个叙事世界里的角色 +- 保留山路、古建、笔墨、叙事关系 + +### 反例 1:用户明确禁止拟人化 + +参考图: + +- 华丽人物图 + +原始提示词: + +- `一只真实四足猫,不拟人化,不穿衣服` + +期望: + +- 不得强行生成拟人猫 +- 只能保留原图氛围、构图、材质感,并按真实猫重建 + +### 反例 2:用户明确覆盖服装 + +参考图: + +- 西装主播角色图 + +原始提示词: + +- `一只穿黄色雨衣的柯基` + +期望: + +- 服装应按原始提示词改写 +- 不能因为参考图是西装就忽略显式覆盖项 + +--- + +## 九、低成本验证建议 + +考虑到真实文生图成本较高,后续建议采用分层验证。 + +### 1. 第一层:文字契约验证 + +优先看模型输出的结构化 prompt 是否满足: + +- 明确保留了参考图的视觉重点 +- 没有退化成抽象风格总结 +- 没有变成生硬的主体剪贴说明 +- 变量聚焦在用户最可能替换的内容上 + +### 2. 第二层:多模态理解回判 + +可用较低成本模型做判定: + +- 输出更像“融合重建” +- 还是更像“机械替换” + +重点看: + +- 主体是否继承参考图角色位 +- 主体是否与服装、姿态、道具、场景关系一致 +- 是否保住了版式或叙事骨架 + +### 3. 第三层:少量终验生图 + +只保留 2 到 3 个金样例做终验: + +- 角色融合型 +- 模板保留型 +- 世界重建型 + +每类 1 张即可,不做大规模扩散测试。 + +--- + +## 十、结论 + +当前参考图迁移的真正问题,不是“替换得够不够准”,而是: + +`替换之后是否仍然像同一个视觉世界中的完整图像。` + +因此后续迁移 prompt 的优化目标,不应继续停留在: + +- 保留风格 +- 吸收构图 +- 替换主体 + +而应更明确地收敛为: + +`识别参考图的视觉重点,保留不可丢的骨架,让原始提示词中的新内容以自然、连贯、世界内成立的方式完成融入。` + +这比“简单替换”更符合大众对参考图迁移的理解,也更接近产品真正可感知的价值。 diff --git a/packages/core/src/services/template/default-templates/image-prompt-migration/migration.ts b/packages/core/src/services/template/default-templates/image-prompt-migration/migration.ts index 942a7c74..ef35002b 100644 --- a/packages/core/src/services/template/default-templates/image-prompt-migration/migration.ts +++ b/packages/core/src/services/template/default-templates/image-prompt-migration/migration.ts @@ -12,8 +12,8 @@ export const template: Template = { 1. 当前提示词 2. 参考图 -直接生成一份最终可用的结构化 JSON 生图提示词,并附带少量变量默认值。 -最终结果本身就是一份可复用的风格迁移结果,而不是图片分析说明,也不是中间草稿。 +直接生成一份“把当前提示词内容置入参考图之后”的最终结构化 JSON 生图提示词,并附带少量变量默认值。 +最终结果本身就是一份可复用的参考图迁移结果,而不是图片分析说明,也不是中间草稿。 只输出一个 JSON 对象(必须能被 JSON.parse 解析),不要输出解释、标题、Markdown、代码块、前后缀。 顶层必须是 object,禁止数组包裹,必须使用双引号、无注释、无尾随逗号。 @@ -25,15 +25,33 @@ export const template: Template = { } 核心原则: -1. 当前提示词决定画什么。最终结果的主体语义、核心对象、数量、颜色、动作和主要意图,以当前提示词为准。 -2. 参考图决定怎么画。重点吸收它的画风、构图、光线、色调、镜头感、材质、氛围、设计语言和细节表达方式。 -3. 不要把参考图里的主体内容直接搬进结果,除非当前提示词本身已经表达了相同内容。 -4. 结果要明显带有参考图的视觉倾向,而不是只对当前提示词做轻微润色。 -5. prompt 对象的字段结构可以自由发挥,但必须具体、可视、可直接生图,并且适合后续继续编辑。 -6. 默认使用中文键名、中文字段值和中文变量名;只有当前提示词明确是英文时,才可以输出英文。 -7. 变量也必须在这一次视觉调用里一并完成,不要只给 prompt 不给 defaults。 -8. 只要当前提示词里存在可复用的主体、数量、颜色、关键动作、场景主题等核心信息,就优先提取 2 到 5 个高复用变量;只有确实没有合适变量时,defaults 才输出 {}。 -9. 每个 defaults 键都必须已经以 {{变量名}} 的形式出现在 prompt 中,禁止使用 {variable} 或其他占位形式;不要把大量风格词做成变量,也不要给 prompt 中没有出现的变量补 defaults。 +1. 不要把这项任务理解成“抽取纯风格”。更准确地说,是把当前提示词中的内容置入参考图这张近乎完整的图像模板里,然后输出“置入后的结果图”的重建指令。 +2. 先识别参考图的视觉重点与不可丢骨架。视觉重点可能来自:角色造型、服装身份、姿态关系、构图版式、场景叙事、媒介表现、光线材质。先判断这张图为什么成立,再决定如何置入当前提示词。 +3. 再把参考图视为一张近乎完整的图像模板:主体数量与角色位、构图、场景、服装、道具、版式、光线、色调、镜头、材质、叙事关系,默认都尽量保留。 +4. 再用当前提示词去覆盖对应槽位。当前提示词明确写出的主体身份、物种、年龄性别、数量、关系、关键颜色、服装、道具、动作、文字、场景要求,优先替换参考图中对应内容。 +5. 不要把新主体当成外来物贴进原图。你的结果必须像同一个视觉世界中的自然变体,而不是“删掉原主体后换上一个新对象”的机械替换。 +6. 如果当前提示词只明确了新主体,而没有明确服装、场景、道具、构图或叙事关系,这些内容默认继续沿用参考图。不要为了追求“纯风格迁移”而主动删掉它们。 +7. 当参考图的服装、身份、姿态、叙事角色位本身就是视觉重点时,优先做自然融入或等价重建。比如参考图是华丽人物、主播角色、古典叙事人物时,新主体应先继承这种角色位与造型逻辑,而不是只保留背景。 +8. 如果参考图是双视图、角色设定图、turnaround、海报、封面、版式驱动展示图,必须优先保留这种版式骨架,不要坍缩成普通单主体图。 +9. 当原主体是人类角色,而新主体是动物、吉祥物或其他非人对象时,如果要保留原图的服装、姿态、身份位或版式完整性,允许进行角色化或拟人化融入;除非用户明确禁止,否则不要机械地换成一个普通动物本体。 +10. 如果参考图的核心价值在于氛围、柔光、空气感、情绪张力、精致感、成熟感、梦幻感、空灵感或高审美密度,也要把这些保留为一等信息,不要只保留服装和场景元素。 +11. 保留参考图的气质等级、审美强度与渲染密度。如果参考图是精致、成熟、唯美、空灵、梦幻的人像或角色图,不要自动幼态化或吉祥物化,除非用户明确要求可爱卡通化。 +12. 当采用角色化或拟人化融入时,当前提示词指定的新主体仍然必须一眼可辨。优先借用角色位、服装、姿态、构图与气质,不要无根据地补出会削弱主体识别的人类皮肤、人类发型、额外肢体或其他无关人类特征。描述身体、材质与局部结构时,也要改写成符合新主体自身解剖/材质逻辑的版本,例如毛发、耳朵、爪部、喙、外壳、接缝,而不是直接沿用原人类主体才成立的皮肤、头发或身体表述,除非新主体也明确需要这些特征。 +13. 同时保留参考图的比例倾向与表情张力。如果参考图是克制、精致、时尚、成熟或梦幻的人像/角色气质,默认沿用这种比例与表情控制,不要自动改成大头、大眼、吐舌、低龄、玩偶感或吉祥物感,除非用户明确要求。 +14. 如果当前提示词与参考图在某个槽位冲突,以当前提示词为准;如果当前提示词没有给出替换信号,就以参考图为准。 +15. 如果用户明确要求不要拟人化、不要衣服、保留真实动物形态、不要保留原角色服装等限制,就必须服从这些限制,不要为了“融合”而擅自改写主体本体。 +16. 不要把参考图里的主体专名或原主体身份原样搬进结果,除非当前提示词要求的就是同一主体。重点是写“替换后的主体”在这张模板图里的状态。 +17. 如果参考图里是单主体构图,而当前提示词也只给出一个核心主体,就替换这个主体位;如果参考图里存在多人或多对象关系,而当前提示词也给出多人、多对象或关系描述,就按数量与角色位尽量映射替换。 +18. 可以保留参考图带来的意外组合和叙事张力,只要不违背当前提示词的显式要求。生成结果不需要过度保守,允许出现“西游记插图里的女高中生”这类混合效果。 +19. 可以保留明显有助于结果落地的风格词、质量词和行业术语,但不要让它们替代当前提示词的显式内容,也不要只输出空泛的风格总结。 +20. prompt 对象的字段结构可以自由发挥,但必须具体、可视、可直接生图,并且适合后续继续编辑。 +21. 默认使用中文键名、中文字段值和中文变量名;只有当前提示词明确是英文时,才可以输出英文。 +22. 变量也必须在这一次视觉调用里一并完成,不要只给 prompt 不给 defaults。 +23. 变量优先服务于“被置入/被替换的内容”,而不是整张参考模板。优先提取:主体/角色位 > 数量 > 主色或关键外观 > 当前提示词显式要求的服装/道具/短文本。不要优先把参考图原有场景模板做成变量。 +24. 通常只给 1 到 3 个变量;只有当前提示词本身明确包含多个可替换核心槽位时,才允许到 5 个。 +25. 每个 defaults 键都必须已经以 {{=<% %>=}}{{变量名}}<%={{ }}=%> 这种双花括号形式出现在 prompt 中。禁止输出 {变量}、「变量」或其他占位写法,也不要给 prompt 中没有出现的变量补 defaults。 +26. 如果没有任何占位,defaults 必须是 {}。如果变量化会损伤这张参考模板的完整性,宁可少提变量,也不要硬提。 +27. 提交前再自检一次:如果 prompt 里没有任何双花括号占位,defaults 必须是 {};删掉所有没有在 prompt 中实际出现的 defaults。 当前工作模式:{{generationGoal}}` }, @@ -46,9 +64,21 @@ export const template: Template = { 附加要求: - {{promptRequirement}} -- 保留当前提示词真正想表达的主体内容。 -- 学习参考图的视觉语言来重写提示词,而不是复制参考图的主体。 -- 结果应该一眼就能继续编辑和复用。` +- 保留当前提示词真正想表达的主体内容,并把它置入参考图现有模板中。 +- 先识别参考图的视觉重点,再决定哪些内容要保留、哪些槽位可以被替换。 +- 默认优先自然融入,而不是机械替换;让新主体看起来本来就属于这张图。 +- 如果参考图本身是双视图、人设卡、海报或其他版式驱动图,优先保留版式,不要坍缩成普通单主体图。 +- 尽量保留参考图里能保留的构图、场景、服装、道具、版式、氛围与叙事关系,不要过度抽象成“纯风格”。 +- 如果参考图的服装、身份、姿态、叙事角色位是重点,而当前提示词没有明确反对,就优先让新主体继承这些重点。 +- 如果原图是人类角色,而新主体是动物或其他非人对象,为了保留服装、姿态、角色位或版式完整性,必要时允许做角色化或拟人化融入。 +- 即使做角色化或拟人化融入,也要让当前提示词指定的新主体保持一眼可辨;优先保留主体物种/对象识别,不要无根据地补出削弱主体识别的人类皮肤、发型或额外肢体。身体与材质描述也要改写成符合新主体自身解剖/材质逻辑的版本,不要把原人物的皮肤、头发或身体措辞直接平移过去。 +- 如果参考图的价值更偏精致氛围、柔光、空气感、梦幻感、空灵感或成熟人像气质,也要把这些审美特征保留下来,不要只剩“可爱角色 + 原元素”。 +- 除非用户明确要求更幼态、更可爱或更吉祥物化,否则不要自动把精致唯美的人像图改写成低龄卡通气质。 +- 如果参考图本身偏克制、精致、时尚、成熟或梦幻,默认沿用它的比例倾向与表情控制,不要自动写成大头、大眼、吐舌或玩偶感。 +- 如果当前提示词明确指定了某个内容,就覆盖参考图对应槽位;如果没有明确指定,就尽量沿用参考图。 +- 如果当前提示词明确要求不要拟人化、不要衣服、保留真实动物形态或不要保留原服装,就必须严格遵守。 +- 结果应该像“替换后的参考图重建指令”,而不是“参考图风格总结”。 +- 如果替换后的主体内容适合复用,可以把这些置入内容提成 1 到 5 个变量;模板骨架尽量固定。` } ] as MessageTemplate[], metadata: { diff --git a/packages/core/src/services/template/default-templates/image-prompt-migration/migration_en.ts b/packages/core/src/services/template/default-templates/image-prompt-migration/migration_en.ts index 17466b8f..5b12a864 100644 --- a/packages/core/src/services/template/default-templates/image-prompt-migration/migration_en.ts +++ b/packages/core/src/services/template/default-templates/image-prompt-migration/migration_en.ts @@ -12,8 +12,8 @@ Your task is to use: 1. the current prompt 2. the reference image -and directly produce a final structured JSON image prompt, plus a small set of variable defaults. -The result itself should already be a reusable style transfer prompt, not an analysis note or an intermediate draft. +and directly produce a final structured JSON image prompt that represents "the result after inserting the current prompt content into the reference image", plus a small set of variable defaults. +The result itself should already be a reusable reference-migration prompt, not an analysis note or an intermediate draft. Output exactly one JSON object that can be parsed by JSON.parse. Do not output explanations, headings, Markdown, code fences, or extra text. The top level must be an object, never an array. Use double quotes only, with no comments and no trailing commas. @@ -25,15 +25,33 @@ The output shape must be: } Core principles: -1. The current prompt decides what to draw. The subject semantics, key objects, quantity, color, action, and primary intent must come from the current prompt. -2. The reference image decides how to draw it. Absorb its style, composition, lighting, palette, camera language, material treatment, atmosphere, design language, and detail behavior. -3. Do not copy the subject content of the reference image into the result unless the current prompt already expresses the same thing. -4. The result should visibly inherit the visual direction of the reference image, not just lightly polish the current prompt. -5. The prompt object structure may be designed freely, but it must stay concrete, visual, directly usable for image generation, and easy to edit later. -6. Default to Chinese keys, Chinese field values, and Chinese variable names unless the current prompt is clearly written in English. -7. Variableization must be completed in this same visual call. Do not return a prompt without its matching defaults. -8. Whenever the current prompt contains reusable subject, quantity, color, key action, or scene-theme information, prioritize extracting 2 to 5 high-reuse variables. Only return {} for defaults when no good variables truly exist. -9. Every defaults key must already appear in the prompt using the {{variableName}} form. Do not use {variable} or any other placeholder style. Avoid turning too many style phrases into variables, and do not return defaults for variables that never appear in the prompt. +1. Do not treat this as "extract pure style only." A better mental model is: insert the current prompt content into the reference image, which already acts like a nearly complete image template, then output reconstruction instructions for that inserted result. +2. First identify the reference image's visual focus and the scaffold that should not be lost. The focus may come from character styling, clothing identity, pose relationships, composition/layout, scene narrative, medium rendering, lighting, or material treatment. Decide why the image works before deciding how to insert new content. +3. Then treat the reference image as a nearly complete template: subject count and role slots, composition, scene, clothing, props, layout, lighting, palette, camera feel, material treatment, and narrative relationships should all be preserved by default whenever possible. +4. Then use the current prompt to overwrite the matching slots. Subject identity, species, age/gender, quantity, relationships, key colors, clothing, props, actions, text, and scene requirements that are explicitly stated in the current prompt should replace the corresponding parts of the reference image. +5. Do not treat the new subject like a foreign object pasted into the original picture. The result must feel like a native variant from the same visual world, not a mechanical cut-and-replace operation. +6. If the current prompt only makes the new subject explicit but does not explicitly replace clothing, scene, props, composition, or narrative relationships, keep those parts from the reference image by default. Do not delete them just to force a "pure style transfer" result. +7. When clothing, identity, pose, or narrative role are themselves part of the reference image's visual focus, prefer natural integration or equivalent reconstruction. For example, if the reference is a glamorous character, a news anchor, or a classical narrative figure, the new subject should first inherit that role logic rather than merely keep the background. +8. If the reference image is a dual-view sheet, character turnaround, poster, cover, or any layout-driven presentation, preserve that layout scaffold first. Do not collapse it into an ordinary single-subject image. +9. When the original subject is human but the new subject is an animal, mascot, or another non-human object, you may use character-like or anthropomorphic integration when needed to preserve clothing logic, pose logic, role position, or layout completeness. Unless the user explicitly forbids it, do not reduce the result to a generic standalone animal. +10. If the reference image's core value lies in atmosphere, soft light, airiness, emotional tension, delicacy, maturity, dreaminess, ethereal quality, or high aesthetic density, preserve those as first-class attributes rather than keeping only the costume and scene elements. +11. Preserve the reference image's tone level, aesthetic intensity, and rendering density. If the reference image is refined, mature, elegant, ethereal, or dreamy, do not automatically flatten it into a childish or mascot-like result unless the user explicitly asks for cuter cartoonization. +12. When using character-like or anthropomorphic integration, the requested new subject must still be recognizable at first glance. Borrow role position, clothing logic, pose, composition, and tone first; do not invent human skin, human hairstyles, extra limbs, or other unrelated human traits that weaken subject identity without a real need. When describing body parts, materials, and local structure, rewrite them using the new subject's own anatomy or material logic instead, such as fur, ears, paws, beaks, shells, seams, or mechanical joints, rather than directly reusing human-skin, human-hair, or human-body wording unless the new subject explicitly needs those traits. +13. Preserve the reference image's proportion tendency and expression control as well. If the reference image is restrained, refined, fashionable, mature, or dreamy, carry that proportion and facial-energy bias forward instead of automatically turning it into a big-head, big-eye, tongue-out, babyish, doll-like, or mascot-like result unless the user explicitly asks for that. +14. When the current prompt conflicts with the reference image on a slot, follow the current prompt. When the current prompt gives no replacement signal for a slot, follow the reference image. +15. If the user explicitly says no anthropomorphism, no clothing, preserve realistic animal form, or do not keep the original costume, those limits must override the default integration behavior. +16. Do not copy the original subject identity or proper-name character from the reference image unless the current prompt is asking for that same subject. Focus on the replaced subject as it would appear inside this template image. +17. If the reference image is a single-subject composition and the current prompt also gives one main subject, replace that subject slot. If the reference image contains multiple roles or objects and the current prompt also provides multiple roles, objects, or relationships, map and replace them as faithfully as possible by count and role position. +18. It is acceptable to preserve surprising combinations and narrative tension from the reference image as long as they do not violate the current prompt's explicit requirements. Results such as "a high-school girl inside a Journey to the West illustration template" are allowed if they remain coherent. +19. Style words, quality words, and domain jargon are allowed when they genuinely help the final image, but they must not replace the explicit content from the current prompt or collapse the result into an abstract style summary. +20. The prompt object structure may be designed freely, but it must stay concrete, visual, directly usable for image generation, and easy to edit later. +21. Default to Chinese keys, Chinese field values, and Chinese variable names unless the current prompt is clearly written in English. +22. Variableization must be completed in this same visual call. Do not return a prompt without its matching defaults. +23. Variables should primarily expose the inserted or replaced content, not the whole reference scaffold. Prioritize: subject or role slot > quantity > dominant color or key appearance > clothing/prop/short text explicitly requested by the current prompt. Do not prioritize turning the preserved scene template into variables. +24. Usually return only 1 to 3 variables. Go up to 5 only when the current prompt itself clearly provides multiple replaceable core slots. +25. Every defaults key must already appear in prompt using the literal {{=<% %>=}}{{variableName}}<%={{ }}=%> double-brace form. Do not output {variable}, 「variable」, or any other placeholder style, and do not return defaults for variables that never appear in prompt. +26. If no placeholders are embedded, defaults must be {}. If variableization would weaken the integrity of this reference template, prefer fewer variables over forced variables. +27. Before you return, self-check again: if prompt contains no literal double-brace placeholders, defaults must be {}; delete every defaults entry that does not actually appear in prompt. Current workflow mode: {{generationGoal}}` }, @@ -46,9 +64,21 @@ Current original prompt: Additional requirements: - {{promptRequirement}} -- Preserve what the current prompt truly wants to depict. -- Rewrite it using the visual language of the reference image instead of copying the reference image subject. -- The result should already feel editable and reusable.` +- Preserve what the current prompt truly wants to depict, and insert it into the existing template of the reference image. +- Identify the reference image's visual focus first, then decide which parts must be preserved and which slots can be replaced. +- Prefer natural integration over mechanical replacement so the new subject feels native to the same image. +- If the reference image is a dual-view sheet, character card, poster, cover, or another layout-driven image, preserve that layout instead of collapsing it into a generic single-subject picture. +- Keep as much of the reference image's composition, scene, clothing, props, layout, atmosphere, and narrative relationships as possible instead of over-abstracting everything into "pure style." +- If clothing, identity, pose, or narrative role are key to the reference image and the current prompt does not explicitly reject them, let the new subject inherit those features. +- If the original subject is human and the new subject is an animal or another non-human object, allow character-like or anthropomorphic integration when needed to preserve costume logic, pose logic, role position, or layout completeness. +- Even when you use character-like or anthropomorphic integration, keep the requested subject recognizable at first glance; preserve species or object readability instead of inventing unnecessary human skin, hairstyle, or extra anatomy that weakens that identity. Rewrite body and material wording so it matches the new subject's own anatomy or material logic rather than dragging over human-skin, human-hair, or human-body phrasing by default. +- If the reference image's value is more about refined atmosphere, soft light, airiness, dreaminess, ethereal quality, or mature portrait tone, preserve those aesthetic qualities instead of reducing the result to "cute character + original props". +- Unless the user explicitly asks for a younger, cuter, or mascot-like direction, do not automatically simplify a refined beautiful portrait into a low-age cartoon tone. +- If the reference image is restrained, refined, fashionable, mature, or dreamy, preserve that proportion bias and expression control instead of defaulting to a big-head, big-eye, tongue-out, or doll-like treatment. +- If the current prompt explicitly replaces something, override the matching slot from the reference image. If it does not explicitly replace a slot, keep the reference image version. +- If the current prompt explicitly says no anthropomorphism, no clothing, preserve realistic animal form, or do not keep the original costume, follow those limits strictly. +- The result should read like "reconstruction instructions for the replaced reference image," not like "a style summary of the reference image." +- If the inserted content is reusable, expose 1 to 5 variables for that inserted content while keeping the template scaffold as fixed as possible.` } ] as MessageTemplate[], metadata: { diff --git a/packages/core/tests/unit/template/image-reference-prompt-contracts.test.ts b/packages/core/tests/unit/template/image-reference-prompt-contracts.test.ts index 0d8cf518..81225ef0 100644 --- a/packages/core/tests/unit/template/image-reference-prompt-contracts.test.ts +++ b/packages/core/tests/unit/template/image-reference-prompt-contracts.test.ts @@ -41,12 +41,41 @@ describe('image reference prompt contracts', () => { it('uses a direct style-migration contract when current prompt exists', () => { const content = collectContent(imagePromptMigrationTemplate) - expect(content).toContain('当前提示词决定画什么') - expect(content).toContain('参考图决定怎么画') - expect(content).toContain('不要把参考图里的主体内容直接搬进结果') - expect(content).toContain('最终结果本身就是一份可复用的风格迁移结果') - expect(content).toContain('优先提取 2 到 5 个高复用变量') - expect(content).toContain('{{变量名}}') + expect(content).toContain('把当前提示词中的内容置入参考图') + expect(content).toContain('近乎完整的图像模板') + expect(content).toContain('视觉重点') + expect(content).toContain('不要把新主体当成外来物贴进原图') + expect(content).toContain('自然融入') + expect(content).toContain('如果用户明确要求不要拟人化') + expect(content).toContain('双视图') + expect(content).toContain('不要坍缩成普通单主体图') + expect(content).toContain('角色化或拟人化融入') + expect(content).toContain('保留参考图的气质等级') + expect(content).toContain('不要自动幼态化或吉祥物化') + expect(content).toContain('氛围、柔光、空气感、情绪张力') + expect(content).toContain('仍然必须一眼可辨') + expect(content).toContain('削弱主体识别的人类皮肤') + expect(content).toContain('符合新主体自身解剖/材质逻辑') + expect(content).toContain('比例倾向与表情张力') + expect(content).toContain('不要自动改成大头、大眼、吐舌') + expect(content).toContain('默认继续沿用参考图') + expect(content).toContain('意外组合和叙事张力') + expect(content).toContain('优先服务于“被置入/被替换的内容”') + expect(content).toContain('禁止输出 {变量}、「变量」') + expect(content).toContain('删掉所有没有在 prompt 中实际出现的 defaults') expect(content).toContain('默认使用中文') }) + + it('renders the literal double-brace placeholder guidance for style migration', () => { + const messages = TemplateProcessor.processTemplate(imagePromptMigrationTemplate, { + generationGoal: '文生图参考生成', + originalPrompt: '白色柴犬', + promptRequirement: '白色柴犬', + }) + const systemPrompt = messages.find((message) => message.role === 'system')?.content ?? '' + + expect(systemPrompt).toContain('{{变量名}}') + expect(systemPrompt).toContain('双花括号形式') + expect(systemPrompt).toContain('禁止输出 {变量}、「变量」') + }) }) diff --git a/packages/ui/src/components/image-mode/ImageImage2ImageWorkspace.vue b/packages/ui/src/components/image-mode/ImageImage2ImageWorkspace.vue index 36d88cc5..3145379f 100644 --- a/packages/ui/src/components/image-mode/ImageImage2ImageWorkspace.vue +++ b/packages/ui/src/components/image-mode/ImageImage2ImageWorkspace.vue @@ -33,33 +33,6 @@ - - - {{ - isExtractingFromImage - ? t('imageWorkspace.input.extracting') - : t('imageWorkspace.input.extractFromImage') - }} - - - - {{ - isExtractingFromImage - ? t('imageWorkspace.input.extracting') - : t('imageWorkspace.input.extractFromImage') - }} - - services.value?.promptService) // 过程态(本地,不持久化) const isOptimizing = ref(false) const isIterating = ref(false) -const isExtractingFromImage = ref(false) -const extractImageInputRef = ref(null) const uploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle') const uploadProgress = ref(0) @@ -1006,8 +921,6 @@ const optimizedReasoning = computed({ // Text 模型选择(与模板选择对齐:自动刷新 + 兜底写回 session store) const modelSelection = useWorkspaceTextModelSelection(services, session) const selectedTextModelKey = modelSelection.selectedTextModelKey -const functionModelManager = useFunctionModelManager(services) -const effectiveImageRecognitionModelKey = functionModelManager.effectiveImageRecognitionModel const selectedImageModelKey = computed({ get: () => session.selectedImageModelKey || '', @@ -1050,55 +963,6 @@ const previewImageUrl = computed(() => { return `data:${mimeType};base64,${inputImageB64.value}` }) -const ensureModelManagerInitializedIfSupported = async (manager: unknown) => { - if (!manager || typeof manager !== 'object') return - - const maybeInitializable = manager as { ensureInitialized?: () => Promise } - if (typeof maybeInitializable.ensureInitialized === 'function') { - await maybeInitializable.ensureInitialized() - } -} - -const getImageRecognitionModelConfig = async (): Promise => { - const manager = services.value?.modelManager - if (!manager) { - throw new Error(t('toast.error.serviceInit')) - } - - await ensureModelManagerInitializedIfSupported(manager) - await functionModelManager.initialize() - - const modelKey = functionModelManager.effectiveImageRecognitionModel.value || '' - if (!modelKey) { - throw new Error(t('functionModel.noImageRecognitionModel')) - } - - const modelConfig = await manager.getModel(modelKey) - if (!modelConfig) { - throw new Error(t('functionModel.noImageRecognitionModel')) - } - - return modelConfig -} - -const openFunctionModelManager = () => { - if (appOpenModelManager) { - appOpenModelManager('function') - return - } - - if (typeof window !== 'undefined') { - window.dispatchEvent( - new CustomEvent('model-manager:set-tab', { detail: 'function' }), - ) - } -} - -const promptConfigureImageRecognitionModel = () => { - toast.error(t('functionModel.noImageRecognitionModel')) - openFunctionModelManager() -} - // 固定模板类型 const templateType = computed(() => "image2imageOptimize" as const) @@ -1779,122 +1643,6 @@ const promptSummary = computed(() => { : originalPrompt.value; }); -const resetExtractedPromptArtifacts = () => { - currentChainId.value = '' - currentVersions.value = [] - currentVersionId.value = '' - - session.updateOptimizedResult({ - optimizedPrompt: '', - reasoning: '', - chainId: '', - versionId: '', - }) - -} - -const readImageFileAsBase64 = (file: File): Promise<{ base64: string; mimeType: string }> => - new Promise((resolve, reject) => { - const reader = new FileReader() - - reader.onload = () => { - const dataUrl = reader.result as string - const base64 = dataUrl.split(',')[1] - resolve({ - base64, - mimeType: file.type || 'image/png', - }) - } - - reader.onerror = () => { - reject(new Error(t('imageWorkspace.upload.readFailed'))) - } - - reader.readAsDataURL(file) - }) - -const extractPromptFromReferenceImage = async (imageB64: string, mimeType: string) => { - if (!effectiveImageRecognitionModelKey.value) { - toast.error(t('functionModel.noImageRecognitionModel')) - return - } - - const modelConfig = await getImageRecognitionModelConfig() - const result = await resolveReferencePromptPreview({ - mode: 'replicate', - originalPrompt: '', - imageB64, - mimeType: mimeType || 'image/png', - modelConfig, - templateManager: services.value?.templateManager, - referenceMode: 'image2image', - }) - - const variableCount = replaceTemporaryVariablesForPrompt( - result.prompt, - result.variableDefaults, - () => tempVarsManager.listVariables(), - (name, value) => tempVarsManager.setVariable(name, value), - (name) => tempVarsManager.deleteVariable(name), - ) - - resetExtractedPromptArtifacts() - originalPrompt.value = result.prompt - - toast.success( - variableCount > 0 - ? t('imageWorkspace.input.extractCompletedWithVariables', { - count: variableCount, - }) - : t('imageWorkspace.input.extractCompleted'), - ) -} - -const openExtractImagePicker = () => { - if (!effectiveImageRecognitionModelKey.value) { - promptConfigureImageRecognitionModel() - return - } - - extractImageInputRef.value?.click() -} - -const handleExtractImageFileChange = async (event: Event) => { - const input = event.target as HTMLInputElement | null - const file = input?.files?.[0] || null - - if (input) { - input.value = '' - } - - if (!file || isExtractingFromImage.value) { - return - } - - if (!/image\/(png|jpeg)/.test(file.type)) { - toast.error(t('imageWorkspace.upload.fileTypeNotSupported')) - return - } - - if (file.size > 10 * 1024 * 1024) { - toast.error(t('imageWorkspace.upload.fileTooLarge')) - return - } - - isExtractingFromImage.value = true - - try { - const { base64, mimeType } = await readImageFileAsBase64(file) - await extractPromptFromReferenceImage(base64, mimeType) - } catch (error) { - toast.error( - getI18nErrorMessage(error, t('imageWorkspace.input.extractFailed')), - ) - } finally { - isExtractingFromImage.value = false - } -} - // 注入 App 层统一的 openTemplateManager / openModelManager / handleSaveFavorite 接口 type TemplateEntryType = | "optimize" diff --git a/packages/ui/src/services/ImageStyleExtractor.ts b/packages/ui/src/services/ImageStyleExtractor.ts index b5f9956e..91b5e725 100644 --- a/packages/ui/src/services/ImageStyleExtractor.ts +++ b/packages/ui/src/services/ImageStyleExtractor.ts @@ -127,9 +127,6 @@ async function buildReferencePromptPrompts( function normalizeReferencePromptPreview(rawText: string): ReferencePromptPreview { const parsed = parseJsonObject(rawText) - const promptObject = normalizePromptObject( - resolvePromptObject(parsed.prompt ?? parsed.promptJson ?? parsed), - ) const variableDefaults = normalizeVariableDefaults( isRecord(parsed.defaults) ? parsed.defaults @@ -137,6 +134,10 @@ function normalizeReferencePromptPreview(rawText: string): ReferencePromptPrevie ? parsed.variableDefaults : {}, ) + const promptObject = normalizePromptObject( + resolvePromptObject(parsed.prompt ?? parsed.promptJson ?? parsed), + Object.keys(variableDefaults), + ) const constrainedPromptObject = constrainPromptVariables(promptObject, variableDefaults) const formattedPrompt = JSON.stringify(constrainedPromptObject, null, 2) @@ -177,9 +178,44 @@ function normalizeVariableDefaults(value: unknown): Record { }, {}) } -function normalizePromptObject(value: Record): Record { - const normalizeString = (content: string): string => - content.replace(/(?, + knownVariableNames: string[] = [], +): Record { + const knownVariableSet = new Set( + knownVariableNames + .map((name) => name.trim()) + .filter((name) => isValidVariableName(name)), + ) + + const normalizeString = (content: string): string => { + let normalized = content.replace( + /(? { + const variableName = rawVariableName.trim() + return knownVariableSet.has(variableName) ? `{{${variableName}}}` : match + }, + ) + + normalized = normalized.replace( + /『([a-zA-Z0-9_\-\u4e00-\u9fa5]+)』/g, + (match, rawVariableName: string) => { + const variableName = rawVariableName.trim() + return knownVariableSet.has(variableName) ? `{{${variableName}}}` : match + }, + ) + + return normalized + } const normalize = (current: unknown): unknown => { if (typeof current === 'string') { diff --git a/packages/ui/tests/unit/image/image-image2image-reference-guards.spec.ts b/packages/ui/tests/unit/image/image-image2image-reference-guards.spec.ts new file mode 100644 index 00000000..3cc87d0a --- /dev/null +++ b/packages/ui/tests/unit/image/image-image2image-reference-guards.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' + +const readSource = (relativePath: string) => + readFileSync(resolve(process.cwd(), relativePath), 'utf8') + +describe('image2image reference entry guards', () => { + it('removes reference extraction entry from image2image without affecting text2image reference flow', () => { + const image2imageSource = readSource('src/components/image-mode/ImageImage2ImageWorkspace.vue') + const text2imageSource = readSource('src/components/image-mode/ImageText2ImageWorkspace.vue') + + expect(image2imageSource).not.toMatch(/image-image2image-extract-button/) + expect(image2imageSource).not.toMatch(/openExtractImagePicker/) + expect(image2imageSource).not.toMatch(/handleExtractImageFileChange/) + expect(image2imageSource).not.toMatch(/extractPromptFromReferenceImage/) + expect(image2imageSource).not.toMatch(/resolveReferencePromptPreview/) + expect(image2imageSource).not.toMatch(/extractImageInputRef/) + expect(text2imageSource).toMatch(/resolveReferencePromptPreview/) + expect(text2imageSource).toMatch(/referenceActionButtons/) + }) +}) diff --git a/packages/ui/tests/unit/services/ImageStyleExtractor.reference-migration.spec.ts b/packages/ui/tests/unit/services/ImageStyleExtractor.reference-migration.spec.ts index 18a0150b..eb86d743 100644 --- a/packages/ui/tests/unit/services/ImageStyleExtractor.reference-migration.spec.ts +++ b/packages/ui/tests/unit/services/ImageStyleExtractor.reference-migration.spec.ts @@ -328,4 +328,59 @@ describe('ImageStyleExtractor reference migration pipeline', () => { 场景主题: '', }) }) + + it('会将中文书名号包裹的变量占位规范化为双花括号', async () => { + const templateManager = { + getTemplate: vi.fn().mockResolvedValue({ + id: 'image-prompt-migration', + name: 'Migrate Prompt With Reference Image', + content: 'unused', + metadata: { + version: '1.0.0', + lastModified: Date.now(), + templateType: 'image-prompt-migration', + language: 'zh', + }, + }), + } + + mockProcessTemplate.mockReturnValue([ + { role: 'system', content: 'migrate prompt with image style' }, + { role: 'user', content: 'keep original subject, transfer the image style' }, + ]) + + mockUnderstand.mockResolvedValue({ + content: JSON.stringify({ + prompt: { + 主体: '一个「主体人物」,「人物动作」', + 背景: '保留参考图的「背景元素」和灯光结构', + }, + defaults: { + 主体人物: '年轻男生', + 人物动作: '戴着耳机,侧脸特写', + 背景元素: '城市夜景', + }, + }), + }) + + const preview = await resolveReferencePromptPreview({ + mode: 'migrate', + originalPrompt: '一个戴着耳机的年轻男生侧脸特写', + imageB64: 'ZmFrZS1pbWFnZQ==', + mimeType: 'image/png', + modelConfig, + templateManager: templateManager as any, + referenceMode: 'text2image', + }) + + expect(preview.prompt).toContain('{{主体人物}}') + expect(preview.prompt).toContain('{{人物动作}}') + expect(preview.prompt).toContain('{{背景元素}}') + expect(preview.prompt).not.toContain('「主体人物」') + expect(preview.variableDefaults).toEqual({ + 主体人物: '年轻男生', + 人物动作: '戴着耳机,侧脸特写', + 背景元素: '城市夜景', + }) + }) })