everydaytok commited on
Commit
fa6a3b0
·
verified ·
1 Parent(s): 01a73c9

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +82 -47
app.js CHANGED
@@ -17,13 +17,13 @@ const app = express();
17
  const PORT = process.env.PORT || 7860;
18
 
19
  app.use(cors());
20
- app.use(express.json({ limit: '50mb' })); // Using express.json per modern standards
21
 
22
  // --- BILLING CONSTANTS ---
23
- const MIN_BASIC_REQUIRED = 50;
24
- const MIN_DIAMOND_REQUIRED = 50;
25
 
26
- // --- STATUS PHASES (Restored) ---
27
  const WORKER_PHASES = [
28
  "Worker: Sorting...",
29
  "Worker: Analyzing Request...",
@@ -53,14 +53,13 @@ function startStatusLoop(projectId, type = 'worker') {
53
  StateManager.setStatus(projectId, phases[0]);
54
 
55
  const interval = setInterval(() => {
56
- index = (index + 1) % phases.length; // Loop properly
57
  StateManager.setStatus(projectId, phases[index]);
58
- }, 3500);
59
 
60
  return () => clearInterval(interval);
61
  }
62
 
63
- // Restored robust regex helpers
64
  function extractWorkerPrompt(text) {
65
  if (!text || typeof text !== 'string') return null;
66
  const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
@@ -102,26 +101,36 @@ const validateRequest = (req, res, next) => {
102
  next();
103
  };
104
 
105
- // --- BILLING LOGIC (Inherited from active version) ---
106
 
107
  async function checkMinimumCredits(userId, type = 'basic') {
 
108
  const { data } = await supabase.from('users').select('credits').eq('id', userId).single();
109
- if (!data) return; // Fail silently or throw, strictly checking existing user
 
110
  const credits = data.credits?.[type] || 0;
111
  const req = type === 'diamond' ? MIN_DIAMOND_REQUIRED : MIN_BASIC_REQUIRED;
112
- if (credits < req) throw new Error(`Insufficient ${type} credits. Required: ${req}, Available: ${credits}`);
 
 
 
113
  }
114
 
115
  async function deductUserCredits(userId, amount, type = 'basic') {
116
  const deduction = Math.floor(parseInt(amount, 10));
117
- if (!deduction || deduction <= 0) return;
118
 
119
  try {
120
  const { data } = await supabase.from('users').select('credits').eq('id', userId).single();
121
  if (!data) return;
 
122
  const currentCredits = data.credits || { basic: 0, diamond: 0 };
123
- const newVal = Math.max(0, (currentCredits[type] || 0) - deduction);
124
- await supabase.from('users').update({ credits: { ...currentCredits, [type]: newVal } }).eq('id', userId);
 
 
 
 
125
  console.log(`[Credits] User ${userId} | -${deduction} ${type} | Remaining: ${newVal}`);
126
  } catch (err) {
127
  console.error("Deduction failed", err);
@@ -192,7 +201,6 @@ async function runBackgroundInitialization(projectId, userId, description) {
192
 
193
  let workerTextAccumulated = "";
194
 
195
- // We use stream here to populate UI immediately if user is watching
196
  const workerResult = await AIEngine.callWorkerStream(
197
  [],
198
  initialWorkerPrompt,
@@ -208,6 +216,7 @@ async function runBackgroundInitialization(projectId, userId, description) {
208
  StateManager.appendStream(projectId, chunk);
209
  }
210
  );
 
211
 
212
  basicUsage += (workerResult.usage?.totalTokenCount || 0);
213
 
@@ -230,7 +239,6 @@ async function runBackgroundInitialization(projectId, userId, description) {
230
  await StateManager.updateProject(projectId, { status: "error" });
231
  } finally {
232
  StateManager.unlock(projectId);
233
- // Deduct collected usage
234
  if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
235
  if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
236
  }
@@ -245,7 +253,8 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
245
  try {
246
  console.log(`[${projectId}] Feedback received. Persisting state immediately.`);
247
 
248
- // 1. Instant Persistence
 
249
  await Promise.all([
250
  StateManager.updateProject(projectId, { status: "working" }),
251
  StateManager.addHistory(projectId, 'worker', 'user', fullInput)
@@ -259,6 +268,8 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
259
  let firstTurnResponse = "";
260
  let thoughtText = "";
261
 
 
 
262
  const currentWorkerHistory = [...(project.workerHistory || [])];
263
 
264
  // 2. First Turn (Worker Execution)
@@ -281,16 +292,17 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
281
  );
282
  stopStatus();
283
  basicUsage += (workerResult.usage?.totalTokenCount || 0);
 
284
 
285
  // 3. Analyze Output (Delegation/Questions)
286
- const routeTask = extractRouteToPM(workerResult.text);
287
- const pmQuestion = extractPMQuestion(workerResult.text);
288
- let finalResponseToSave = workerResult.text;
289
 
290
  if (routeTask || pmQuestion) {
291
- // Save preliminary response
292
- await StateManager.addHistory(projectId, 'worker', 'model', workerResult.text);
293
- currentWorkerHistory.push({ role: 'model', parts: [{ text: workerResult.text }] });
294
 
295
  let pmPrompt = "";
296
  let pmContextPrefix = "";
@@ -328,17 +340,20 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
328
  );
329
  stopStatus();
330
  diamondUsage += (pmResult.usage?.totalTokenCount || 0);
 
331
 
332
  await StateManager.addHistory(projectId, 'pm', 'user', pmPrompt);
333
  await StateManager.addHistory(projectId, 'pm', 'model', pmResponseText);
334
 
335
- // Execute PM Code if found
 
336
  await processAndQueueResponse(projectId, pmResponseText, userId);
337
 
338
  // 5. Run Worker Continuation
339
  const nextInstruction = extractWorkerPrompt(pmResponseText) || pmResponseText;
340
  const workerContinuationPrompt = `${pmContextPrefix} ${nextInstruction}\n\nBased on this, continue the task and output the code.`;
341
 
 
342
  await StateManager.addHistory(projectId, 'worker', 'user', workerContinuationPrompt);
343
  currentWorkerHistory.push({ role: 'user', parts: [{ text: workerContinuationPrompt }] });
344
 
@@ -363,27 +378,33 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
363
  }
364
  );
365
  stopStatus();
366
- // Only bill completion tokens for continuation to avoid double charging history
367
- basicUsage += (secondWorkerResult.usage?.outputTokens || secondWorkerResult.usage?.totalTokenCount || 0);
 
 
368
 
369
  await StateManager.addHistory(projectId, 'worker', 'model', secondTurnResponse);
370
  finalResponseToSave = secondTurnResponse;
371
 
372
  } else {
373
- // No delegation, save the single turn
374
  await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
375
  }
376
 
377
  StateManager.setStatus(projectId, "Idle");
378
- await StateManager.updateProject(projectId, { status: "idle" });
379
-
380
  // 6. Final Execution & Cleanup
381
  await processAndQueueResponse(projectId, finalResponseToSave, userId);
 
 
 
382
 
383
  } catch (err) {
384
  console.error("Async Feedback Error:", err);
 
385
  await StateManager.updateProject(projectId, { status: "error" });
386
  } finally {
 
387
  if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
388
  if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
389
  }
@@ -392,13 +413,13 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
392
  async function processAndQueueResponse(projectId, rawResponse, userId) {
393
  if (!rawResponse) return;
394
 
395
- // Image Generation Handling
396
  const imgPrompt = extractImagePrompt(rawResponse);
397
  if (imgPrompt) {
398
  try {
399
  const imgResult = await AIEngine.generateImage(imgPrompt);
400
  if (imgResult?.image) {
401
- if (userId) await deductUserCredits(userId, 1000, 'basic'); // Flat fee for images
402
  await StateManager.queueCommand(projectId, { type: "CREATE_ASSET", payload: imgResult.image });
403
  }
404
  } catch (e) {
@@ -407,12 +428,16 @@ async function processAndQueueResponse(projectId, rawResponse, userId) {
407
  }
408
 
409
  // Code Execution
410
- const codeMatch = rawResponse.match(/```(?:lua)?([\s\S]*?)```/);
 
 
411
  if (codeMatch) {
412
  await StateManager.queueCommand(projectId, { type: "EXECUTE", payload: codeMatch[1].trim() });
413
  } else {
414
- // Fallback: If the response is purely code but without blocks (rare but possible)
415
- await StateManager.queueCommand(projectId, { type: "EXECUTE", payload: rawResponse });
 
 
416
  }
417
  }
418
 
@@ -423,8 +448,14 @@ app.post('/onboarding/analyze', validateRequest, async (req, res) => {
423
  if (!description) return res.status(400).json({ error: "Description required" });
424
 
425
  try {
426
- await checkMinimumCredits(userId, 'basic');
427
- const result = await AIEngine.generateEntryQuestions(description);
 
 
 
 
 
 
428
 
429
  if (result.usage?.totalTokenCount > 0) {
430
  await deductUserCredits(userId, result.usage.totalTokenCount, 'basic');
@@ -444,15 +475,19 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
444
  const { userId, description, answers } = req.body;
445
 
446
  try {
447
- await checkMinimumCredits(userId, 'basic');
448
- await checkMinimumCredits(userId, 'diamond');
 
 
 
 
 
449
 
450
  const randomHex = (n = 6) => crypto.randomBytes(Math.ceil(n/2)).toString("hex").slice(0, n);
451
  const projectId = `proj_${Date.now()}_${randomHex(7)}`;
452
 
453
  const gradingResult = await AIEngine.gradeProject(description, answers);
454
 
455
- // Deduct grading cost immediately
456
  if (gradingResult.usage?.totalTokenCount) {
457
  await deductUserCredits(userId, gradingResult.usage.totalTokenCount, 'basic');
458
  }
@@ -477,7 +512,6 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
477
  return res.status(500).json({ error: "Database save failed" });
478
  }
479
 
480
- // Kick off background initialization
481
  runBackgroundInitialization(projectId, userId, description);
482
  }
483
 
@@ -502,12 +536,18 @@ app.post('/project/feedback', async (req, res) => {
502
  const project = await StateManager.getProject(projectId);
503
  if (!project || project.userId !== userId) return res.status(403).json({ error: "Auth Error" });
504
 
505
- await checkMinimumCredits(userId, 'basic');
 
 
 
 
 
 
506
 
507
  const context = formatContext({ hierarchyContext, scriptContext, logContext });
508
  const fullInput = `USER: ${prompt || "Automatic Feedback"}${context}`;
509
 
510
- // Trigger Async - Process continues in background
511
  runAsyncFeedback(projectId, userId, fullInput, images || []);
512
 
513
  res.json({ success: true, message: "Processing started" });
@@ -527,14 +567,12 @@ app.post('/project/ping', async (req, res) => {
527
  if (!project || project.userId !== userId) return res.json({ action: "IDLE" });
528
 
529
  if (isFrontend) {
530
- // Return Snapshot for streaming UI
531
  return res.json({
532
  status: StateManager.getStatus(projectId),
533
  snapshot: StateManager.getSnapshot(projectId)
534
  });
535
  }
536
 
537
- // Backend Polling for Commands
538
  const command = await StateManager.popCommand(projectId);
539
  const streamData = StateManager.popStream(projectId);
540
 
@@ -559,14 +597,13 @@ app.post('/human/override', validateRequest, async (req, res) => {
559
  await checkMinimumCredits(userId, 'basic');
560
  const project = await StateManager.getProject(projectId);
561
 
562
- res.json({ success: true }); // Respond immediately
563
 
564
  const overrideMsg = `[SYSTEM OVERRIDE]: ${instruction}`;
565
 
566
  let overrideText = "";
567
  let stopStatus = startStatusLoop(projectId, 'worker');
568
 
569
- // Async execution of override
570
  const workerResult = await AIEngine.callWorkerStream(
571
  project.workerHistory,
572
  overrideMsg,
@@ -582,7 +619,6 @@ app.post('/human/override', validateRequest, async (req, res) => {
582
  StateManager.appendStream(projectId, chunk);
583
  }
584
  );
585
-
586
  stopStatus();
587
 
588
  await StateManager.addHistory(projectId, 'worker', 'user', overrideMsg);
@@ -593,7 +629,6 @@ app.post('/human/override', validateRequest, async (req, res) => {
593
  if (usage > 0) await deductUserCredits(userId, usage, 'basic');
594
 
595
  } catch (err) {
596
- // Since we already responded with json success, we just log here
597
  console.error("Override failed", err);
598
  }
599
  });
 
17
  const PORT = process.env.PORT || 7860;
18
 
19
  app.use(cors());
20
+ app.use(express.json({ limit: '50mb' }));
21
 
22
  // --- BILLING CONSTANTS ---
23
+ const MIN_BASIC_REQUIRED = 100;
24
+ const MIN_DIAMOND_REQUIRED = 100;
25
 
26
+ // --- STATUS PHASES ---
27
  const WORKER_PHASES = [
28
  "Worker: Sorting...",
29
  "Worker: Analyzing Request...",
 
53
  StateManager.setStatus(projectId, phases[0]);
54
 
55
  const interval = setInterval(() => {
56
+ index = (index + 1) % phases.length;
57
  StateManager.setStatus(projectId, phases[index]);
58
+ }, 2000); // Slightly faster updates for better UI feedback
59
 
60
  return () => clearInterval(interval);
61
  }
62
 
 
63
  function extractWorkerPrompt(text) {
64
  if (!text || typeof text !== 'string') return null;
65
  const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
 
101
  next();
102
  };
103
 
104
+ // --- BILLING LOGIC ---
105
 
106
  async function checkMinimumCredits(userId, type = 'basic') {
107
+ if (!supabase) return;
108
  const { data } = await supabase.from('users').select('credits').eq('id', userId).single();
109
+ if (!data) return;
110
+
111
  const credits = data.credits?.[type] || 0;
112
  const req = type === 'diamond' ? MIN_DIAMOND_REQUIRED : MIN_BASIC_REQUIRED;
113
+
114
+ if (credits < req) {
115
+ throw new Error(`Insufficient ${type} credits. Required: ${req}, Available: ${credits}`);
116
+ }
117
  }
118
 
119
  async function deductUserCredits(userId, amount, type = 'basic') {
120
  const deduction = Math.floor(parseInt(amount, 10));
121
+ if (!supabase || !deduction || deduction <= 0) return;
122
 
123
  try {
124
  const { data } = await supabase.from('users').select('credits').eq('id', userId).single();
125
  if (!data) return;
126
+
127
  const currentCredits = data.credits || { basic: 0, diamond: 0 };
128
+ const currentVal = currentCredits[type] || 0;
129
+ const newVal = Math.max(0, currentVal - deduction);
130
+
131
+ const updatedCredits = { ...currentCredits, [type]: newVal };
132
+
133
+ await supabase.from('users').update({ credits: updatedCredits }).eq('id', userId);
134
  console.log(`[Credits] User ${userId} | -${deduction} ${type} | Remaining: ${newVal}`);
135
  } catch (err) {
136
  console.error("Deduction failed", err);
 
201
 
202
  let workerTextAccumulated = "";
203
 
 
204
  const workerResult = await AIEngine.callWorkerStream(
205
  [],
206
  initialWorkerPrompt,
 
216
  StateManager.appendStream(projectId, chunk);
217
  }
218
  );
219
+ stopStatus();
220
 
221
  basicUsage += (workerResult.usage?.totalTokenCount || 0);
222
 
 
239
  await StateManager.updateProject(projectId, { status: "error" });
240
  } finally {
241
  StateManager.unlock(projectId);
 
242
  if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
243
  if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
244
  }
 
253
  try {
254
  console.log(`[${projectId}] Feedback received. Persisting state immediately.`);
255
 
256
+ // 1. Instant Persistence: Saves status and message BEFORE processing
257
+ // This ensures if user reloads, the state is preserved
258
  await Promise.all([
259
  StateManager.updateProject(projectId, { status: "working" }),
260
  StateManager.addHistory(projectId, 'worker', 'user', fullInput)
 
268
  let firstTurnResponse = "";
269
  let thoughtText = "";
270
 
271
+ // Clone history to prevent mutation issues
272
+ // Note: project.workerHistory includes the message we just saved above
273
  const currentWorkerHistory = [...(project.workerHistory || [])];
274
 
275
  // 2. First Turn (Worker Execution)
 
292
  );
293
  stopStatus();
294
  basicUsage += (workerResult.usage?.totalTokenCount || 0);
295
+ firstTurnResponse = workerResult.text || firstTurnResponse;
296
 
297
  // 3. Analyze Output (Delegation/Questions)
298
+ const routeTask = extractRouteToPM(firstTurnResponse);
299
+ const pmQuestion = extractPMQuestion(firstTurnResponse);
300
+ let finalResponseToSave = firstTurnResponse;
301
 
302
  if (routeTask || pmQuestion) {
303
+ // Save preliminary response to DB so it doesn't get lost
304
+ await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
305
+ currentWorkerHistory.push({ role: 'model', parts: [{ text: firstTurnResponse }] });
306
 
307
  let pmPrompt = "";
308
  let pmContextPrefix = "";
 
340
  );
341
  stopStatus();
342
  diamondUsage += (pmResult.usage?.totalTokenCount || 0);
343
+ pmResponseText = pmResult.text || pmResponseText;
344
 
345
  await StateManager.addHistory(projectId, 'pm', 'user', pmPrompt);
346
  await StateManager.addHistory(projectId, 'pm', 'model', pmResponseText);
347
 
348
+ // --- EXECUTE PM CODE ---
349
+ // If the PM wrote code (e.g. ServerScriptService logic), execute it immediately
350
  await processAndQueueResponse(projectId, pmResponseText, userId);
351
 
352
  // 5. Run Worker Continuation
353
  const nextInstruction = extractWorkerPrompt(pmResponseText) || pmResponseText;
354
  const workerContinuationPrompt = `${pmContextPrefix} ${nextInstruction}\n\nBased on this, continue the task and output the code.`;
355
 
356
+ // Add system prompt to history
357
  await StateManager.addHistory(projectId, 'worker', 'user', workerContinuationPrompt);
358
  currentWorkerHistory.push({ role: 'user', parts: [{ text: workerContinuationPrompt }] });
359
 
 
378
  }
379
  );
380
  stopStatus();
381
+
382
+ // Deduct usage for second turn
383
+ basicUsage += (secondWorkerResult.usage?.totalTokenCount || 0);
384
+ secondTurnResponse = secondWorkerResult.text || secondTurnResponse;
385
 
386
  await StateManager.addHistory(projectId, 'worker', 'model', secondTurnResponse);
387
  finalResponseToSave = secondTurnResponse;
388
 
389
  } else {
390
+ // No delegation, just save the first response
391
  await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
392
  }
393
 
394
  StateManager.setStatus(projectId, "Idle");
395
+
 
396
  // 6. Final Execution & Cleanup
397
  await processAndQueueResponse(projectId, finalResponseToSave, userId);
398
+
399
+ // Ensure status is idle (this also updates timestamps)
400
+ await StateManager.updateProject(projectId, { status: "idle" });
401
 
402
  } catch (err) {
403
  console.error("Async Feedback Error:", err);
404
+ StateManager.setStatus(projectId, "Error: " + err.message);
405
  await StateManager.updateProject(projectId, { status: "error" });
406
  } finally {
407
+ // Billing
408
  if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
409
  if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
410
  }
 
413
  async function processAndQueueResponse(projectId, rawResponse, userId) {
414
  if (!rawResponse) return;
415
 
416
+ // Image Generation
417
  const imgPrompt = extractImagePrompt(rawResponse);
418
  if (imgPrompt) {
419
  try {
420
  const imgResult = await AIEngine.generateImage(imgPrompt);
421
  if (imgResult?.image) {
422
+ if (userId) await deductUserCredits(userId, 1000, 'basic'); // Flat fee
423
  await StateManager.queueCommand(projectId, { type: "CREATE_ASSET", payload: imgResult.image });
424
  }
425
  } catch (e) {
 
428
  }
429
 
430
  // Code Execution
431
+ // Regex looks for lua/luau code blocks to execute
432
+ const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
433
+
434
  if (codeMatch) {
435
  await StateManager.queueCommand(projectId, { type: "EXECUTE", payload: codeMatch[1].trim() });
436
  } else {
437
+ // If there's no code block but response is non-empty, maybe check if it's pure code?
438
+ // For safety, we usually only execute if wrapped in blocks.
439
+ // But if you want to support raw text execution:
440
+ // await StateManager.queueCommand(projectId, { type: "EXECUTE", payload: rawResponse });
441
  }
442
  }
443
 
 
448
  if (!description) return res.status(400).json({ error: "Description required" });
449
 
450
  try {
451
+ // await checkMinimumCredits(userId, 'basic');
452
+ try {
453
+ await checkMinimumCredits(userId, 'basic');
454
+ } catch (creditErr) {
455
+ return res.json({ success: false, insufficient: true, error: creditErr.message });
456
+ }
457
+
458
+ const result = await AIEngine.generateEntryQuestions(description);
459
 
460
  if (result.usage?.totalTokenCount > 0) {
461
  await deductUserCredits(userId, result.usage.totalTokenCount, 'basic');
 
475
  const { userId, description, answers } = req.body;
476
 
477
  try {
478
+
479
+ try {
480
+ await checkMinimumCredits(userId, 'basic');
481
+ await checkMinimumCredits(userId, 'diamond');
482
+ } catch (creditErr) {
483
+ return res.json({ success: false, insufficient: true, error: creditErr.message });
484
+ }
485
 
486
  const randomHex = (n = 6) => crypto.randomBytes(Math.ceil(n/2)).toString("hex").slice(0, n);
487
  const projectId = `proj_${Date.now()}_${randomHex(7)}`;
488
 
489
  const gradingResult = await AIEngine.gradeProject(description, answers);
490
 
 
491
  if (gradingResult.usage?.totalTokenCount) {
492
  await deductUserCredits(userId, gradingResult.usage.totalTokenCount, 'basic');
493
  }
 
512
  return res.status(500).json({ error: "Database save failed" });
513
  }
514
 
 
515
  runBackgroundInitialization(projectId, userId, description);
516
  }
517
 
 
536
  const project = await StateManager.getProject(projectId);
537
  if (!project || project.userId !== userId) return res.status(403).json({ error: "Auth Error" });
538
 
539
+ // --- FIXED CREDIT CHECK ---
540
+ // Catch credit errors here to return a soft 200 OK so frontend banner shows
541
+ try {
542
+ await checkMinimumCredits(userId, 'basic');
543
+ } catch (creditErr) {
544
+ return res.json({ success: false, insufficient: true, error: creditErr.message });
545
+ }
546
 
547
  const context = formatContext({ hierarchyContext, scriptContext, logContext });
548
  const fullInput = `USER: ${prompt || "Automatic Feedback"}${context}`;
549
 
550
+ // Trigger Async
551
  runAsyncFeedback(projectId, userId, fullInput, images || []);
552
 
553
  res.json({ success: true, message: "Processing started" });
 
567
  if (!project || project.userId !== userId) return res.json({ action: "IDLE" });
568
 
569
  if (isFrontend) {
 
570
  return res.json({
571
  status: StateManager.getStatus(projectId),
572
  snapshot: StateManager.getSnapshot(projectId)
573
  });
574
  }
575
 
 
576
  const command = await StateManager.popCommand(projectId);
577
  const streamData = StateManager.popStream(projectId);
578
 
 
597
  await checkMinimumCredits(userId, 'basic');
598
  const project = await StateManager.getProject(projectId);
599
 
600
+ res.json({ success: true });
601
 
602
  const overrideMsg = `[SYSTEM OVERRIDE]: ${instruction}`;
603
 
604
  let overrideText = "";
605
  let stopStatus = startStatusLoop(projectId, 'worker');
606
 
 
607
  const workerResult = await AIEngine.callWorkerStream(
608
  project.workerHistory,
609
  overrideMsg,
 
619
  StateManager.appendStream(projectId, chunk);
620
  }
621
  );
 
622
  stopStatus();
623
 
624
  await StateManager.addHistory(projectId, 'worker', 'user', overrideMsg);
 
629
  if (usage > 0) await deductUserCredits(userId, usage, 'basic');
630
 
631
  } catch (err) {
 
632
  console.error("Override failed", err);
633
  }
634
  });