8 changed files with 482 additions and 84 deletions
@ -0,0 +1,199 @@ |
|||
# 多张图片生成技术实现记录 |
|||
|
|||
## 背景 |
|||
|
|||
本功能在 `TEST-s` 分支中为底部生成对话框增加类似 libTV 的图片张数选择能力。用户可以在图片生成模式下选择 `1张`、`2张` 或 `4张`,一次生成得到多张候选图。 |
|||
|
|||
产品语义采用“单节点多候选图”:一次多图生成只创建或更新一个 `nanoBanana` 图片节点,多张结果进入该节点的轮播历史,当前选中的图片作为节点输出。 |
|||
|
|||
核心文件: |
|||
|
|||
- `src/components/composer/GenerationComposer.tsx` |
|||
- `src/store/execution/nanoBananaExecutor.ts` |
|||
- `src/types/nodes.ts` |
|||
- `src/store/utils/nodeDefaults.ts` |
|||
- `src/components/__tests__/GenerationComposer.test.tsx` |
|||
- `src/store/execution/__tests__/nanoBananaExecutor.test.ts` |
|||
- `src/store/utils/__tests__/nodeDefaults.test.ts` |
|||
|
|||
## 当前功能范围 |
|||
|
|||
已实现: |
|||
|
|||
- 底部生成对话框在图片模式下显示张数下拉。 |
|||
- 张数选项固定为 `1张`、`2张`、`4张`。 |
|||
- 新建图片节点时写入 `imageCount`。 |
|||
- 编辑已有图片节点时可以修改 `imageCount`。 |
|||
- `nanoBanana` 执行器按 `imageCount` 顺序请求现有 `/api/generate`。 |
|||
- 多张结果写入同一个节点的 `imageHistory`。 |
|||
- 本次生成的第一张图写入 `outputImage`,并作为当前选中输出。 |
|||
- 每张成功结果都会进入全局图片历史。 |
|||
- 每张成功结果都会追加到下游 `outputGallery`。 |
|||
- 自动保存路径启用时,每张成功结果都会独立保存。 |
|||
- 如果批次中前几张成功、后续失败,保留已成功候选图并把错误记录在节点数据中。 |
|||
|
|||
未实现: |
|||
|
|||
- 不使用 provider 原生 `n` 参数。 |
|||
- 不自动创建多个图片节点。 |
|||
- 不把当前切换到的候选图自动作为下一次生成的参考图。 |
|||
- 不提供“展开为多个图片节点”动作。 |
|||
- 不提供“以当前图继续生成”动作。 |
|||
|
|||
## 状态模型 |
|||
|
|||
图片生成张数由 `NanoBananaNodeData.imageCount` 表示: |
|||
|
|||
```ts |
|||
export type ImageGenerationCount = 1 | 2 | 4; |
|||
|
|||
export interface NanoBananaNodeData extends BaseNodeData { |
|||
imageCount?: ImageGenerationCount; |
|||
} |
|||
``` |
|||
|
|||
字段为可选字段,旧工作流没有该字段时按 `1` 处理。 |
|||
|
|||
新建 `nanoBanana` 节点时默认写入: |
|||
|
|||
```ts |
|||
imageCount: 1 |
|||
``` |
|||
|
|||
底部对话框内部草稿 `ComposerDraft` 同步维护同名字段,避免用户在提交前切换节点或刷新草稿时丢失选择。 |
|||
|
|||
## 底部对话框 |
|||
|
|||
`GenerationComposer` 在图片能力模式下显示三个控制: |
|||
|
|||
```text |
|||
模型 / 画幅比例 / 清晰度 / 张数 |
|||
``` |
|||
|
|||
张数下拉定义为: |
|||
|
|||
```ts |
|||
const IMAGE_COUNTS: ImageGenerationCount[] = [1, 2, 4]; |
|||
``` |
|||
|
|||
提交新节点时,`buildInitialDataForNode("nanoBanana", draft)` 会把 `draft.imageCount` 写入节点数据。 |
|||
|
|||
编辑已有图片节点时,`imageComposerAdapter.readDraft()` 从节点读取 `imageCount`,`buildPatch()` 在字段变脏时回写 `imageCount`。 |
|||
|
|||
## 运行逻辑 |
|||
|
|||
`executeNanoBanana()` 不改变 provider 请求协议。执行器会根据节点的 `imageCount` 做顺序循环: |
|||
|
|||
```text |
|||
imageCount = 1 -> 请求 1 次 |
|||
imageCount = 2 -> 请求 2 次 |
|||
imageCount = 4 -> 请求 4 次 |
|||
``` |
|||
|
|||
每次请求仍然使用现有 `/api/generate` payload,包括: |
|||
|
|||
- `images` |
|||
- `prompt` |
|||
- `aspectRatio` |
|||
- `resolution` |
|||
- `selectedModel` |
|||
- `parameters` |
|||
- `dynamicInputs` |
|||
|
|||
这样可以兼容当前 PopiArt Nano Pro / NewApiWG / Gemini native 链路,因为该链路目前稳定返回单张图片。 |
|||
|
|||
## 结果写入 |
|||
|
|||
每张成功结果会生成一个历史项: |
|||
|
|||
```ts |
|||
{ |
|||
id, |
|||
image, |
|||
timestamp, |
|||
prompt, |
|||
aspectRatio, |
|||
model, |
|||
modelDisplayName, |
|||
modelProvider, |
|||
} |
|||
``` |
|||
|
|||
本次批量结果按生成顺序插入到 `imageHistory` 前面: |
|||
|
|||
```text |
|||
[本次第1张, 本次第2张, 本次第3张, 本次第4张, ...旧历史] |
|||
``` |
|||
|
|||
节点输出规则: |
|||
|
|||
- `outputImage` 使用本次第 1 张。 |
|||
- `selectedHistoryIndex` 设为 `0`。 |
|||
- 节点内已有轮播控件负责切换候选图。 |
|||
- 切换候选图时,下游读取的是当前 `outputImage`。 |
|||
|
|||
## 输出画廊 |
|||
|
|||
如果图片生成节点连接到 `outputGallery`,本次成功的每一张候选图都会调用: |
|||
|
|||
```ts |
|||
appendOutputGalleryImage(target.id, image) |
|||
``` |
|||
|
|||
因此画廊可以直接看到整批候选图,而普通下游节点仍然只读取当前选中的 `outputImage`。 |
|||
|
|||
## 失败处理 |
|||
|
|||
执行器的失败策略: |
|||
|
|||
- 第一张就失败:节点进入 `error`,如果有 fallback model,则走现有 fallback 流程。 |
|||
- 前几张成功、后续失败:节点进入 `complete`,保留成功候选图,并把错误写入 `error`。 |
|||
- 用户取消:继续沿用 `AbortError` 逻辑,不提交未完成批次。 |
|||
|
|||
第一版没有做并发生成,顺序生成更容易保证取消、fallback、历史顺序和保存逻辑一致。 |
|||
|
|||
## 参考图语义 |
|||
|
|||
多张候选图不会改变参考图来源。重新生成仍然使用: |
|||
|
|||
- 上游连接图片 |
|||
- 节点已有 `inputImages` |
|||
- 当前 prompt |
|||
- 当前参数 |
|||
- 当前 `imageCount` |
|||
|
|||
用户切换到第 2/4 张候选图,不会自动把这张图变成下一次生成的参考图。后续如需迭代能力,应单独设计显式动作,例如“以当前图继续生成”。 |
|||
|
|||
## 测试覆盖 |
|||
|
|||
当前相关测试: |
|||
|
|||
```text |
|||
src/components/__tests__/GenerationComposer.test.tsx |
|||
src/store/execution/__tests__/nanoBananaExecutor.test.ts |
|||
src/store/utils/__tests__/nodeDefaults.test.ts |
|||
``` |
|||
|
|||
覆盖点: |
|||
|
|||
- 新建图片节点会保存用户选择的 `imageCount`。 |
|||
- 默认 `nanoBanana` 节点数据包含 `imageCount: 1`。 |
|||
- `imageCount=4` 时执行器请求 4 次。 |
|||
- 多张结果写入同一个节点历史。 |
|||
- 多张结果中的第一张成为 `outputImage`。 |
|||
- 批次中后续失败时保留已成功候选图。 |
|||
- 下游 `outputGallery` 会收到每张候选图。 |
|||
|
|||
建议验证命令: |
|||
|
|||
```bash |
|||
npm run test:run -- src/components/__tests__/GenerationComposer.test.tsx src/store/execution/__tests__/nanoBananaExecutor.test.ts src/store/utils/__tests__/nodeDefaults.test.ts |
|||
``` |
|||
|
|||
## 维护注意点 |
|||
|
|||
- 不要把 `imageCount` 扩展为任意数字,当前产品选项只允许 `1 | 2 | 4`。 |
|||
- 如果未来接入 provider 原生多图参数,需要保留当前循环实现作为兼容 fallback。 |
|||
- 如果未来新增“展开为多个图片节点”,应作为显式用户动作,不应改变默认生成行为。 |
|||
- 如果未来新增“以当前图继续生成”,应明确写入 `inputImages`,不要让普通轮播切换隐式改变参考图。 |
|||
- 自动保存和输出画廊需要继续按“每张成功图片一次写入”维护。 |
|||
Loading…
Reference in new issue