victor HF Staff commited on
Commit
efeb0ba
·
1 Parent(s): b333926

refactor: Standardize quotes and reduce AI max_tokens

Browse files
Files changed (1) hide show
  1. server.js +217 -162
server.js CHANGED
@@ -6,7 +6,7 @@ import cookieParser from "cookie-parser";
6
  import { createRepo, uploadFiles, whoAmI } from "@huggingface/hub";
7
  import { InferenceClient } from "@huggingface/inference";
8
  import bodyParser from "body-parser";
9
- import { diff_match_patch } from 'diff-match-patch'; // Using a library for robustness
10
 
11
  import checkUser from "./middlewares/checkUser.js";
12
 
@@ -27,7 +27,7 @@ const MODEL_ID = "deepseek-ai/DeepSeek-V3-0324";
27
  const MAX_REQUESTS_PER_IP = 4; // Increased limit for testing diffs
28
 
29
  app.use(cookieParser());
30
- app.use(bodyParser.json({ limit: '10mb' })); // Increase limit if HTML gets large
31
  app.use(express.static(path.join(__dirname, "dist")));
32
 
33
  app.get("/api/login", (_req, res) => {
@@ -174,7 +174,6 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
174
  }
175
  });
176
 
177
-
178
  // --- Diff Parsing and Applying Logic ---
179
 
180
  const SEARCH_START = "<<<<<<< SEARCH";
@@ -187,52 +186,57 @@ const REPLACE_END = ">>>>>>> REPLACE";
187
  * @returns {Array<{original: string, updated: string}>} - Array of diff blocks.
188
  */
189
  function parseDiffBlocks(content) {
190
- const blocks = [];
191
- const lines = content.split('\n');
192
- let i = 0;
193
- while (i < lines.length) {
194
- // Trim lines for comparison to handle potential trailing whitespace from AI
195
- if (lines[i].trim() === SEARCH_START) {
196
- const originalLines = [];
197
- const updatedLines = [];
198
- i++; // Move past SEARCH_START
199
- while (i < lines.length && lines[i].trim() !== DIVIDER) {
200
- originalLines.push(lines[i]);
201
- i++;
202
- }
203
- if (i >= lines.length || lines[i].trim() !== DIVIDER) {
204
- console.warn("Malformed diff block: Missing or misplaced '=======' after SEARCH block. Block content:", originalLines.join('\n'));
205
- // Skip to next potential block start or end
206
- while (i < lines.length && !lines[i].includes(SEARCH_START)) i++;
207
- continue;
208
- }
209
- i++; // Move past DIVIDER
210
- while (i < lines.length && lines[i].trim() !== REPLACE_END) {
211
- updatedLines.push(lines[i]);
212
- i++;
213
- }
214
- if (i >= lines.length || lines[i].trim() !== REPLACE_END) {
215
- console.warn("Malformed diff block: Missing or misplaced '>>>>>>> REPLACE' after REPLACE block. Block content:", updatedLines.join('\n'));
216
- // Skip to next potential block start or end
217
- while (i < lines.length && !lines[i].includes(SEARCH_START)) i++;
218
- continue;
219
- }
220
- // Important: Re-add newline characters lost during split('\n')
221
- // Only add trailing newline if it wasn't the *very last* line of the block content before split
222
- const originalText = originalLines.join('\n');
223
- const updatedText = updatedLines.join('\n');
224
-
225
- blocks.push({
226
- original: originalText, // Don't add trailing newline here, handle in apply
227
- updated: updatedText
228
- });
229
- }
230
  i++;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  }
232
- return blocks;
 
 
233
  }
234
 
235
-
236
  /**
237
  * Applies a single diff block to the current HTML content using diff-match-patch.
238
  * @param {string} currentHtml - The current HTML content.
@@ -241,55 +245,67 @@ function parseDiffBlocks(content) {
241
  * @returns {string | null} - The updated HTML content, or null if patching failed.
242
  */
