import fetch from 'node-fetch'; const MODELS = [ 'minimax-m2.5-free', // Only working free model as of 2026-05-04 ]; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } export async function generateHyperFramesCode(userPrompt, systemPrompt) { let lastError = null; // Try each model, with a retry on rate limit for (const model of MODELS) { for (let attempt = 0; attempt < 2; attempt++) { try { if (attempt > 0) { console.log(`Retry ${attempt} for model: ${model} after rate limit delay...`); await sleep(5000); } console.log(`Trying model: ${model} (attempt ${attempt + 1})`); const result = await callModel(model, userPrompt, systemPrompt); if (result && result.trim().length > 100) { console.log(`Success with ${model} (${result.length} chars)`); return result; } console.log(`Model ${model} returned insufficient output (${result?.length || 0} chars), trying next...`); break; } catch (err) { console.error(`Model ${model} attempt ${attempt + 1} failed:`, err.message); lastError = err; if (err.message.includes('429') && attempt === 0) { continue; } break; } } } throw lastError || new Error('All AI models failed to generate code'); } async function callModel(model, userPrompt, systemPrompt) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 180000); // 3 min timeout // Generate random user_id to bypass rate limits const randomUserId = `user_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`; try { const response = await fetch('https://opencode.ai/zen/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer public', 'x-opencode-client': 'desktop', 'x-opencode-user-id': randomUserId, 'Accept': 'text/event-stream', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, body: JSON.stringify({ model, messages: [ { role: 'system', content: 'You are a code generator. Output ONLY code, no explanations, no reasoning, no commentary. Start directly with the code block.' }, { role: 'system', content: 'CRITICAL FONT RULE: You MUST use ONLY these fonts: Arial, Helvetica, "Arial Black", Verdana, Tahoma, "Trebuchet MS", Impact, Georgia, "Times New Roman", "Courier New". NEVER use Google Fonts, web fonts, or fonts not in this list. They cause 404 errors.' }, { role: 'system', content: 'CRITICAL GSAP RULE: Every GSAP selector (e.g., "#scene-2 .flare-pulse") MUST match an element that EXISTS in your HTML. Never animate elements you did not create. Check your HTML before writing animations.' }, { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt } ], temperature: 0.7, max_tokens: 8000, stream: true }), signal: controller.signal }); if (!response.ok) { const errText = await response.text().catch(() => ''); throw new Error(`API returned ${response.status}: ${errText.slice(0, 200)}`); } let fullContent = ''; let buffer = ''; // Read response body as text stream for await (const chunk of response.body) { // Decode chunk as UTF-8 const text = chunk.toString('utf-8'); buffer += text; // Process complete lines const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (!line.trim() || !line.startsWith('data:')) { continue; } const data = line.slice(5).trim(); if (data === '[DONE]') { break; } try { const event = JSON.parse(data); const choices = event.choices || []; if (choices.length > 0) { const delta = choices[0].delta || {}; // Only collect content field, ignore reasoning (minimax is a thinking model) const content = delta.content || ''; if (content) { fullContent += content; } } } catch (e) { // Skip malformed JSON chunks } } } // Extract HTML from markdown code blocks if present const htmlMatch = fullContent.match(/```html\n([\s\S]*?)\n```/); if (htmlMatch) { return htmlMatch[1]; } // Try to find raw HTML (look for complete HTML document) const docTypeMatch = fullContent.match(/()/i); if (docTypeMatch) { return docTypeMatch[1]; } // If no HTML found, throw error with preview of what we got const preview = fullContent.substring(0, 200); throw new Error(`AI did not generate valid HTML. Got: ${preview}...`); } finally { clearTimeout(timeout); } }