同步前端图片模型测试到 GPT Image 2.5 重命名

- 6 个 image-editor 测试文件的旧 gpt-image-2 断言改为 gpt-image-2.5 / GPT Image 2.5,覆盖图集导出、元数据弹窗、UI 素材提取模型菜单与生成/快速编辑 dialog 参数快照
- 工作流测试夹具与提取模型按钮改用 IMAGE_MODEL_GPT_IMAGE_2_5 常量,legacy 夹具保留并断言提交边界归一为当前业务模型
- decision-log 2026-09-21 条目补 platform-image 与前端全量测试验证结论
This commit is contained in:
2026-09-22 11:47:15 +08:00
parent 71a17d6096
commit 46c8573b0e
7 changed files with 28 additions and 27 deletions
@@ -7,7 +7,7 @@
- 边界:nanobanana 生成与编辑共用一个 concrete model,参考图不改变档位;不引入跨模型 fallback;历史持久化值与审计字符串不改写。
- 同批修复:`raw_image_edit` 发送前复用共享 `ensure_provider_matches_model` 白名单校验;`platform-image` 兜底 task id 前缀改为跟随实际 provider`tiantoken-edit` / `tiantoken-nanobanana` 不再带 `vector-engine` 前缀);`resolve_image_provider` 增加 `gpt-image-2 → Tiantoken` 断言把历史值意图钉死;删除 `openai_image_generation.rs` 中仅测试使用的 `create_openai_image_edit` / `create_openai_image_edit_with_references`(测试改用 `..._and_model` 变体,保留空参考图本地校验覆盖);`normalize_editor_generation_options` 的具体 provider key 改用常量;External v1 OpenAPI 的 `model` 描述改为与实现一致(`kind=ui-design` / `publication-material` 固定 `gpt-image-2.5`,UI 素材提取未传默认 nanobanana)。
- 影响范围:`api-server``editor_generation_config``editor_project``editor_project_icon``character_visual_assets``editor_agent/tool``openai_image_generation`)、`platform-image``raw_image_edit``runtime/executor`、集成测试)、ADR、里程碑文档、External v1 OpenAPI 描述、`.codex/skills/gpt-image-2-apimart` 脚本与 SKILL.md(改用 `TIANTOKEN_*` 凭据 + concrete model)。
- 验证:`cargo test -p api-server editor_generation_config``cargo test -p api-server editor_agent::tool``cargo test -p api-server`1123 passed / 1 failed,失败项为 mock LLM 连接超时,单测隔离重跑通过)。
- 验证:`cargo test -p api-server editor_generation_config``cargo test -p api-server editor_agent::tool``cargo test -p api-server`1123 passed / 1 failed,失败项为 mock LLM 连接超时,单测隔离重跑通过)`cargo test -p platform-image`22 + 64 + 14 passed);`npx vitest run` 全量 382 files / 4619 passed6 个测试文件的旧 `gpt-image-2` 断言与 mock 请求体读取竞态同步修正);`npm run typecheck``npm run check:encoding``git diff --check` 通过
- 关联文档:[`docs/adr/【ADR】GPT Image 2.5模型路由与历史值兼容-2026-09-18.md`](../../adr/【ADR】GPT%20Image%202.5模型路由与历史值兼容-2026-09-18.md)、[`docs/project-memory/plans/【里程碑】GPT Image 2.5 provider边界重构-2026-09-18.md`](../plans/【里程碑】GPT%20Image%202.5%20provider边界重构-2026-09-18.md)。
## 2026-09-21 图片 provider 白名单收紧,并整体删除 gpt-image-2-c
@@ -1527,7 +1527,7 @@ describe('ImageCanvasEditorView generation integration', () => {
expect(generateEditorImageMock).toHaveBeenCalledWith(
expect.objectContaining({
kind: 'spec',
model: 'gpt-image-2',
model: 'gpt-image-2.5',
size: '2048x1152',
aspectRatio: '16:9',
imageSize: '2K',
@@ -1872,7 +1872,7 @@ describe('ImageCanvasEditorView generation integration', () => {
await waitFor(() => {
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
expect.objectContaining({
model: 'gpt-image-2',
model: 'gpt-image-2.5',
aspectRatio: '2:3',
imageSize: '2K',
}),
@@ -2315,7 +2315,7 @@ describe('ImageCanvasEditorView generation integration', () => {
expect(generateEditorImageMock).toHaveBeenCalledWith(
expect.objectContaining({
kind: 'spec',
model: 'gpt-image-2',
model: 'gpt-image-2.5',
size: '2048x1152',
aspectRatio: '16:9',
imageSize: '2K',
@@ -3894,7 +3894,7 @@ describe('ImageCanvasEditorView generation integration', () => {
expect(within(metadataDialog).getByText('生成提示词')).toBeTruthy();
expect(within(metadataDialog).getByText('一张可修改的生成图')).toBeTruthy();
expect(within(metadataDialog).getByText('Model')).toBeTruthy();
expect(within(metadataDialog).getByText('gpt-image-2')).toBeTruthy();
expect(within(metadataDialog).getByText('GPT Image 2.5')).toBeTruthy();
expect(within(metadataDialog).queryByText('Size')).toBeNull();
expect(within(metadataDialog).getByText('Resolution')).toBeTruthy();
expect(within(metadataDialog).getByText('1024 x 1024 px')).toBeTruthy();
@@ -1274,7 +1274,7 @@ describe('ImageCanvasEditorView', () => {
fields: [{ title: '生成提示词', value: '明亮主视觉' }],
references: [],
},
model: 'gpt-image-2',
model: 'GPT Image 2.5',
task: '1',
object: 'asset-object-visible',
resolution: '1024 x 1024 px',
@@ -187,7 +187,7 @@ describe('ImageCanvasExportModel', () => {
},
],
},
model: 'gpt-image-2',
model: 'GPT Image 2.5',
task: '1',
object: 'generated/layer.png',
resolution: '1024 x 1024 px',
@@ -268,7 +268,7 @@ describe('ImageCanvasExportModel', () => {
],
references: [],
});
expect(metadata.visible.model).toBe('gpt-image-2');
expect(metadata.visible.model).toBe('GPT Image 2.5');
});
it('builds a Spine JSON attachment timeline from image sequence frames', () => {
@@ -81,7 +81,7 @@ describe('ImageCanvasMetadataModalView', () => {
within(dialog).getByText('项目资源 · resource-reference'),
).toBeTruthy();
expect(within(dialog).getByText('Model')).toBeTruthy();
expect(within(dialog).getByText('gpt-image-2')).toBeTruthy();
expect(within(dialog).getByText('GPT Image 2.5')).toBeTruthy();
expect(within(dialog).getByText('1024 x 768 px')).toBeTruthy();
expect(within(dialog).queryByText('Provider')).toBeNull();
expect(within(dialog).queryByText(/VectorEngine/u)).toBeNull();
@@ -129,7 +129,7 @@ describe('ImageCanvasMetadataModalView', () => {
expect(within(dialog).getByText('角色抠图')).toBeTruthy();
expect(within(dialog).queryByText('处理模型')).toBeNull();
expect(within(dialog).getByText('Model')).toBeTruthy();
expect(within(dialog).getByText('gpt-image-2')).toBeTruthy();
expect(within(dialog).getByText('GPT Image 2.5')).toBeTruthy();
expect(within(dialog).queryByText('birefnet')).toBeNull();
expect(within(dialog).queryByText('Aliyun Matting')).toBeNull();
expect(within(dialog).queryByText('segment-common-image')).toBeNull();
@@ -6,7 +6,7 @@ import { describe, expect, it, vi } from 'vitest';
import type { CanvasLayer } from './ImageCanvasEditorTypes';
import {
IMAGE_MODEL_GPT_IMAGE_2,
IMAGE_MODEL_GPT_IMAGE_2_5,
IMAGE_MODEL_NANOBANANA2,
} from './ImageCanvasGenerationModel';
import type { UiAssetExtractionState } from './ImageCanvasUiAssetExtractionModel';
@@ -229,11 +229,11 @@ describe('ImageCanvasUiAssetExtractionOverlayView', () => {
fireEvent.click(
within(screen.getByRole('menu', { name: '提取素材模型选项' })).getByRole(
'button',
{ name: 'gpt-image-2' },
{ name: 'GPT Image 2.5' },
),
);
expect(onModelChange).toHaveBeenCalledWith(IMAGE_MODEL_GPT_IMAGE_2);
expect(onModelChange).toHaveBeenCalledWith(IMAGE_MODEL_GPT_IMAGE_2_5);
});
it('disables extraction submission while reference uploads are pending', () => {
@@ -298,12 +298,12 @@ describe('ImageCanvasUiAssetExtractionOverlayView', () => {
fireEvent.click(
within(screen.getByRole('menu', { name: '提取素材模型选项' })).getByRole(
'button',
{ name: 'gpt-image-2' },
{ name: 'GPT Image 2.5' },
),
);
expect(alertMock).toHaveBeenCalledWith(
'当前已有 5 张参考图,gpt-image-2 最多允许 4 张,请先删除多余参考图后再切换',
'当前已有 5 张参考图,GPT Image 2.5 最多允许 4 张,请先删除多余参考图后再切换',
);
expect(onModelChange).not.toHaveBeenCalled();
expect(screen.getByRole('menu', { name: '提取素材模型选项' })).toBeTruthy();
@@ -31,6 +31,7 @@ import {
CANVAS_GENERATION_PARAMETER_FALLBACK_WARNING,
DEFAULT_IMAGE_MODEL,
IMAGE_MODEL_GPT_IMAGE_2,
IMAGE_MODEL_GPT_IMAGE_2_5,
} from './ImageCanvasGenerationModel';
import {
readPerfectPixelOperations,
@@ -622,7 +623,7 @@ function GenerationWorkflowHarness({
status: 'idle',
composerOpen: true,
sourceLayerId: 'layer-source',
imageModel: 'gpt-image-2',
imageModel: IMAGE_MODEL_GPT_IMAGE_2_5,
aspectRatio: '2:3',
imageSize: '2K',
})
@@ -780,7 +781,7 @@ function GenerationWorkflowHarness({
status: 'idle',
composerOpen: true,
generatedLayerId: 'layer-source',
imageModel: 'gpt-image-2',
imageModel: IMAGE_MODEL_GPT_IMAGE_2_5,
aspectRatio: '2:3',
imageSize: '2K',
})
@@ -797,7 +798,7 @@ function GenerationWorkflowHarness({
status: 'generating',
composerOpen: false,
sourceLayerId: 'layer-source',
imageModel: 'gpt-image-2',
imageModel: IMAGE_MODEL_GPT_IMAGE_2_5,
aspectRatio: '1:1',
imageSize: '1K',
})
@@ -1241,7 +1242,7 @@ function GenerationWorkflowHarness({
<button
type="button"
onClick={() =>
workflow.changeUiAssetExtractionModel(IMAGE_MODEL_GPT_IMAGE_2)
workflow.changeUiAssetExtractionModel(IMAGE_MODEL_GPT_IMAGE_2_5)
}
>
GPT提取模型
@@ -1504,7 +1505,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
'游戏名:星海远征\n游戏分类:冒险\n一句话描述游戏:驾驶飞船探索星云',
);
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
'publication:gpt-image-2:9:16:1K',
'publication:gpt-image-2.5:9:16:1K',
);
});
@@ -1522,7 +1523,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
'layer-source:idle:-',
);
expect(screen.getByTestId('quick-edit-image-options').textContent).toBe(
'quick-edit:gpt-image-2:2:3:2K',
'quick-edit:gpt-image-2.5:2:3:2K',
);
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
'layer-source:none:0',
@@ -1722,7 +1723,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(generateEditorImageMock).toHaveBeenCalledWith(
expect.objectContaining({
prompt: '旧版森林场景',
model: IMAGE_MODEL_GPT_IMAGE_2,
model: IMAGE_MODEL_GPT_IMAGE_2_5,
generationInputs: expect.objectContaining({
version: 2,
action: 'image.generate',
@@ -1788,7 +1789,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect.objectContaining({
prompt: '红发骑士',
kind: 'character',
model: IMAGE_MODEL_GPT_IMAGE_2,
model: IMAGE_MODEL_GPT_IMAGE_2_5,
referenceImageSrcs: ['resource-character-spec'],
generationInputs: expect.objectContaining({
version: 2,
@@ -1880,7 +1881,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(screen.getByTestId('dialog').textContent).toBe('-');
expect(screen.getByTestId('quick-edit-image-options').textContent).toBe(
'quick-edit:gpt-image-2:2:3:2K',
'quick-edit:gpt-image-2.5:2:3:2K',
);
});
@@ -1906,7 +1907,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
'generate:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
'generate:gpt-image-2:2:3:2K',
'generate:gpt-image-2.5:2:3:2K',
);
});
@@ -1925,7 +1926,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
'generate:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
'generate:gpt-image-2:2:3:2K',
'generate:gpt-image-2.5:2:3:2K',
);
});
@@ -5131,7 +5132,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
fireEvent.click(screen.getByRole('button', { name: '框选UI素材' }));
expect(screen.getByTestId('ui-extraction').textContent).toBe(
'layer-source:ellipse:1:idle:gpt-image-2',
'layer-source:ellipse:1:idle:gpt-image-2.5',
);
});