243
  function applySingleDiffFuzzy(currentHtml, originalBlock, updatedBlock) {
244
- const dmp = new diff_match_patch();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
- // Handle potential trailing newline inconsistencies between AI and actual file
247
- // If originalBlock doesn't end with newline but exists in currentHtml *with* one, add it.
248
- let searchBlock = originalBlock;
249
- if (!originalBlock.endsWith('\n') && currentHtml.includes(originalBlock + '\n')) {
250
- searchBlock = originalBlock + '\n';
251
- }
252
- // If updatedBlock is meant to replace a block ending in newline, ensure it also does (unless empty)
253
- let replaceBlock = updatedBlock;
254
- if (searchBlock.endsWith('\n') && updatedBlock.length > 0 && !updatedBlock.endsWith('\n')) {
255
- replaceBlock = updatedBlock + '\n';
256
- }
257
- // If deleting a block ending in newline, the replacement is empty
258
- if (searchBlock.endsWith('\n') && updatedBlock.length === 0) {
259
- replaceBlock = "";
260
- }
261
-
262
-
263
- // 1. Create a patch from the (potentially adjusted) original and updated blocks
264
- const patchText = dmp.patch_make(searchBlock, replaceBlock);
265
-
266
- // 2. Apply the patch to the current HTML
267
- // diff-match-patch is good at finding the location even with slight context variations.
268
- // Increase Match_Threshold for potentially larger files or more significant context drift.
269
- dmp.Match_Threshold = 0.6; // Adjust as needed (0.0 to 1.0)
270
- dmp.Patch_DeleteThreshold = 0.6; // Adjust as needed
271
- const [patchedHtml, results] = dmp.patch_apply(patchText, currentHtml);
272
-
273
- // 3. Check if the patch applied successfully
274
- if (results.every(result => result === true)) {
275
- return patchedHtml;
276
- } else {
277
- console.warn("Patch application failed using diff-match-patch. Results:", results);
278
- // Fallback: Try exact string replacement (less robust)
279
- if (currentHtml.includes(searchBlock)) {
280
- console.log("Falling back to direct string replacement.");
281
- // Use replace only once
282
- const index = currentHtml.indexOf(searchBlock);
283
- if (index !== -1) {
284
- return currentHtml.substring(0, index) + replaceBlock + currentHtml.substring(index + searchBlock.length);
285
- }
286
- }
287
- console.error("Direct string replacement fallback also failed.");
288
- return null; // Indicate failure
289
  }
 
 
 
290
  }
291
 
292
-
293
  /**
294
  * Applies all parsed diff blocks sequentially to the original HTML.
295
  * @param {string} originalHtml - The initial HTML content.
@@ -298,69 +314,94 @@ function applySingleDiffFuzzy(currentHtml, originalBlock, updatedBlock) {
298
  * @throws {Error} If any diff block fails to apply.
299
  */
