mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
fix(picture): use image[] array syntax for multi-ref gpt-image-2 edits
The try-on path POST'd N reference images as repeated `image` fields in the multipart body. OpenAI's edits endpoint answers that with `duplicate_parameter: Duplicate parameter: 'image'. You provided multiple values for this parameter, whereas only one is allowed. If you are trying to provide a list of values, use the array syntax instead e.g. 'image[]=<value>'.` Switch to the array-syntax field name `image[]`, which OpenAI accepts for cardinality ≥ 1 (no branching needed for the single-ref case). Also surface the underlying error from the three 502 branches (ownership-check, media-fetch, OpenAI call) into both the server log (structured console.error with refIds + openai body) and the response `detail` field. The client's callGenerateWithReference now prepends `detail` to the thrown message so the user sees the concrete reason in-module instead of a generic "Try-On fehlgeschlagen (502)". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f9ca6ca44b
commit
15beddeda9
2 changed files with 41 additions and 8 deletions
|
|
@ -309,7 +309,12 @@ routes.post('/generate-with-reference', async (c) => {
|
|||
if (e.status === 404) {
|
||||
return c.json({ error: 'Reference media not found', missing: e.missing }, 404);
|
||||
}
|
||||
return c.json({ error: 'Ownership check failed' }, 502);
|
||||
console.error('[picture/generate-with-reference] ownership check failed', {
|
||||
userId,
|
||||
refIds,
|
||||
error: e.message,
|
||||
});
|
||||
return c.json({ error: 'Ownership check failed', detail: e.message }, 502);
|
||||
}
|
||||
|
||||
// Fetch reference buffers in parallel. The mana-media /file route is
|
||||
|
|
@ -325,8 +330,13 @@ routes.post('/generate-with-reference', async (c) => {
|
|||
filename: `ref-${i}.${ext === 'jpeg' ? 'jpg' : ext}`,
|
||||
};
|
||||
});
|
||||
} catch (_err) {
|
||||
return c.json({ error: 'Failed to fetch reference media' }, 502);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error('[picture/generate-with-reference] failed to fetch reference media', {
|
||||
refIds,
|
||||
error: message,
|
||||
});
|
||||
return c.json({ error: 'Failed to fetch reference media', detail: message }, 502);
|
||||
}
|
||||
|
||||
// Multipart POST to OpenAI. FormData auto-sets Content-Type with a
|
||||
|
|
@ -337,9 +347,14 @@ routes.post('/generate-with-reference', async (c) => {
|
|||
formData.append('size', size);
|
||||
formData.append('quality', quality);
|
||||
formData.append('n', String(effectiveBatch));
|
||||
// gpt-image-* accepts a repeated `image` field for multi-reference.
|
||||
// gpt-image-* requires the array-syntax `image[]` for multi-reference
|
||||
// calls — a repeated plain `image` field triggers OpenAI's
|
||||
// `duplicate_parameter` error even though the old DALL·E edits
|
||||
// endpoint tolerated it. Keep `image[]` for the single-ref case too:
|
||||
// OpenAI accepts the array form with any cardinality ≥ 1, so there's
|
||||
// no need to branch here.
|
||||
for (const ref of referenceBlobs) {
|
||||
formData.append('image', ref.blob, ref.filename);
|
||||
formData.append('image[]', ref.blob, ref.filename);
|
||||
}
|
||||
|
||||
let generatedBuffers: ArrayBuffer[];
|
||||
|
|
@ -351,6 +366,16 @@ routes.post('/generate-with-reference', async (c) => {
|
|||
});
|
||||
if (!res.ok) {
|
||||
const detail = await res.text().catch(() => '');
|
||||
console.error('[picture/generate-with-reference] OpenAI returned non-ok', {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
body: detail.slice(0, 1000),
|
||||
refCount: referenceBlobs.length,
|
||||
prompt: prompt.slice(0, 120),
|
||||
model: openaiModel,
|
||||
size,
|
||||
quality,
|
||||
});
|
||||
return c.json({ error: 'OpenAI image edit failed', detail: detail.slice(0, 500) }, 502);
|
||||
}
|
||||
const data = (await res.json()) as { data?: Array<{ b64_json?: string }> };
|
||||
|
|
@ -360,8 +385,10 @@ routes.post('/generate-with-reference', async (c) => {
|
|||
const bin = Buffer.from(b64, 'base64');
|
||||
return bin.buffer.slice(bin.byteOffset, bin.byteOffset + bin.byteLength) as ArrayBuffer;
|
||||
});
|
||||
} catch (_err) {
|
||||
return c.json({ error: 'OpenAI image edit failed' }, 502);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error('[picture/generate-with-reference] OpenAI fetch threw', { error: message });
|
||||
return c.json({ error: 'OpenAI image edit failed', detail: message }, 502);
|
||||
}
|
||||
|
||||
// Success path: consume credits, then upload the new images.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ async function callGenerateWithReference(opts: {
|
|||
if (!res.ok) {
|
||||
const body = (await res.json().catch(() => ({}))) as {
|
||||
error?: string;
|
||||
detail?: string;
|
||||
required?: number;
|
||||
missing?: string[];
|
||||
};
|
||||
|
|
@ -60,7 +61,12 @@ async function callGenerateWithReference(opts: {
|
|||
'Ein oder mehrere Referenzbilder sind im Server-Ownership-Check durchgefallen — vermutlich sind Face/Body noch nicht in diesem Space hochgeladen.'
|
||||
);
|
||||
}
|
||||
throw new Error(body.error ?? `Try-On fehlgeschlagen (${res.status})`);
|
||||
// Surface the server's `detail` so the user sees *why* it failed
|
||||
// (OpenAI policy rejection, media-download timeout, etc.) instead
|
||||
// of a generic "Try-On fehlgeschlagen". Server always includes
|
||||
// detail on 502 branches — see routes.ts generate-with-reference.
|
||||
const label = body.error ?? `Try-On fehlgeschlagen (${res.status})`;
|
||||
throw new Error(body.detail ? `${label}: ${body.detail}` : label);
|
||||
}
|
||||
|
||||
const data = (await res.json()) as {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue