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