300
  function applyDiffs(originalHtml, aiResponseContent) {
301
- const diffBlocks = parseDiffBlocks(aiResponseContent);
302
-
303
- if (diffBlocks.length === 0) {
304
- console.warn("AI response did not contain valid SEARCH/REPLACE blocks.");
305
- // Check if the AI *tried* to use the format but failed, or just gave full code
306
- if (aiResponseContent.includes(SEARCH_START) || aiResponseContent.includes(DIVIDER) || aiResponseContent.includes(REPLACE_END)) {
307
- throw new Error("AI response contained malformed or unparseable diff blocks. Could not apply changes.");
308
- }
309
- // If no diff blocks *at all*, maybe the AI ignored the instruction and gave full code?
310
- // Heuristic: If the response looks like a full HTML doc, use it directly.
311
- const trimmedResponse = aiResponseContent.trim().toLowerCase();
312
- if (trimmedResponse.startsWith('<!doctype html') || trimmedResponse.startsWith('<html')) {
313
- console.warn("[Diff Apply] AI response seems to be full HTML despite diff instructions. Using full response as fallback.");
314
- return aiResponseContent;
315
- }
316
- console.warn("[Diff Apply] No valid diff blocks found and response doesn't look like full HTML. Returning original HTML.");
317
- return originalHtml; // Return original if no diffs and not full HTML
 
 
 
 
 
 
 
 
318
  }
 
 
 
 
 
319
 
320
- console.log(`Found ${diffBlocks.length} diff blocks to apply.`);
321
- let currentHtml = originalHtml;
322
- for (let i = 0; i < diffBlocks.length; i++) {
323
- const { original, updated } = diffBlocks[i];
324
- console.log(`Applying block ${i + 1}...`);
325
- const result = applySingleDiffFuzzy(currentHtml, original, updated);
326
-
327
- if (result === null) {
328
- // Log detailed error for debugging
329
- console.error(`Failed to apply diff block ${i + 1}:`);
330
- console.error("--- SEARCH ---");
331
- console.error(original);
332
- console.error("--- REPLACE ---");
333
- console.error(updated);
334
- console.error("--- CURRENT CONTEXT (approx) ---");
335
- // Try finding the first line of the original block for context
336
- const firstLine = original.split('\n')[0];
337
- let contextIndex = -1;
338
- if (firstLine) {
339
- contextIndex = currentHtml.indexOf(firstLine);
340
- }
341
- if (contextIndex === -1) { // If first line not found, maybe try middle line?
342
- const lines = original.split('\n');
343
- if (lines.length > 2) {
344
- contextIndex = currentHtml.indexOf(lines[Math.floor(lines.length / 2)]);
345
- }
346
- }
347
- if (contextIndex === -1) { // Still not found, just show start
348
- contextIndex = 0;
349
- }
350
-
351
- console.error(currentHtml.substring(Math.max(0, contextIndex - 150), Math.min(currentHtml.length, contextIndex + original.length + 300)));
352
- console.error("---------------------------------");
353
-
354
- throw new Error(`Failed to apply AI-suggested change ${i + 1}. The 'SEARCH' block might not accurately match the current code.`);
355
  }
356
- currentHtml = result;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  }
 
 
358
 
359
- console.log("All diff blocks applied successfully.");
360
- return currentHtml;
361
  }
362
 
363
-
364
  // --- AI Interaction Route ---
