Files
vibegif.lol/assets/js/openrouter.js

103 lines
2.9 KiB
JavaScript

const OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions";
function toDataUrlMaybe(item) {
if (!item) return null;
if (typeof item === "string") {
if (item.startsWith("http://") || item.startsWith("https://") || item.startsWith("data:image/")) return item;
return `data:image/png;base64,${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 msg = json?.choices?.[0]?.message;
if (!msg) return null;
// 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;
}
}
// 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?.imageUrl?.url || part?.url;
if (u) return u;
}
if (part?.type === "output_image" && part?.image_url?.url) return part.image_url.url;
}
}
// 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 maybe = toDataUrlMaybe(pool[0]);
if (maybe) return maybe;
}
}
return null;
}
export async function generateImageFrame({
apiKey,
model,
textPrompt,
previousFrames = [],
imageSize = "1K",
aspectRatio = "1:1"
}) {
// 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 }
});
}
const body = {
model,
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, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"HTTP-Referer": window.location.origin,
"X-Title": "vibegif.lol"
},
body: JSON.stringify(body)
});
const json = await res.json().catch(() => ({}));
if (!res.ok) {
const msg = json?.error?.message || `OpenRouter error (${res.status})`;
throw new Error(msg);
}
const image = extractImageFromResponse(json);
if (!image) throw new Error("No image found in model response.");
return image;
}