diff --git a/.memory/worklog.json b/.memory/worklog.json index 9398246..756298c 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -484,6 +484,19 @@ "message": "auto-save 2026-05-19 10:40 (+1, ~3)", "hash": "12159ca", "files_changed": 4 + }, + { + "ts": "2026-05-19T10:43:58+08:00", + "type": "commit", + "message": "feat: add upload replicate mode", + "hash": "a9b1250", + "files_changed": 3 + }, + { + "ts": "2026-05-19T02:50:00Z", + "type": "session-heartbeat", + "message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:feat: add upload replicate mode", + "files_changed": 1 } ] } diff --git a/src/lib/providers.ts b/src/lib/providers.ts index dfa81b5..45fb5a4 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -44,12 +44,19 @@ export async function generateMock(opts: { }); } -function readImageBase64(payload: unknown): string { +async function imageResponseToDataUrl(payload: unknown): Promise { const data = payload as { data?: Array<{ b64_json?: string; url?: string }> }; const first = data.data?.[0]; if (!first) throw new Error('GPT image response missing data'); - if (first.b64_json) return first.b64_json; - throw new Error('GPT image response missing b64_json'); + if (first.b64_json) return `data:image/png;base64,${first.b64_json}`; + if (first.url) { + const res = await fetch(first.url); + if (!res.ok) throw new Error(`GPT image URL fetch ${res.status}: ${await res.text()}`); + const type = res.headers.get('content-type')?.split(';')[0] || 'image/png'; + const buf = Buffer.from(await res.arrayBuffer()); + return `data:${type};base64,${buf.toString('base64')}`; + } + throw new Error('GPT image response missing b64_json or url'); } export async function generateGptImages(opts: { @@ -77,14 +84,13 @@ export async function generateGptImages(opts: { prompt: `${opts.prompt}${refHint}`, n: 1, size: '1024x1024', - response_format: 'b64_json', }), }); if (!res.ok) throw new Error(`GPT image ${res.status}: ${await res.text()}`); const data = await res.json(); return { id: `img_${opts.sessionId}_${i}`, - url: `data:image/png;base64,${readImageBase64(data)}`, + url: await imageResponseToDataUrl(data), prompt: opts.prompt, status: 'pending' as const, meta: { provider: 'gpt', model: GPT_IMAGE_MODEL, index: i }, @@ -94,14 +100,6 @@ export async function generateGptImages(opts: { return Promise.all(calls); } -function readEditImageBase64(payload: unknown): string { - return readImageBase64(payload); -} - -function dataUrlFromImageResponse(payload: unknown): string { - return `data:image/png;base64,${readEditImageBase64(payload)}`; -} - function readResponseText(data: { output_text?: string; output?: Array<{ content?: Array<{ text?: string }> }>; @@ -136,7 +134,6 @@ export async function generateGptImageEdit(opts: { form.set('model', process.env.GPT_IMAGE_EDIT_MODEL || GPT_IMAGE_MODEL); form.set('prompt', opts.prompt); form.set('size', opts.size || '1024x1024'); - form.set('response_format', 'b64_json'); const imageBytes = source.buf.buffer.slice(source.buf.byteOffset, source.buf.byteOffset + source.buf.byteLength) as ArrayBuffer; form.set('image', new Blob([imageBytes], { type: source.type }), source.filename); @@ -149,7 +146,7 @@ export async function generateGptImageEdit(opts: { const data = await res.json(); return { id: `img_${opts.sessionId}_edit`, - url: dataUrlFromImageResponse(data), + url: await imageResponseToDataUrl(data), prompt: opts.prompt, status: 'pending', meta: { provider: 'gpt', model: process.env.GPT_IMAGE_EDIT_MODEL || GPT_IMAGE_MODEL, edit: true },