365
  app.post("/api/ask-ai", async (req, res) => {
366
  const { prompt, html, previousPrompt } = req.body;
@@ -372,7 +413,7 @@ app.post("/api/ask-ai", async (req, res) => {
372
  }
373
 
374
  const isFollowUp = !!html && !!previousPrompt; // Check if it's a follow-up request
375
- console.log(`[AI Request] Type: ${isFollowUp ? 'Follow-up' : 'Initial'}`);
376
 
377
  const { hf_token } = req.cookies;
378
  let token = hf_token;
@@ -390,7 +431,8 @@ app.post("/api/ask-ai", async (req, res) => {
390
  return res.status(429).send({
391
  ok: false,
392
  openLogin: true,
393
- message: "Log In to continue using the service (Rate limit exceeded for anonymous users)",
 
394
  });
395
  }
396
  token = process.env.DEFAULT_HF_TOKEN;
@@ -445,8 +487,14 @@ ${REPLACE_END}
445
  ONLY output the changes in this format. Do NOT output the full HTML file again.`;
446
 
447
  // --- Prepare Messages for AI ---
448
- const systemPromptContent = isFollowUp ? followUpSystemPrompt : initialSystemPrompt;
449
- console.log(`[AI Request] Using system prompt: ${isFollowUp ? 'Follow-up (Diff)' : 'Initial (Full HTML)'}`);
 
 
 
 
 
 
450
  // console.log("[AI Request] System Prompt Content:\n", systemPromptContent); // Uncomment for full prompt text
451
 
452
  const messages = [
@@ -457,12 +505,18 @@ ONLY output the changes in this format. Do NOT output the full HTML file again.`
457
  // Include previous context if available
458
  ...(previousPrompt ? [{ role: "user", content: previousPrompt }] : []),
459
  // Provide current code clearly ONLY if it's a follow-up
460
- ...(isFollowUp && html ? [{ role: "assistant", content: `Okay, I have the current code. It is:\n\`\`\`html\n${html}\n\`\`\`` }] : []),
 
 
 
 
 
 
 
461
  // Current user prompt
462
  { role: "user", content: prompt },
463
  ];
464
 
465
-
466
  const client = new InferenceClient(token);
467
  let completeResponse = "";
468
 
@@ -472,13 +526,15 @@ ONLY output the changes in this format. Do NOT output the full HTML file again.`
472
  res.setHeader("Cache-Control", "no-cache");
473
  res.setHeader("Connection", "keep-alive");
474
  res.setHeader("X-Response-Type", isFollowUp ? "diff" : "full"); // Signal type to client
475
- console.log(`[AI Request] Set X-Response-Type: ${isFollowUp ? 'diff' : 'full'}`);
 
 
476
 
477
  const chatCompletion = client.chatCompletionStream({
478
  model: MODEL_ID,
479
  provider: isFollowUp ? "fireworks-ai" : "sambanova", // Use sambanova for initial, fireworks for follow-up
480
  messages: messages,
481
- max_tokens: 12_000, // Keep max_tokens reasonable
482
  temperature: isFollowUp ? 0 : undefined, // Set temperature to 0 for follow-ups, otherwise use default
483
  });
484
 
@@ -499,17 +555,16 @@ ONLY output the changes in this format. Do NOT output the full HTML file again.`
499
  // console.log("--------------------------");
500
 
501
  res.end(); // End the stream
502
-
503
  } catch (error) {
504
  console.error("Error during AI interaction:", error); // Removed "or diff application"
505
  // If we haven't sent headers/started streaming yet
506
  if (!res.headersSent) {
507
- // Check if it's an AbortError which might happen if the client disconnects
508
- if (error.name === 'AbortError') {
509
- console.warn('Client disconnected before AI response finished.');
510
- // Don't send another response if client is gone
511
- return;
512
- }
513
  res.status(500).send({
514
  ok: false,
515
  // Provide a more user-friendly message, but keep details for logs
@@ -521,7 +576,7 @@ ONLY output the changes in this format. Do NOT output the full HTML file again.`
521
  console.error("Error occurred mid-stream. Ending response.");
522
  res.end(); // End the stream abruptly if error occurs during streaming
523
  }
524
- // If streaming failed *after* res.end() was called (unlikely but possible), do nothing more.
525
  }
526
  });
527
 
 
6
  import { createRepo, uploadFiles, whoAmI } from "@huggingface/hub";
7
  import { InferenceClient } from "@huggingface/inference";
8
  import bodyParser from "body-parser";
9
+ import { diff_match_patch } from "diff-match-patch"; // Using a library for robustness
10
 
11
  import checkUser from "./middlewares/checkUser.js";
12
 
 
27
  const MAX_REQUESTS_PER_IP = 4; // Increased limit for testing diffs
28
 
29
  app.use(cookieParser());
30
+ app.use(bodyParser.json({ limit: "10mb" })); // Increase limit if HTML gets large
31
  app.use(express.static(path.join(__dirname, "dist")));
32
 
33
  app.get("/api/login", (_req, res) => {
 
174
  }
175
  });
176
 
 
177
  // --- Diff Parsing and Applying Logic ---
178
 
179
  const SEARCH_START = "<<<<<<< SEARCH";
 
186
  * @returns {Array<{original: string, updated: string}>} - Array of diff blocks.
187
  */
188
  function parseDiffBlocks(content) {
189
+ const blocks = [];
190
+ const lines = content.split("\n");
191
+ let i = 0;
192
+ while (i < lines.length) {
193
+ // Trim lines for comparison to handle potential trailing whitespace from AI
194
+ if (lines[i].trim() === SEARCH_START) {
195
+ const originalLines = [];
196
+ const updatedLines = [];
197
+ i++; // Move past SEARCH_START
198
+ while (i < lines.length && lines[i].trim() !== DIVIDER) {
199
+ originalLines.push(lines[i]);
200
+ i++;
201
+ }
202
+ if (i >= lines.length || lines[i].trim() !== DIVIDER) {
203
+ console.warn(
204
+ "Malformed diff block: Missing or misplaced '=======' after SEARCH block. Block content:",
205
+ originalLines.join("\n")
206
+ );
207
+ // Skip to next potential block start or end
208
+ while (i < lines.length && !lines[i].includes(SEARCH_START)) i++;
209
+ continue;
210
+ }
211
+ i++; // Move past DIVIDER
212
+ while (i < lines.length && lines[i].trim() !== REPLACE_END) {
213
+ updatedLines.push(lines[i]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  i++;
215
+ }
216
+ if (i >= lines.length || lines[i].trim() !== REPLACE_END) {
217
+ console.warn(
218
+ "Malformed diff block: Missing or misplaced '>>>>>>> REPLACE' after REPLACE block. Block content:",
219
+ updatedLines.join("\n")
220
+ );
221
+ // Skip to next potential block start or end
222
+ while (i < lines.length && !lines[i].includes(SEARCH_START)) i++;
223
+ continue;
224
+ }
225
+ // Important: Re-add newline characters lost during split('\n')
226
+ // Only add trailing newline if it wasn't the *very last* line of the block content before split
227
+ const originalText = originalLines.join("\n");
228
+ const updatedText = updatedLines.join("\n");
229
+
230
+ blocks.push({
231
+ original: originalText, // Don't add trailing newline here, handle in apply
232
+ updated: updatedText,
233
+ });
234
  }
235
+ i++;
236
+ }
237
+ return blocks;
238
  }
239
 
 
240
  /**
241
  * Applies a single diff block to the current HTML content using diff-match-patch.
242
  * @param {string} currentHtml - The current HTML content.
 
245
  * @returns {string | null} - The updated HTML content, or null if patching failed.
246
  */
247
  function applySingleDiffFuzzy(currentHtml, originalBlock, updatedBlock) {
248
+ const dmp = new diff_match_patch();
249
+
250
+ // Handle potential trailing newline inconsistencies between AI and actual file
251
+ // If originalBlock doesn't end with newline but exists in currentHtml *with* one, add it.
252
+ let searchBlock = originalBlock;
253
+ if (
254
+ !originalBlock.endsWith("\n") &&
255
+ currentHtml.includes(originalBlock + "\n")
256
+ ) {
257
+ searchBlock = originalBlock + "\n";
258
+ }
259
+ // If updatedBlock is meant to replace a block ending in newline, ensure it also does (unless empty)
260
+ let replaceBlock = updatedBlock;
261
+ if (
262
+ searchBlock.endsWith("\n") &&
263
+ updatedBlock.length > 0 &&
264
+ !updatedBlock.endsWith("\n")
265
+ ) {
266
+ replaceBlock = updatedBlock + "\n";
267
+ }
268
+ // If deleting a block ending in newline, the replacement is empty
269
+ if (searchBlock.endsWith("\n") && updatedBlock.length === 0) {
270
+ replaceBlock = "";
271
+ }
272
 
273
+ // 1. Create a patch from the (potentially adjusted) original and updated blocks
274
+ const patchText = dmp.patch_make(searchBlock, replaceBlock);
275
+
276
+ // 2. Apply the patch to the current HTML
277
+ // diff-match-patch is good at finding the location even with slight context variations.
278
+ // Increase Match_Threshold for potentially larger files or more significant context drift.
279
+ dmp.Match_Threshold = 0.6; // Adjust as needed (0.0 to 1.0)
280
+ dmp.Patch_DeleteThreshold = 0.6; // Adjust as needed
281
+ const [patchedHtml, results] = dmp.patch_apply(patchText, currentHtml);
282
+
283
+ // 3. Check if the patch applied successfully
284
+ if (results.every((result) => result === true)) {
285
+ return patchedHtml;
286
+ } else {
287
+ console.warn(
288
+ "Patch application failed using diff-match-patch. Results:",
289
+ results
290
+ );
291
+ // Fallback: Try exact string replacement (less robust)
292
+ if (currentHtml.includes(searchBlock)) {
293
+ console.log("Falling back to direct string replacement.");
294
+ // Use replace only once
295
+ const index = currentHtml.indexOf(searchBlock);
296
+ if (index !== -1) {
297
+ return (
298
+ currentHtml.substring(0, index) +
299
+ replaceBlock +
300
+ currentHtml.substring(index + searchBlock.length)
301
+ );
302
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
+ console.error("Direct string replacement fallback also failed.");
305
+ return null; // Indicate failure
306
+ }
307
  }
308
 
 
309
  /**
310
  * Applies all parsed diff blocks sequentially to the original HTML.
311
  * @param {string} originalHtml - The initial HTML content.
 
314
  * @throws {Error} If any diff block fails to apply.
315
  */
316
  function applyDiffs(originalHtml, aiResponseContent) {
317
+ const diffBlocks = parseDiffBlocks(aiResponseContent);
318
+
319
+ if (diffBlocks.length === 0) {
320
+ console.warn("AI response did not contain valid SEARCH/REPLACE blocks.");
321
+ // Check if the AI *tried* to use the format but failed, or just gave full code
322
+ if (
323
+ aiResponseContent.includes(SEARCH_START) ||
324
+ aiResponseContent.includes(DIVIDER) ||
325
+ aiResponseContent.includes(REPLACE_END)
326
+ ) {
327
+ throw new Error(
328
+ "AI response contained malformed or unparseable diff blocks. Could not apply changes."
329
+ );
330
+ }
331
+ // If no diff blocks *at all*, maybe the AI ignored the instruction and gave full code?
332
+ // Heuristic: If the response looks like a full HTML doc, use it directly.
333
+ const trimmedResponse = aiResponseContent.trim().toLowerCase();
334
+ if (
335
+ trimmedResponse.startsWith("<!doctype html") ||
336
+ trimmedResponse.startsWith("<html")
337
+ ) {
338
+ console.warn(
339
+ "[Diff Apply] AI response seems to be full HTML despite diff instructions. Using full response as fallback."
340
+ );
341
+ return aiResponseContent;
342
  }
343
+ console.warn(
344
+ "[Diff Apply] No valid diff blocks found and response doesn't look like full HTML. Returning original HTML."
345
+ );
346
+ return originalHtml; // Return original if no diffs and not full HTML
347
+ }
348
 
349
+ console.log(`Found ${diffBlocks.length} diff blocks to apply.`);
350
+ let currentHtml = originalHtml;
351
+ for (let i = 0; i < diffBlocks.length; i++) {
352
+ const { original, updated } = diffBlocks[i];
353
+ console.log(`Applying block ${i + 1}...`);
354
+ const result = applySingleDiffFuzzy(currentHtml, original, updated);
355
+
356
+ if (result === null) {
357
+ // Log detailed error for debugging
358
+ console.error(`Failed to apply diff block ${i + 1}:`);
359
+ console.error("--- SEARCH ---");
360
+ console.error(original);
361
+ console.error("--- REPLACE ---");
362
+ console.error(updated);
363
+ console.error("--- CURRENT CONTEXT (approx) ---");
364
+ // Try finding the first line of the original block for context
365
+ const firstLine = original.split("\n")[0];
366
+ let contextIndex = -1;
367
+ if (firstLine) {
368
+ contextIndex = currentHtml.indexOf(firstLine);
369
+ }
370
+ if (contextIndex === -1) {
371
+ // If first line not found, maybe try middle line?
372
+ const lines = original.split("\n");
373
+ if (lines.length > 2) {
374
+ contextIndex = currentHtml.indexOf(
375
+ lines[Math.floor(lines.length / 2)]
376
+ );
 
 
 
 
 
 
 
377
  }
378
+ }
379
+ if (contextIndex === -1) {
380
+ // Still not found, just show start
381
+ contextIndex = 0;
382
+ }
383
+
384
+ console.error(
385
+ currentHtml.substring(
386
+ Math.max(0, contextIndex - 150),
387
+ Math.min(currentHtml.length, contextIndex + original.length + 300)
388
+ )
389
+ );
390
+ console.error("---------------------------------");
391
+
392
+ throw new Error(
393
+ `Failed to apply AI-suggested change ${
394
+ i + 1
395
+ }. The 'SEARCH' block might not accurately match the current code.`
396
+ );
397
  }
398
+ currentHtml = result;
399
+ }
400
 
401
+ console.log("All diff blocks applied successfully.");
402
+ return currentHtml;
403
  }
404
 
 
405
  // --- AI Interaction Route ---
406
  app.post("/api/ask-ai", async (req, res) => {
407
  const { prompt, html, previousPrompt } = req.body;
 
413
  }
414
 
415
  const isFollowUp = !!html && !!previousPrompt; // Check if it's a follow-up request
416
+ console.log(`[AI Request] Type: ${isFollowUp ? "Follow-up" : "Initial"}`);
417
 
418
  const { hf_token } = req.cookies;
419
  let token = hf_token;
 
431
  return res.status(429).send({
432
  ok: false,
433
  openLogin: true,
434
+ message:
435
+ "Log In to continue using the service (Rate limit exceeded for anonymous users)",
436
  });
437
  }
438
  token = process.env.DEFAULT_HF_TOKEN;
 
487
  ONLY output the changes in this format. Do NOT output the full HTML file again.`;
488
 
489
  // --- Prepare Messages for AI ---
490
+ const systemPromptContent = isFollowUp
491
+ ? followUpSystemPrompt
492
+ : initialSystemPrompt;
493
+ console.log(
494
+ `[AI Request] Using system prompt: ${
495
+ isFollowUp ? "Follow-up (Diff)" : "Initial (Full HTML)"
496
+ }`
497
+ );
498
  // console.log("[AI Request] System Prompt Content:\n", systemPromptContent); // Uncomment for full prompt text
499
 
500
  const messages = [
 
505
  // Include previous context if available
506
  ...(previousPrompt ? [{ role: "user", content: previousPrompt }] : []),
507
  // Provide current code clearly ONLY if it's a follow-up
508
+ ...(isFollowUp && html
509
+ ? [
510
+ {
511
+ role: "assistant",
512
+ content: `Okay, I have the current code. It is:\n\`\`\`html\n${html}\n\`\`\``,
513
+ },
514
+ ]
515
+ : []),
516
  // Current user prompt
517
  { role: "user", content: prompt },
518
  ];
519
 
 
520
  const client = new InferenceClient(token);
521
  let completeResponse = "";
522
 
 
526
  res.setHeader("Cache-Control", "no-cache");
527
  res.setHeader("Connection", "keep-alive");
528
  res.setHeader("X-Response-Type", isFollowUp ? "diff" : "full"); // Signal type to client
529
+ console.log(
530
+ `[AI Request] Set X-Response-Type: ${isFollowUp ? "diff" : "full"}`
531
+ );
532
 
533
  const chatCompletion = client.chatCompletionStream({
534
  model: MODEL_ID,
535
  provider: isFollowUp ? "fireworks-ai" : "sambanova", // Use sambanova for initial, fireworks for follow-up
536
  messages: messages,
537
+ max_tokens: 6_000, // Keep max_tokens reasonable
538
  temperature: isFollowUp ? 0 : undefined, // Set temperature to 0 for follow-ups, otherwise use default
539
  });
540
 
 
555
  // console.log("--------------------------");
556
 
557
  res.end(); // End the stream
 
558
  } catch (error) {
559
  console.error("Error during AI interaction:", error); // Removed "or diff application"
560
  // If we haven't sent headers/started streaming yet
561
  if (!res.headersSent) {
562
+ // Check if it's an AbortError which might happen if the client disconnects
563
+ if (error.name === "AbortError") {
564
+ console.warn("Client disconnected before AI response finished.");
565
+ // Don't send another response if client is gone
566
+ return;
567
+ }
568
  res.status(500).send({
569
  ok: false,
570
  // Provide a more user-friendly message, but keep details for logs
 
576
  console.error("Error occurred mid-stream. Ending response.");
577
  res.end(); // End the stream abruptly if error occurs during streaming
578
  }
579
+ // If streaming failed *after* res.end() was called (unlikely but possible), do nothing more.
580
  }
581
  });
582