From d512ed31f7a17f470108f584162b7d1656d51a68 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Fri, 20 Mar 2026 21:22:26 -0700 Subject: [PATCH] Fix: docs-aligned image request and response parsing --- assets/js/openrouter.js | 81 ++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/assets/js/openrouter.js b/assets/js/openrouter.js index 55e3c40..9b419d4 100644 --- a/assets/js/openrouter.js +++ b/assets/js/openrouter.js @@ -8,51 +8,46 @@ function toDataUrlMaybe(item) { } if (item.url) return item.url; if (item.image_url?.url) return item.image_url.url; + if (item.imageUrl?.url) return item.imageUrl.url; if (item.b64_json) return `data:image/png;base64,${item.b64_json}`; if (item.data) return `data:image/png;base64,${item.data}`; return null; } function extractImageFromResponse(json) { - const c = json?.choices?.[0]; - if (!c) return null; + const msg = json?.choices?.[0]?.message; + if (!msg) return null; - const msg = c.message || {}; - const content = msg.content; + // Primary docs format: message.images[].image_url.url + if (Array.isArray(msg.images) && msg.images.length) { + for (const image of msg.images) { + const url = image?.image_url?.url || image?.imageUrl?.url || image?.url; + if (url) return url; + const maybe = toDataUrlMaybe(image); + if (maybe) return maybe; + } + } - if (Array.isArray(content)) { - for (const part of content) { + // Fallback: content parts + if (Array.isArray(msg.content)) { + for (const part of msg.content) { if (part?.type === "image_url") { - const u = part?.image_url?.url || part?.url; + const u = part?.image_url?.url || part?.imageUrl?.url || part?.url; if (u) return u; } if (part?.type === "output_image" && part?.image_url?.url) return part.image_url.url; - if (part?.type === "text" && typeof part.text === "string") { - const m = part.text.match(/https?:\/\/\S+\.(?:png|jpg|jpeg|webp|gif)/i); - if (m) return m[0]; - } } } - const fallbackPools = [ - json?.images, - json?.data, - msg?.images, - c?.images - ]; - + // Extra fallback pools + const fallbackPools = [json?.images, json?.data, msg?.data, json?.choices?.[0]?.images]; for (const pool of fallbackPools) { if (Array.isArray(pool) && pool.length) { - const x = toDataUrlMaybe(pool[0]); - if (x) return x; + const maybe = toDataUrlMaybe(pool[0]); + if (maybe) return maybe; } } - if (typeof content === "string") { - const m = content.match(/https?:\/\/\S+\.(?:png|jpg|jpeg|webp|gif)/i); - if (m) return m[0]; - } - return null; } @@ -64,37 +59,24 @@ export async function generateImageFrame({ imageSize = "1K", aspectRatio = "1:1" }) { - const messages = []; - - // rolling window of 2 frames only - const recent = previousFrames.slice(-2); - - if (recent.length) { - for (const frame of recent) { - messages.push({ - role: "user", - content: [ - { type: "image_url", image_url: { url: frame } } - ] - }); - } + // Docs recommend text first, then images in content array + const content = [{ type: "text", text: textPrompt }]; + for (const frame of previousFrames.slice(-2)) { + content.push({ + type: "image_url", + image_url: { url: frame } + }); } - messages.push({ - role: "user", - content: [ - { type: "text", text: textPrompt } - ] - }); - const body = { model, - modalities: ["image"], - messages, + modalities: ["image"], // As requested: image only + messages: [{ role: "user", content }], image_config: { image_size: imageSize, aspect_ratio: aspectRatio - } + }, + stream: false }; const res = await fetch(OPENROUTER_URL, { @@ -109,7 +91,6 @@ export async function generateImageFrame({ }); const json = await res.json().catch(() => ({})); - if (!res.ok) { const msg = json?.error?.message || `OpenRouter error (${res.status})`; throw new Error(msg);