Emalawi19 commited on
Commit
2d13fdc
Β·
verified Β·
1 Parent(s): 5548e36

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +175 -257
server.js CHANGED
@@ -1,19 +1,57 @@
1
  import { pipeline } from '@huggingface/transformers';
2
  import http from 'http';
 
 
3
 
4
- const PORT = 7860;
5
  const MODEL_NAME = 'onnx-community/Qwen2.5-0.5B-Instruct';
6
  let generator;
7
  let modelReady = false;
8
 
9
- // ── QUEUE SYSTEM ──────────────────────────────────────────────────────────────
10
- const queue = []; // pending requests
11
- const MAX_PARALLEL = 2; // max concurrent generations
12
- let activeCount = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- function enqueue(task) {
 
 
 
 
 
 
 
 
 
 
 
15
  return new Promise((resolve, reject) => {
16
- queue.push({ task, resolve, reject });
17
  processQueue();
18
  });
19
  }
@@ -21,8 +59,16 @@ function enqueue(task) {
21
  async function processQueue() {
22
  if (activeCount >= MAX_PARALLEL || queue.length === 0) return;
23
  activeCount++;
24
- const { task, resolve, reject } = queue.shift();
 
 
 
 
 
 
 
25
  try {
 
26
  resolve(await task());
27
  } catch (err) {
28
  reject(err);
@@ -32,305 +78,184 @@ async function processQueue() {
32
  }
33
  }
34
 
35
- function getQueuePosition() {
36
- return queue.length;
37
- }
38
-
39
- // ── CORS ──────────────────────────────────────────────────────────────────────
40
- function setCORS(res) {
41
- res.setHeader('Access-Control-Allow-Origin', '*');
42
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
43
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
44
- }
45
-
46
- function sendJSON(res, status, data) {
47
- setCORS(res);
48
- res.setHeader('Content-Type', 'application/json');
49
- res.writeHead(status);
50
- res.end(JSON.stringify(data));
51
- }
52
-
53
- // ── MODEL ─────────────────────────────────────────────────────────────────────
54
  async function loadModel() {
55
- console.log("Loading model...");
56
- generator = await pipeline('text-generation', MODEL_NAME, { dtype: 'q4' });
57
  modelReady = true;
58
- console.log("Model ready!");
59
  }
60
 
61
- // ── SINGLE CHUNK GENERATION ───────────────────────────────────────────────────
62
- // Generates one chunk with retry on failure
63
  async function generateChunk(messages, maxTokens, attempt = 0) {
 
64
  try {
65
  const output = await generator(messages, {
66
- max_new_tokens: maxTokens,
67
- temperature: 0.3,
68
  repetition_penalty: 1.15,
69
- do_sample: false
70
  });
71
  const generated = output[0].generated_text;
72
- if (Array.isArray(generated)) return generated.at(-1)?.content || '';
73
- return String(generated || '');
 
 
 
 
 
74
  } catch (err) {
75
- if (attempt < 2) {
76
- console.log(`Chunk failed, retrying (attempt ${attempt + 1})...`);
77
- await new Promise(r => setTimeout(r, 1000));
78
  return generateChunk(messages, maxTokens, attempt + 1);
79
  }
80
  throw err;
81
  }
82
  }
83
 
84
- // ── SLIDING WINDOW STREAMING ──────────────────────────────────────────────────
85
- // Breaks generation into chunks, streams each chunk, auto-resumes with context
86
- async function slidingWindowStream(res, systemPrompt, userPrompt, totalTokens) {
87
- const CHUNK_TOKENS = 250; // tokens per chunk
88
- const CONTEXT_CHARS = 300; // chars of previous output to use as context
89
- const totalChunks = Math.ceil(totalTokens / CHUNK_TOKENS);
90
-
91
- let fullOutput = '';
92
- let previousContext = '';
 
 
 
 
 
 
 
 
 
 
93
 
94
- for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
95
  const isFirst = chunkIndex === 0;
96
- const isLast = chunkIndex === totalChunks - 1;
97
  const chunkNum = chunkIndex + 1;
98
 
99
- // Send chunk start event
100
- res.write(`data: ${JSON.stringify({
101
- type: 'chunk_start',
102
- chunk: chunkNum,
103
- totalChunks: totalChunks
104
- })}\n\n`);
105
-
106
- // Build messages for this chunk
107
- let chunkUserPrompt;
108
- if (isFirst) {
109
- chunkUserPrompt = userPrompt;
110
- } else {
111
- // Sliding window: include last N chars of previous output as context
112
- chunkUserPrompt =
113
- `Continue EXACTLY from where you left off. Do not repeat anything. ` +
114
- `Here is the end of what you wrote so far:\n\n` +
115
- `...${previousContext}\n\n` +
116
- `Continue from here. Original request was: ${userPrompt}`;
117
- }
118
 
119
  const messages = [
120
  { role: 'system', content: systemPrompt },
121
- { role: 'user', content: chunkUserPrompt }
122
  ];
123
 
124
- // Queue the chunk generation for fair processing
125
- let chunkText = '';
126
  try {
127
- chunkText = await enqueue(() => generateChunk(messages, CHUNK_TOKENS));
128
- } catch (err) {
129
- // Chunk failed after retries β€” send error event but continue
130
- res.write(`data: ${JSON.stringify({
131
- type: 'chunk_error',
132
- chunk: chunkNum,
133
- error: err.message
134
- })}\n\n`);
135
- continue;
136
- }
137
-
138
- if (!chunkText || chunkText.trim().length === 0) continue;
139
-
140
- // Stream the chunk word by word so user sees output immediately
141
- const words = chunkText.split(' ');
142
- for (let i = 0; i < words.length; i++) {
143
- const token = (i === 0 && !isFirst ? '' : i === 0 ? '' : ' ') + words[i];
144
- res.write(`data: ${JSON.stringify({ type: 'token', text: token })}\n\n`);
145
- // Small delay between words for smooth rendering
146
- await new Promise(r => setTimeout(r, 8));
147
- }
148
 
149
- // Update context window for next chunk (sliding window)
150
- fullOutput += (isFirst ? '' : ' ') + chunkText;
151
- previousContext = fullOutput.slice(-CONTEXT_CHARS);
 
 
 
 
 
152
 
153
- // Send chunk complete event
154
- res.write(`data: ${JSON.stringify({
155
- type: 'chunk_done',
156
- chunk: chunkNum,
157
- totalChunks: totalChunks
158
- })}\n\n`);
159
 
160
- // If model signals it's done early (short output), stop
161
- if (chunkText.trim().length < 50 && !isFirst) break;
 
 
 
162
  }
163
 
164
- // Send final done event with complete text
165
- res.write(`data: ${JSON.stringify({
166
- type: 'done',
167
- result: fullOutput.trim()
168
- })}\n\n`);
169
  res.end();
170
  }
171
 
172
- // ── DETECT IF LONG OUTPUT IS NEEDED ──────────────────────────────────────────
173
- function estimateTokensNeeded(prompt) {
174
- const lower = prompt.toLowerCase();
175
-
176
- // Explicit length requests
177
- const lineMatch = lower.match(/(\d+)\s*line/);
178
- if (lineMatch) return Math.min(parseInt(lineMatch[1]) * 6, 2000);
179
-
180
- const wordMatch = lower.match(/(\d+)\s*word/);
181
- if (wordMatch) return Math.min(parseInt(wordMatch[1]) * 2, 2000);
182
-
183
- // Long output keywords
184
- const longKeywords = [
185
- 'full website', 'complete website', 'entire website',
186
- 'full code', 'complete code', 'entire code',
187
- 'step by step', 'all steps', 'detailed guide',
188
- 'full backend', 'full frontend', 'complete app',
189
- 'build a website', 'create a website', 'generate a website',
190
- 'write a program', 'full script', 'complete script'
191
- ];
192
- if (longKeywords.some(k => lower.includes(k))) return 800;
193
-
194
- // Short answer keywords
195
- const shortKeywords = [
196
- 'what is', 'what are', 'who is', 'explain briefly',
197
- 'define', 'tell me about', 'describe', 'history of'
198
- ];
199
- if (shortKeywords.some(k => lower.includes(k))) return 200;
200
-
201
- // Default: medium
202
- return 400;
203
- }
204
-
205
- // ── SYSTEM PROMPT BUILDER ─────────────────────────────────────────────────────
206
- function buildSystemPrompt(isCoding) {
207
- if (isCoding) {
208
- return `You are Gini AI, a full-stack web development AI assistant created by Emalawi19, founded by Professor Roosevelt Chinkwende.
209
-
210
- IDENTITY: Your name is Gini AI. Company: Emalawi19. Founder: Professor Roosevelt Chinkwende.
211
-
212
- CODING RULES:
213
- 1. ALWAYS write code when asked. NEVER refuse.
214
- 2. Complete, real, copy-paste ready code only β€” no placeholders.
215
- 3. Default: JavaScript/HTML/CSS. Never Python unless asked.
216
- 4. Add comments inside the code.
217
- 5. When continuing a previous chunk, do NOT repeat already-written code. Continue EXACTLY from where you left off.
218
-
219
- HTML RULES:
220
- 1. Start with <!DOCTYPE html> then <html lang="en">
221
- 2. Include proper <head> with meta tags, title, and <style>
222
- 3. End with </body> then </html>
223
- 4. All CSS in <style>, all JS in <script> at bottom.`;
224
- }
225
- return `You are Gini AI, a helpful AI assistant created by Emalawi19, founded by Professor Roosevelt Chinkwende.
226
-
227
- IDENTITY: Your name is Gini AI. Company: Emalawi19. Founder: Professor Roosevelt Chinkwende.
228
-
229
- RULES:
230
- - Answer questions clearly and helpfully.
231
- - Be concise for simple questions, detailed for complex ones.
232
- - When continuing a response, do NOT repeat what was already said. Continue EXACTLY from where you left off.`;
233
- }
234
-
235
- function isCodingRequest(prompt) {
236
- const lower = prompt.toLowerCase();
237
- const nonCoding = ['what is','what are','who is','explain','describe','history','meaning','function of','functions of','tell me about'];
238
- if (nonCoding.some(k => lower.includes(k))) return false;
239
- const coding = ['generate','create','build','make','code','write','website','login','register','form','html','css','javascript','backend','frontend','server','api','function','script'];
240
- return coding.some(k => lower.includes(k));
241
- }
242
-
243
  // ── HTTP SERVER ───────────────────────────────────────────────────────────────
244
  const server = http.createServer(async (req, res) => {
245
  setCORS(res);
 
246
 
247
- if (req.method === 'OPTIONS') {
248
- res.writeHead(200);
249
- return res.end('{}');
250
- }
251
 
252
- const pathname = req.url.split('?')[0];
 
253
 
254
- // Status
255
  if (pathname === '/' && req.method === 'GET') {
256
- return sendJSON(res, 200, {
257
- status: modelReady ? "ready" : "loading",
258
- model: MODEL_NAME,
259
- queue_length: queue.length,
260
- active: activeCount
261
- });
262
  }
263
 
264
- // Queue status
265
- if (pathname === '/queue' && req.method === 'GET') {
266
- const position = getQueuePosition();
267
- const eta = position * 20; // rough seconds per request
268
- return sendJSON(res, 200, { position, eta_seconds: eta, active: activeCount });
 
 
 
 
 
 
 
 
 
269
  }
270
 
271
- // Generate
272
  if (pathname === '/generate' && req.method === 'POST') {
273
  let body = '';
274
  req.on('data', c => { body += c.toString(); });
275
  req.on('end', async () => {
276
-
277
  let parsed;
278
- try { parsed = JSON.parse(body); }
279
- catch { return sendJSON(res, 400, { error: "Invalid JSON" }); }
280
-
281
- const { prompt } = parsed;
282
- if (!prompt || !prompt.trim()) return sendJSON(res, 400, { error: "prompt is required" });
283
- if (!modelReady) return sendJSON(res, 503, { error: "Model loading, please wait..." });
284
-
285
- const coding = isCodingRequest(prompt);
286
- const totalTokens = estimateTokensNeeded(prompt);
287
- const systemPrompt = buildSystemPrompt(coding);
288
-
289
- console.log(`Request: "${prompt.slice(0, 60)}" | tokens: ${totalTokens} | coding: ${coding} | queue: ${queue.length}`);
290
-
291
- // If request is in queue, send queue position first
292
- if (queue.length > 0) {
293
- const pos = queue.length;
294
- const eta = pos * 20;
295
- // For queued requests we still use SSE so user sees queue position
296
- res.setHeader('Content-Type', 'text/event-stream');
297
- res.setHeader('Cache-Control', 'no-cache');
298
- res.setHeader('Connection', 'keep-alive');
299
- res.setHeader('X-Accel-Buffering', 'no');
300
- res.writeHead(200);
301
- res.write(`data: ${JSON.stringify({ type: 'queued', position: pos, eta_seconds: eta })}\n\n`);
302
- }
303
 
304
- // Short requests: single chunk, plain JSON response
305
- if (totalTokens <= 300 && queue.length === 0) {
306
- try {
307
- const messages = [
308
- { role: 'system', content: systemPrompt },
309
- { role: 'user', content: prompt.trim() }
310
- ];
311
- const result = await enqueue(() => generateChunk(messages, totalTokens));
312
- return sendJSON(res, 200, { result: result || "I couldn't generate a response. Please try again." });
313
- } catch (err) {
314
- return sendJSON(res, 500, { error: err.message });
315
- }
316
- }
317
 
318
- // Long requests: SSE streaming with sliding window
319
- if (!res.headersSent) {
320
- res.setHeader('Content-Type', 'text/event-stream');
321
- res.setHeader('Cache-Control', 'no-cache');
322
- res.setHeader('Connection', 'keep-alive');
323
- res.setHeader('X-Accel-Buffering', 'no');
324
- res.writeHead(200);
325
  }
326
 
327
- // Send queue position if waiting
328
- res.write(`data: ${JSON.stringify({ type: 'start', totalTokens, queue: queue.length })}\n\n`);
 
 
 
 
 
 
 
329
 
330
  try {
331
- await slidingWindowStream(res, systemPrompt, prompt.trim(), totalTokens);
332
  } catch (err) {
333
- res.write(`data: ${JSON.stringify({ type: 'error', error: err.message })}\n\n`);
334
  res.end();
335
  }
336
  });
@@ -341,12 +266,5 @@ const server = http.createServer(async (req, res) => {
341
  });
342
 
343
  loadModel().then(() => {
344
- server.listen(PORT, '0.0.0.0', () => {
345
- console.log(`Gini AI backend on port ${PORT}`);
346
- });
347
- }).catch(err => {
348
- console.error("Model load failed:", err.message);
349
- server.listen(PORT, '0.0.0.0', () => {
350
- console.log(`Server started (model failed)`);
351
- });
352
  });
 
1
  import { pipeline } from '@huggingface/transformers';
2
  import http from 'http';
3
+ import crypto from 'crypto';
4
+ import fs from 'fs';
5
 
6
+ const PORT = 7860;
7
  const MODEL_NAME = 'onnx-community/Qwen2.5-0.5B-Instruct';
8
  let generator;
9
  let modelReady = false;
10
 
11
+ // ── SYSTEM STATE & MEMORY ─────────────────────────────────────────────────────
12
+ const queue = [];
13
+ const jobs = new Map(); // Stores job state, checkpoints, and cancellation flags
14
+ const rateLimits = new Map(); // IP-based rate limiting
15
+ const MAX_PARALLEL = 1; // Keep at 1 for transformers.js to prevent thread blocking
16
+ let activeCount = 0;
17
+
18
+ // ── UTILITIES ─────────────────────────────────────────────────────────────────
19
+ function generateId() { return crypto.randomBytes(8).toString('hex'); }
20
+
21
+ function log(event, details = {}) {
22
+ const timestamp = new Date().toISOString();
23
+ const logEntry = `[${timestamp}] ${event.toUpperCase()} - ${JSON.stringify(details)}\n`;
24
+ process.stdout.write(logEntry);
25
+ fs.appendFileSync('generation_logs.txt', logEntry); // Log to file
26
+ }
27
+
28
+ function setCORS(res) {
29
+ res.setHeader('Access-Control-Allow-Origin', '*');
30
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
31
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
32
+ }
33
+
34
+ function sendJSON(res, status, data) {
35
+ setCORS(res);
36
+ res.setHeader('Content-Type', 'application/json');
37
+ res.writeHead(status);
38
+ res.end(JSON.stringify(data));
39
+ }
40
 
41
+ // ── QUEUE & RATE LIMITING ─────────────────────────────────────────────────────
42
+ function checkRateLimit(ip) {
43
+ const now = Date.now();
44
+ const limit = rateLimits.get(ip) || { count: 0, resetTime: now + 60000 };
45
+ if (now > limit.resetTime) { limit.count = 0; limit.resetTime = now + 60000; }
46
+ if (limit.count >= 10) return false; // Max 10 requests per minute
47
+ limit.count++;
48
+ rateLimits.set(ip, limit);
49
+ return true;
50
+ }
51
+
52
+ function enqueue(jobId, task) {
53
  return new Promise((resolve, reject) => {
54
+ queue.push({ jobId, task, resolve, reject });
55
  processQueue();
56
  });
57
  }
 
59
  async function processQueue() {
60
  if (activeCount >= MAX_PARALLEL || queue.length === 0) return;
61
  activeCount++;
62
+ const { jobId, task, resolve, reject } = queue.shift();
63
+
64
+ // Check if job was cancelled while in queue
65
+ if (jobs.get(jobId)?.status === 'cancelled') {
66
+ activeCount--;
67
+ return processQueue();
68
+ }
69
+
70
  try {
71
+ jobs.get(jobId).status = 'processing';
72
  resolve(await task());
73
  } catch (err) {
74
  reject(err);
 
78
  }
79
  }
80
 
81
+ // ── MODEL INITIALIZATION ──────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  async function loadModel() {
83
+ log('system', { message: "Loading model..." });
84
+ generator = await pipeline('text-generation', MODEL_NAME, { dtype: 'q4' });
85
  modelReady = true;
86
+ log('system', { message: "Model ready!" });
87
  }
88
 
89
+ // ── GENERATION & VALIDATION ───────────────────────────────────────────────────
 
90
  async function generateChunk(messages, maxTokens, attempt = 0) {
91
+ const startTime = Date.now();
92
  try {
93
  const output = await generator(messages, {
94
+ max_new_tokens: maxTokens,
95
+ temperature: 0.3,
96
  repetition_penalty: 1.15,
97
+ do_sample: false
98
  });
99
  const generated = output[0].generated_text;
100
+ let text = Array.isArray(generated) ? generated.at(-1)?.content || '' : String(generated || '');
101
+
102
+ // Output Validation: Detect truncated code blocks
103
+ const openBlocks = (text.match(/```/g) || []).length;
104
+ if (openBlocks % 2 !== 0) text += '\n```'; // Repair formatting
105
+
106
+ return { text, duration: Date.now() - startTime };
107
  } catch (err) {
108
+ if (attempt < 3) { // Failure Recovery: Exponential backoff
109
+ log('retry', { attempt: attempt + 1, error: err.message });
110
+ await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
111
  return generateChunk(messages, maxTokens, attempt + 1);
112
  }
113
  throw err;
114
  }
115
  }
116
 
117
+ // ── SLIDING WINDOW ENGINE (AUTO-RESUME & CHUNKING) ────────────────────────────
118
+ async function slidingWindowEngine(res, jobId, systemPrompt, userPrompt, totalTokens) {
119
+ const CHUNK_TOKENS = 250;
120
+ const CONTEXT_CHARS = 800; // Expanded for better Context Awareness
121
+ const totalChunks = Math.ceil(totalTokens / CHUNK_TOKENS);
122
+ const job = jobs.get(jobId);
123
+
124
+ let fullOutput = job.checkpoint || '';
125
+ let previousContext = fullOutput.slice(-CONTEXT_CHARS);
126
+ let chunksCompleted = job.chunksCompleted || 0;
127
+ let tokensGenerated = 0;
128
+ const globalStartTime = Date.now();
129
+
130
+ for (let chunkIndex = chunksCompleted; chunkIndex < totalChunks; chunkIndex++) {
131
+ // Cancellation Check
132
+ if (job.status === 'cancelled') {
133
+ res.write(`data: ${JSON.stringify({ type: 'cancelled', message: 'Job stopped by user.' })}\n\n`);
134
+ return res.end();
135
+ }
136
 
 
137
  const isFirst = chunkIndex === 0;
 
138
  const chunkNum = chunkIndex + 1;
139
 
140
+ let chunkUserPrompt = isFirst ? userPrompt :
141
+ `[SYSTEM: Intelligent Context Compression]\n` +
142
+ `Original Request: ${userPrompt}\n` +
143
+ `Recent Output Context (Maintain consistency, variables, and formatting):\n...${previousContext}\n\n` +
144
+ `INSTRUCTION: Continue generating seamlessly from the exact last character above. Do not repeat the context.`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  const messages = [
147
  { role: 'system', content: systemPrompt },
148
+ { role: 'user', content: chunkUserPrompt }
149
  ];
150
 
 
 
151
  try {
152
+ const { text: chunkText, duration } = await enqueue(jobId, () => generateChunk(messages, CHUNK_TOKENS));
153
+
154
+ if (!chunkText || chunkText.trim().length === 0) continue;
155
+
156
+ const words = chunkText.split(' ');
157
+ tokensGenerated += words.length; // Rough estimation
158
+
159
+ // Progress Tracking Metrics
160
+ const speed = (words.length / (duration / 1000)).toFixed(2); // words per second
161
+ const percentComplete = Math.round((chunkNum / totalChunks) * 100);
162
+ const eta = ((totalChunks - chunkNum) * (duration / 1000)).toFixed(0);
163
+
164
+ // Stream Output
165
+ for (let i = 0; i < words.length; i++) {
166
+ const token = (i === 0 && !isFirst ? '' : i === 0 ? '' : ' ') + words[i];
167
+ res.write(`data: ${JSON.stringify({
168
+ type: 'token', text: token, speed: `${speed} w/s`, eta: `${eta}s`, progress: `${percentComplete}%`
169
+ })}\n\n`);
170
+ await new Promise(r => setTimeout(r, 10)); // Streaming effect
171
+ }
 
172
 
173
+ // Auto Resume & Checkpoint System Update
174
+ fullOutput += (isFirst ? '' : ' ') + chunkText;
175
+ previousContext = fullOutput.slice(-CONTEXT_CHARS);
176
+
177
+ job.checkpoint = fullOutput;
178
+ job.chunksCompleted = chunkNum;
179
+ jobs.set(jobId, job);
180
+ log('checkpoint_saved', { jobId, chunkNum });
181
 
182
+ // Stop condition
183
+ if (chunkText.trim().length < 50 && !isFirst) break;
 
 
 
 
184
 
185
+ } catch (err) {
186
+ log('error', { jobId, error: err.message });
187
+ res.write(`data: ${JSON.stringify({ type: 'error', error: 'Chunk failed. Checkpoint saved for recovery.' })}\n\n`);
188
+ return res.end();
189
+ }
190
  }
191
 
192
+ job.status = 'completed';
193
+ res.write(`data: ${JSON.stringify({ type: 'done', result: fullOutput.trim(), totalTokens: tokensGenerated })}\n\n`);
 
 
 
194
  res.end();
195
  }
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  // ── HTTP SERVER ───────────────────────────────────────────────────────────────
198
  const server = http.createServer(async (req, res) => {
199
  setCORS(res);
200
+ if (req.method === 'OPTIONS') { res.writeHead(200); return res.end('{}'); }
201
 
202
+ const url = new URL(req.url, `http://${req.headers.host}`);
203
+ const pathname = url.pathname;
204
+ const clientIp = req.socket.remoteAddress;
 
205
 
206
+ // Rate Limiting
207
+ if (!checkRateLimit(clientIp)) return sendJSON(res, 429, { error: "Too many requests. Please wait." });
208
 
 
209
  if (pathname === '/' && req.method === 'GET') {
210
+ return sendJSON(res, 200, { status: modelReady ? "ready" : "loading", queue: queue.length });
 
 
 
 
 
211
  }
212
 
213
+ // Cancellation Endpoint
214
+ if (pathname === '/cancel' && req.method === 'POST') {
215
+ let body = '';
216
+ req.on('data', c => { body += c.toString(); });
217
+ req.on('end', () => {
218
+ const { jobId } = JSON.parse(body);
219
+ if (jobs.has(jobId)) {
220
+ jobs.get(jobId).status = 'cancelled';
221
+ log('cancelled', { jobId });
222
+ return sendJSON(res, 200, { message: `Job ${jobId} cancelled.` });
223
+ }
224
+ return sendJSON(res, 404, { error: "Job not found." });
225
+ });
226
+ return;
227
  }
228
 
 
229
  if (pathname === '/generate' && req.method === 'POST') {
230
  let body = '';
231
  req.on('data', c => { body += c.toString(); });
232
  req.on('end', async () => {
 
233
  let parsed;
234
+ try { parsed = JSON.parse(body); } catch { return sendJSON(res, 400, { error: "Invalid JSON" }); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
+ const { prompt, resumeJobId } = parsed;
237
+ if (!modelReady) return sendJSON(res, 503, { error: "Model loading..." });
 
 
 
 
 
 
 
 
 
 
 
238
 
239
+ // Initialize or Resume Job
240
+ const jobId = resumeJobId && jobs.has(resumeJobId) ? resumeJobId : generateId();
241
+ if (!jobs.has(jobId)) {
242
+ jobs.set(jobId, { status: 'queued', checkpoint: '', chunksCompleted: 0 });
 
 
 
243
  }
244
 
245
+ const totalTokens = 2000; // Simulated dynamic estimate
246
+ log('job_started', { jobId, promptLength: prompt?.length });
247
+
248
+ res.setHeader('Content-Type', 'text/event-stream');
249
+ res.setHeader('Cache-Control', 'no-cache');
250
+ res.setHeader('Connection', 'keep-alive');
251
+ res.writeHead(200);
252
+
253
+ res.write(`data: ${JSON.stringify({ type: 'start', jobId, queuePosition: queue.length })}\n\n`);
254
 
255
  try {
256
+ await slidingWindowEngine(res, jobId, "You are a helpful coding assistant.", prompt || "Continue", totalTokens);
257
  } catch (err) {
258
+ res.write(`data: ${JSON.stringify({ type: 'fatal_error', error: err.message })}\n\n`);
259
  res.end();
260
  }
261
  });
 
266
  });
267
 
268
  loadModel().then(() => {
269
+ server.listen(PORT, '0.0.0.0', () => console.log(`Engine running on port ${PORT}`));
 
 
 
 
 
 
 
270
  });