Spaces:
No application file
No application file
File size: 5,082 Bytes
9849557 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 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(/(<!doctype html[\s\S]*?<\/html>)/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);
}
}
|