From 6ac616e93e8b114e8ba8c6332faa0e4e72010c29 Mon Sep 17 00:00:00 2001 From: jiajia Date: Mon, 11 May 2026 15:42:33 +0800 Subject: [PATCH] Preserve image ratio settings through NewApiWG generation Image generation settings arrive at /api/generate as top-level aspectRatio and resolution fields, but the NewApiWG provider path only forwarded model-specific parameters. Native NewApiWG Gemini image generation therefore fell back to default imageConfig values and ignored composer/control-panel ratio choices. The route now merges top-level image settings into provider parameters while preserving explicit model parameter overrides. Constraint: NewApiWG provider implementations read aspect ratio and image size from GenerationInput.parameters. Rejected: Parse aspect ratio from prompt text | UI settings should be authoritative and prompts are ambiguous. Confidence: high Scope-risk: narrow Directive: Keep top-level GenerateRequest image settings and provider parameters in sync for image providers. Tested: npm test -- --run src/app/api/generate/__tests__/route.test.ts src/store/execution/__tests__/nanoBananaExecutor.test.ts src/components/__tests__/GenerationComposer.test.tsx Tested: npx tsc --noEmit --pretty false filtered to generate route files Not-tested: Full tsc due existing test mock overload errors in route.test.ts --- src/app/api/generate/__tests__/route.test.ts | 54 ++++++++++++++++++++ src/app/api/generate/route.ts | 19 ++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/app/api/generate/__tests__/route.test.ts b/src/app/api/generate/__tests__/route.test.ts index 6d763545..37489823 100644 --- a/src/app/api/generate/__tests__/route.test.ts +++ b/src/app/api/generate/__tests__/route.test.ts @@ -1020,6 +1020,60 @@ describe("/api/generate route", () => { expect(data.image).toMatch(/^data:image\/png;base64,/); expect(data.contentType).toBe("image"); }); + + it("should pass top-level aspect ratio and resolution to native NewApiWG image models", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + candidates: [ + { + content: { + parts: [ + { + inlineData: { + mimeType: "image/png", + data: "nativeImageData", + }, + }, + ], + }, + }, + ], + }), + }); + + const request = createMockPostRequest( + { + prompt: "A tall poster", + aspectRatio: "9:16", + resolution: "2K", + selectedModel: { + provider: "newapiwg", + modelId: "apiyi_nano_banana_2", + displayName: "PopiArt Nano Pro", + capabilities: ["text-to-image", "image-to-image"], + }, + }, + { "X-NewApiWG-Key": "test-newapiwg-key" } + ); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining("/models/gemini-3.1-flash-image-preview:generateContent"), + expect.objectContaining({ + body: expect.stringContaining('"aspectRatio":"9:16"'), + }) + ); + const body = JSON.parse(mockFetch.mock.calls[0][1].body as string); + expect(body.generationConfig.imageConfig).toMatchObject({ + aspectRatio: "9:16", + imageSize: "2K", + }); + }); }); describe("Replicate provider", () => { diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index 6f51b4ee..059598ff 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -83,6 +83,23 @@ function capabilitiesForMediaType(mediaType?: string): ModelCapability[] { return map[mediaType ?? ""] ?? ["text-to-image"]; } +function mergeImageGenerationParameters( + parameters: Record | undefined, + aspectRatio: string | undefined, + resolution: string | undefined +): Record | undefined { + const merged = { ...(parameters || {}) }; + if (aspectRatio && merged.aspectRatio === undefined && merged.aspect_ratio === undefined) { + merged.aspectRatio = aspectRatio; + merged.aspect_ratio = aspectRatio; + } + if (resolution && merged.resolution === undefined && merged.imageSize === undefined) { + merged.resolution = resolution; + merged.imageSize = resolution; + } + return Object.keys(merged).length > 0 ? merged : undefined; +} + export async function POST(request: NextRequest) { const requestId = Math.random().toString(36).substring(7); console.log(`\n[API:${requestId}] ========== NEW GENERATE REQUEST ==========`); @@ -162,7 +179,7 @@ export async function POST(request: NextRequest) { }, prompt: prompt || "", images: images ? [...images] : [], - parameters, + parameters: mergeImageGenerationParameters(parameters, aspectRatio, resolution), dynamicInputs, };