import { getApiKey } from './storage.js'; const BASE = 'https://openrouter.ai/api/v1/chat/completions'; const MASTER_PROMPT = 'minimal black and white line doodle, single stroke, white background, kawaii style'; /** * Generates a single image frame via OpenRouter. * @param {Object} opts * @param {string} opts.model * @param {Array} opts.messages - chat history messages * @param {string} opts.imageSize - "1K" or "0.5K" * @param {string} opts.aspectRatio - e.g. "1:1" * @returns {Promise<{base64: string, assistantMsg: Object}>} */ export async function generateFrame({ model, messages, imageSize, aspectRatio }) { const isGemini = model.startsWith('google/'); const body = { model, messages, modalities: ['image'], image_config: { aspect_ratio: aspectRatio, image_size: imageSize, }, }; if (!isGemini && imageSize === '0.5K') { body.image_config.image_size = '1K'; } const res = await fetch(BASE, { method: 'POST', headers: { 'Authorization': `Bearer ${getApiKey()}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://vibegif.lol', 'X-Title': 'vibegif.lol', }, body: JSON.stringify(body), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err?.error?.message || `API error ${res.status}`); } const data = await res.json(); const choice = data.choices?.[0]; if (!choice) throw new Error('No response from model'); const images = choice.message?.images; if (!images?.length) throw new Error('No image in response. The model may have refused or returned text only.'); const url = images[0].image_url?.url || images[0].url; if (!url) throw new Error('Could not parse image from response'); const base64 = url.startsWith('data:') ? url : `data:image/png;base64,${url}`; const assistantMsg = { role: 'assistant', content: [ { type: 'image_url', image_url: { url: base64 } } ] }; return { base64, assistantMsg }; } /** * Build the first user message (frame 1). */ export function buildFirstMessage(userPrompt) { return { role: 'user', content: `${MASTER_PROMPT}, ${userPrompt}` }; } /** * Build follow-up user message for subsequent frames. */ export function buildNextMessage(frameIndex, frameCount) { return { role: 'user', content: `imagine we are trying to create a ${frameCount} frame gif. generate the next meaningful frame (frame ${frameIndex} of ${frameCount})` }; }