sam12345324 commited on
Commit
9849557
·
verified ·
1 Parent(s): edfbf39

Upload server/services/opencode.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. server/services/opencode.js +143 -0
server/services/opencode.js ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fetch from 'node-fetch';
2
+
3
+ const MODELS = [
4
+ 'minimax-m2.5-free', // Only working free model as of 2026-05-04
5
+ ];
6
+
7
+ function sleep(ms) {
8
+ return new Promise(resolve => setTimeout(resolve, ms));
9
+ }
10
+
11
+ export async function generateHyperFramesCode(userPrompt, systemPrompt) {
12
+ let lastError = null;
13
+
14
+ // Try each model, with a retry on rate limit
15
+ for (const model of MODELS) {
16
+ for (let attempt = 0; attempt < 2; attempt++) {
17
+ try {
18
+ if (attempt > 0) {
19
+ console.log(`Retry ${attempt} for model: ${model} after rate limit delay...`);
20
+ await sleep(5000);
21
+ }
22
+ console.log(`Trying model: ${model} (attempt ${attempt + 1})`);
23
+ const result = await callModel(model, userPrompt, systemPrompt);
24
+ if (result && result.trim().length > 100) {
25
+ console.log(`Success with ${model} (${result.length} chars)`);
26
+ return result;
27
+ }
28
+ console.log(`Model ${model} returned insufficient output (${result?.length || 0} chars), trying next...`);
29
+ break;
30
+ } catch (err) {
31
+ console.error(`Model ${model} attempt ${attempt + 1} failed:`, err.message);
32
+ lastError = err;
33
+ if (err.message.includes('429') && attempt === 0) {
34
+ continue;
35
+ }
36
+ break;
37
+ }
38
+ }
39
+ }
40
+
41
+ throw lastError || new Error('All AI models failed to generate code');
42
+ }
43
+
44
+ async function callModel(model, userPrompt, systemPrompt) {
45
+ const controller = new AbortController();
46
+ const timeout = setTimeout(() => controller.abort(), 180000); // 3 min timeout
47
+
48
+ // Generate random user_id to bypass rate limits
49
+ const randomUserId = `user_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`;
50
+
51
+ try {
52
+ const response = await fetch('https://opencode.ai/zen/v1/chat/completions', {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Authorization': 'Bearer public',
57
+ 'x-opencode-client': 'desktop',
58
+ 'x-opencode-user-id': randomUserId,
59
+ 'Accept': 'text/event-stream',
60
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
61
+ },
62
+ body: JSON.stringify({
63
+ model,
64
+ messages: [
65
+ { role: 'system', content: 'You are a code generator. Output ONLY code, no explanations, no reasoning, no commentary. Start directly with the code block.' },
66
+ { 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.' },
67
+ { 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.' },
68
+ { role: 'system', content: systemPrompt },
69
+ { role: 'user', content: userPrompt }
70
+ ],
71
+ temperature: 0.7,
72
+ max_tokens: 8000,
73
+ stream: true
74
+ }),
75
+ signal: controller.signal
76
+ });
77
+
78
+ if (!response.ok) {
79
+ const errText = await response.text().catch(() => '');
80
+ throw new Error(`API returned ${response.status}: ${errText.slice(0, 200)}`);
81
+ }
82
+
83
+ let fullContent = '';
84
+ let buffer = '';
85
+
86
+ // Read response body as text stream
87
+ for await (const chunk of response.body) {
88
+ // Decode chunk as UTF-8
89
+ const text = chunk.toString('utf-8');
90
+ buffer += text;
91
+
92
+ // Process complete lines
93
+ const lines = buffer.split('\n');
94
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
95
+
96
+ for (const line of lines) {
97
+ if (!line.trim() || !line.startsWith('data:')) {
98
+ continue;
99
+ }
100
+
101
+ const data = line.slice(5).trim();
102
+ if (data === '[DONE]') {
103
+ break;
104
+ }
105
+
106
+ try {
107
+ const event = JSON.parse(data);
108
+ const choices = event.choices || [];
109
+
110
+ if (choices.length > 0) {
111
+ const delta = choices[0].delta || {};
112
+ // Only collect content field, ignore reasoning (minimax is a thinking model)
113
+ const content = delta.content || '';
114
+
115
+ if (content) {
116
+ fullContent += content;
117
+ }
118
+ }
119
+ } catch (e) {
120
+ // Skip malformed JSON chunks
121
+ }
122
+ }
123
+ }
124
+
125
+ // Extract HTML from markdown code blocks if present
126
+ const htmlMatch = fullContent.match(/```html\n([\s\S]*?)\n```/);
127
+ if (htmlMatch) {
128
+ return htmlMatch[1];
129
+ }
130
+
131
+ // Try to find raw HTML (look for complete HTML document)
132
+ const docTypeMatch = fullContent.match(/(<!doctype html[\s\S]*?<\/html>)/i);
133
+ if (docTypeMatch) {
134
+ return docTypeMatch[1];
135
+ }
136
+
137
+ // If no HTML found, throw error with preview of what we got
138
+ const preview = fullContent.substring(0, 200);
139
+ throw new Error(`AI did not generate valid HTML. Got: ${preview}...`);
140
+ } finally {
141
+ clearTimeout(timeout);
142
+ }
143
+ }