everydaytok commited on
Commit
893fe0d
·
verified ·
1 Parent(s): a01c2c7

Delete app2.js

Browse files
Files changed (1) hide show
  1. app2.js +0 -707
app2.js DELETED
@@ -1,707 +0,0 @@
1
- import express from 'express';
2
- import bodyParser from 'body-parser';
3
- import cors from 'cors';
4
- import { StateManager, initDB } from './stateManager.js';
5
- import { AIEngine } from './aiEngine.js';
6
- import fs from 'fs';
7
- import admin from 'firebase-admin';
8
- import crypto from "crypto";
9
-
10
- // --- FIREBASE SETUP ---
11
- let db = null;
12
- let firestore = null;
13
- let storage = null;
14
-
15
- try {
16
- if (process.env.FIREBASE_SERVICE_ACCOUNT_JSON) {
17
- const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON);
18
-
19
- // Specific bucket as requested
20
- const bucketName = `hollowpad-ai-default-rtdb.firebaseio.com` // `shago-web.firebasestorage.app`;
21
-
22
- if (admin.apps.length === 0) {
23
- admin.initializeApp({
24
- credential: admin.credential.cert(serviceAccount),
25
- databaseURL: "https://shago-web-default-rtdb.firebaseio.com",
26
- storageBucket: bucketName
27
- });
28
- }
29
-
30
- db = admin.database();
31
- firestore = admin.firestore();
32
- storage = admin.storage();
33
-
34
- initDB(db);
35
- console.log("🔥 Firebase Connected (RTDB, Firestore, Storage) & StateManager Linked");
36
- } else {
37
- console.warn("⚠️ Memory-Only mode.");
38
- }
39
- } catch (e) { console.error("Firebase Init Error:", e); }
40
-
41
-
42
- const app = express();
43
- const PORT = process.env.PORT || 7860;
44
-
45
- // ---
46
- /*
47
-
48
- app.use((req, res, next) => {
49
- console.log('[REQ]', req.method, req.originalUrl);
50
- console.log('[HEADERS]', req.headers);
51
- console.log('[BODY]', req.body ?? '(unparseable or empty)');
52
- next();
53
- });
54
-
55
- */
56
-
57
- // ---
58
-
59
-
60
- // Load prompts safely
61
- let sysPrompts = {};
62
- try {
63
- sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8'));
64
- } catch (e) {
65
- console.error("Failed to load prompts.json:", e);
66
- }
67
-
68
- app.use(cors());
69
- app.use(bodyParser.json({ limit: '50mb' }));
70
-
71
- // --- CREDIT CONFIGURATION ---
72
-
73
- // Minimums required to start a request
74
- const MIN_BASIC_REQUIRED = 50;
75
- const MIN_DIAMOND_REQUIRED = 50;
76
-
77
- // Fixed cost for images (Basic credits)
78
- const IMAGE_COST_BASIC = 1000;
79
-
80
- /**
81
- * Checks if user has enough credits of a specific type.
82
- * @param {string} userId
83
- * @param {'basic'|'diamond'} type
84
- */
85
- async function checkMinimumCredits(userId, type = 'basic') {
86
- if (!db) return;
87
-
88
- // Path: users/{uid}/credits/basic OR users/{uid}/credits/diamond
89
- const snap = await db.ref(`users/${userId}/credits/${type}`).once('value');
90
- const credits = snap.val() || 0;
91
-
92
- const required = type === 'diamond' ? MIN_DIAMOND_REQUIRED : MIN_BASIC_REQUIRED;
93
-
94
- if (credits < required) {
95
-
96
- res.status(500).json({
97
- insufficient: true,
98
- error:`Insufficient ${type} credits. You have ${credits}, need minimum ${required} to proceed.`
99
-
100
- });
101
- // throw new Error(`Insufficient ${type} credits. You have ${credits}, need minimum ${required} to proceed.`);
102
- }
103
- }
104
-
105
- /**
106
- * Deducts exact tokens from the user's specific wallet.
107
- * @param {string} userId
108
- * @param {number} amount
109
- * @param {'basic'|'diamond'} type
110
- */
111
- async function deductUserCredits(userId, amount, type = 'basic') {
112
- if (!db || !amount || amount <= 0) return;
113
-
114
- try {
115
- const ref = db.ref(`users/${userId}/credits/${type}`);
116
- await ref.transaction((current_credits) => {
117
- const current = current_credits || 0;
118
- return Math.max(0, current - amount);
119
- });
120
- console.log(`[Credits] Deducted ${amount} ${type} credits from User ${userId}`);
121
- } catch (err) {
122
- console.error(`[Credits] Failed to deduct ${amount} ${type} from ${userId}:`, err);
123
- }
124
- }
125
-
126
- // --- MIDDLEWARE ---
127
-
128
- const validateRequest = (req, res, next) => {
129
- if (req.path.includes('/admin/cleanup')) return next();
130
-
131
- const { userId, projectId } = req.body;
132
-
133
- // Ensure userId exists for credit checks
134
- if (!userId && (req.path.includes('/onboarding') || req.path.includes('/new') || req.path.includes('/project'))) {
135
- return res.status(400).json({ error: "Missing userId" });
136
- }
137
-
138
- next();
139
- };
140
-
141
- function extractWorkerPrompt(text) {
142
- const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
143
- return match ? match[1].trim() : null;
144
- }
145
-
146
- function formatContext({ hierarchyContext, scriptContext, logContext }) {
147
- let out = "";
148
- if (scriptContext) out += `\n[TARGET SCRIPT]: ${scriptContext.targetName}\n[SOURCE PREVIEW]: ${scriptContext.scriptSource}`;
149
- // if (scriptContext) out += `\n[TARGET SCRIPT]: ${scriptContext.targetName}\n[SOURCE PREVIEW]: ${scriptContext.scriptSource?.substring(0, 1000)}...`;
150
- if (logContext) out += `\n[LAST LOGS]: ${logContext.logs}`;
151
- // my own line
152
- if (hierarchyContext) out += `\n[Hierarchy Context]: ${hierarchyContext}`;
153
- return out;
154
- }
155
-
156
- function extractPMQuestion(text) {
157
- const match = text.match(/\[ASK_PM:\s*(.*?)\]/s);
158
- return match ? match[1].trim() : null;
159
- }
160
-
161
- function extractImagePrompt(text) {
162
- const match = text.match(/\[GENERATE_IMAGE:\s*(.*?)\]/s);
163
- return match ? match[1].trim() : null;
164
- }
165
-
166
- // --- ADMIN ENDPOINTS ---
167
-
168
- app.get('/admin/cleanup', async (req, res) => {
169
- try {
170
- const removed = StateManager.cleanupMemory();
171
- res.json({ success: true, removedCount: removed });
172
- } catch (err) {
173
- res.status(500).json({ error: "Cleanup failed" });
174
- }
175
- });
176
-
177
- // --- ONBOARDING ENDPOINTS ---
178
-
179
- app.post('/onboarding/analyze', validateRequest, async (req, res) => {
180
- const { description, userId } = req.body;
181
-
182
- if (!description) return res.status(400).json({ error: "Description required" });
183
-
184
- try {
185
- // Analyst uses BASIC models (Flash)
186
- await checkMinimumCredits(userId, 'basic');
187
-
188
- console.log(`[Onboarding] Analyzing idea...`);
189
-
190
- const result = await AIEngine.generateEntryQuestions(description);
191
-
192
- const usage = result.usage?.totalTokenCount || 0;
193
-
194
- // Bill Basic
195
- if (usage > 0) await deductUserCredits(userId, usage, 'basic');
196
-
197
- if (result.status === "REJECTED") {
198
- return res.json({
199
- rejected: true,
200
- reason: result.reason || "Idea violates TOS."
201
- });
202
- }
203
- res.json({ questions: result.questions });
204
-
205
- } catch (err) {
206
- console.error(err);
207
- if (err.message.includes("Insufficient")) {
208
- return res.status(402).json({ error: err.message });
209
- }
210
- res.status(500).json({ error: "Analysis failed" });
211
- }
212
- });
213
-
214
- app.post('/onboarding/create', validateRequest, async (req, res) => {
215
- const { userId, description, answers } = req.body;
216
- let basicTokens = 0; // Create flow uses Grader (Basic) + Image (Basic)
217
-
218
- try {
219
- // Pre-flight check
220
- await checkMinimumCredits(userId, 'basic');
221
-
222
- const randomHex = (n = 6) => crypto.randomBytes(Math.ceil(n/2)).toString("hex").slice(0, n);
223
- const projectId = `proj_${Date.now()}_${randomHex(7)}`;
224
-
225
- console.log(`[Onboarding] Grading Project ${projectId}...`);
226
-
227
- // 1. Grading (Basic)
228
- const gradingResult = await AIEngine.gradeProject(description, answers);
229
- basicTokens += (gradingResult.usage?.totalTokenCount || 0);
230
-
231
- const isFailure = gradingResult.feasibility < 30 || gradingResult.rating === 'F';
232
-
233
- let thumbnailBase64 = null;
234
-
235
- /*
236
- * image generator
237
- if (!isFailure) {
238
- console.log(`[Onboarding] ✅ Passed. Generating Thumbnail...`);
239
- const imagePrompt = `Game Title: ${gradingResult.title}. Core Concept: ${description}`;
240
-
241
- // 2. Image Generation (Basic)
242
- const imgResult = await AIEngine.generateImage(imagePrompt);
243
- if (imgResult) {
244
- thumbnailBase64 = imgResult.image;
245
- // Add token usage for text + fixed cost
246
- basicTokens += (imgResult.usage || 0);
247
- if (imgResult.image) basicTokens += IMAGE_COST_BASIC;
248
- }
249
- } */
250
-
251
- // Upload Logic
252
- let thumbnailUrl = null;
253
- if (thumbnailBase64 && storage) {
254
- try {
255
- const base64Data = thumbnailBase64.replace(/^data:image\/\w+;base64,/, "");
256
- const buffer = Buffer.from(base64Data, 'base64');
257
- const bucket = storage.bucket();
258
- const file = bucket.file(`${projectId}/thumbnail.png`);
259
- await file.save(buffer, { metadata: { contentType: 'image/png' } });
260
- await file.makePublic();
261
- thumbnailUrl = `https://storage.googleapis.com/${bucket.name}/${projectId}/thumbnail.png`;
262
- } catch (uploadErr) {
263
- console.error("Storage Upload Failed:", uploadErr);
264
- }
265
- }
266
-
267
- const timestamp = Date.now();
268
- const status = isFailure ? "rejected" : "Idle";
269
-
270
- // Save Data Logic
271
- if (!isFailure) {
272
- const memoryObject = {
273
- id: projectId,
274
- userId,
275
- title: gradingResult.title || "Untitled",
276
- description,
277
- answers,
278
- stats: gradingResult,
279
- thumbnail: thumbnailUrl,
280
- createdAt: timestamp,
281
- status,
282
- workerHistory: [],
283
- pmHistory: [],
284
- commandQueue: [],
285
- failureCount: 0
286
- };
287
- await StateManager.updateProject(projectId, memoryObject);
288
- }
289
-
290
- if (firestore && !isFailure) {
291
- await firestore.collection('projects').doc(projectId).set({
292
- id: projectId,
293
- userId: userId,
294
- assets: thumbnailUrl ? [thumbnailUrl] : [],
295
- createdAt: admin.firestore.FieldValue.serverTimestamp()
296
- });
297
- }
298
-
299
- if (db && !isFailure) {
300
- const updates = {};
301
- updates[`projects/${projectId}/info`] = {
302
- id: projectId,
303
- userId,
304
- title: gradingResult.title || "Untitled",
305
- description,
306
- answers,
307
- stats: gradingResult,
308
- createdAt: timestamp,
309
- status
310
- };
311
- if (thumbnailUrl) updates[`projects/${projectId}/thumbnail`] = { url: thumbnailUrl };
312
- updates[`projects/${projectId}/state`] = {
313
- workerHistory: [],
314
- pmHistory: [],
315
- commandQueue: [],
316
- failureCount: 0
317
- };
318
- await db.ref().update(updates);
319
- }
320
-
321
- // Deduct Basic Credits
322
- if (basicTokens > 0) await deductUserCredits(userId, basicTokens, 'basic');
323
- console.log("sending");
324
- res.json({
325
- status: 200,
326
- success: !isFailure,
327
- projectId,
328
- stats: gradingResult,
329
- title: gradingResult.title || "Untitled",
330
- thumbnail: thumbnailBase64
331
- });
332
-
333
- } catch (err) {
334
- console.error("Create Error:", err);
335
- if (err.message.includes("Insufficient")) {
336
- return res.status(402).json({ error: err.message });
337
- }
338
- res.status(500).json({ error: "Creation failed" });
339
- }
340
- });
341
-
342
- // --- CORE ENDPOINTS ---
343
-
344
- async function runBackgroundInitialization(projectId, userId, description) {
345
- console.log(`[Background] Starting initialization for ${projectId}`);
346
-
347
- // Separate Counters
348
- let diamondUsage = 0; // For PM
349
- let basicUsage = 0; // For Worker
350
-
351
- try {
352
- const pmHistory = [];
353
-
354
- // 1. Generate GDD (PM -> Diamond)
355
- const gddPrompt = `Create a comprehensive GDD for: ${description}`;
356
- const gddResult = await AIEngine.callPM(pmHistory, gddPrompt);
357
-
358
- diamondUsage += (gddResult.usage?.totalTokenCount || 0);
359
- const gddText = gddResult.text;
360
-
361
- pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] });
362
- pmHistory.push({ role: 'model', parts: [{ text: gddText }] });
363
-
364
- // 2. Generate First Task (PM -> Diamond)
365
- const taskPrompt = "Based on the GDD, generate the first technical milestone.\nOutput format:\nTASK_NAME: <Name>\nWORKER_PROMPT: <Specific, isolated instructions for the worker>";
366
- const taskResult = await AIEngine.callPM(pmHistory, taskPrompt);
367
-
368
- diamondUsage += (taskResult.usage?.totalTokenCount || 0);
369
- const taskText = taskResult.text;
370
-
371
- pmHistory.push({ role: 'user', parts: [{ text: taskPrompt }] });
372
- pmHistory.push({ role: 'model', parts: [{ text: taskText }] });
373
-
374
- // 3. Initialize Worker (Worker -> Basic)
375
- const initialWorkerInstruction = extractWorkerPrompt(taskText) || `Initialize structure for: ${description}`;
376
- const workerHistory = [];
377
- const initialWorkerPrompt = `CONTEXT: New Project. \nINSTRUCTION: ${initialWorkerInstruction}`;
378
-
379
- const workerResult = await AIEngine.callWorker(workerHistory, initialWorkerPrompt, []);
380
-
381
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
382
- const workerText = workerResult.text;
383
-
384
- workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
385
- workerHistory.push({ role: 'model', parts: [{ text: workerText }] });
386
-
387
- // 4. Update Memory
388
- await StateManager.updateProject(projectId, {
389
- userId,
390
- pmHistory,
391
- workerHistory,
392
- gdd: gddText,
393
- failureCount: 0
394
- });
395
-
396
- // 5. Queue commands (Handle assets + basic costs)
397
- // We pass userId to deduct image costs (Basic) immediately inside this function
398
- await processAndQueueResponse(projectId, workerText, userId);
399
-
400
- // 6. Update Status
401
- if(db) await db.ref(`projects/${projectId}/info`).update({
402
- status: "IDLE",
403
- lastUpdated: Date.now()
404
- });
405
-
406
- // 7. Deduct Accumulated Credits
407
- if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
408
- if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
409
-
410
- console.log(`[Background] Init complete. Diamond: ${diamondUsage}, Basic: ${basicUsage}`);
411
-
412
- } catch (err) {
413
- console.error(`[Background] Init Error for ${projectId}:`, err);
414
- if(db) await db.ref(`projects/${projectId}/info/status`).set("error");
415
- }
416
- }
417
-
418
- app.post('/new/project', validateRequest, async (req, res) => {
419
- const { userId, projectId, description } = req.body;
420
-
421
- try {
422
- // Init requires BOTH types to be safe
423
- await checkMinimumCredits(userId, 'diamond');
424
- await checkMinimumCredits(userId, 'basic');
425
-
426
- if(db) db.ref(`projects/${projectId}/info/status`).set("initializing");
427
-
428
- res.json({
429
- success: true,
430
- message: "Project initialization started in background."
431
- });
432
-
433
- runBackgroundInitialization(projectId, userId, description);
434
-
435
- } catch (err) {
436
- if (err.message.includes("Insufficient")) {
437
- return res.status(402).json({ error: err.message });
438
- }
439
- res.status(500).json({ error: "Failed to start project" });
440
- }
441
- });
442
-
443
- app.post('/project/feedback', async (req, res) => {
444
- const { userId, projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete, images } = req.body;
445
-
446
- let diamondUsage = 0;
447
- let basicUsage = 0;
448
-
449
- try {
450
- const project = await StateManager.getProject(projectId);
451
- if (!project) return res.status(404).json({ error: "Project not found." });
452
- if (project.userId !== userId) return res.status(403).json({ error: "Unauthorized" });
453
-
454
- // Basic Check (most interaction is worker)
455
- await checkMinimumCredits(userId, 'basic');
456
-
457
- if(db) await db.ref(`projects/${projectId}/info/status`).set("working");
458
-
459
- if (taskComplete) {
460
- console.log(`[${projectId}] ✅ TASK COMPLETE.`);
461
- const summary = `Worker completed the previous task. Logs: ${logContext?.logs || "Clean"}. \nGenerate the NEXT task using 'WORKER_PROMPT:' format.`;
462
-
463
- // PM Call -> Diamond
464
- const pmResult = await AIEngine.callPM(project.pmHistory, summary);
465
- diamondUsage += (pmResult.usage?.totalTokenCount || 0);
466
- const pmText = pmResult.text;
467
-
468
- project.pmHistory.push({ role: 'user', parts: [{ text: summary }] });
469
- project.pmHistory.push({ role: 'model', parts: [{ text: pmText }] });
470
-
471
- const nextInstruction = extractWorkerPrompt(pmText);
472
- if (!nextInstruction) {
473
- await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, status: "IDLE" });
474
- if(db) await db.ref(`projects/${projectId}/info`).update({ status: "IDLE", lastUpdated: Date.now() });
475
-
476
- // Deduct what we used
477
- if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
478
-
479
- return res.json({ success: true, message: "No further tasks. Project Idle." });
480
- }
481
-
482
- const newWorkerHistory = [];
483
- const newPrompt = `New Objective: ${nextInstruction}`;
484
-
485
- // Worker Call -> Basic
486
- const workerResult = await AIEngine.callWorker(newWorkerHistory, newPrompt, []);
487
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
488
- const workerText = workerResult.text;
489
-
490
- newWorkerHistory.push({ role: 'user', parts: [{ text: newPrompt }] });
491
- newWorkerHistory.push({ role: 'model', parts: [{ text: workerText }] });
492
-
493
- await StateManager.updateProject(projectId, {
494
- pmHistory: project.pmHistory,
495
- workerHistory: newWorkerHistory,
496
- failureCount: 0
497
- });
498
-
499
- StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "warn('Starting Next Task...')" });
500
- await processAndQueueResponse(projectId, workerText, userId);
501
-
502
- if(db) await db.ref(`projects/${projectId}/info`).update({ status: "working", lastUpdated: Date.now() });
503
-
504
- // Final Bill
505
- if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
506
- if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
507
-
508
- return res.json({ success: true, message: "Next Task Assigned" });
509
- }
510
-
511
- // Logic for Failure Handling (PM Guidance)
512
- if (project.failureCount > 3) {
513
- const pmPrompt = (sysPrompts.pm_guidance_prompt || "Analyze logs: {{LOGS}}").replace('{{LOGS}}', logContext?.logs);
514
-
515
- // PM -> Diamond
516
- const pmResult = await AIEngine.callPM(project.pmHistory, pmPrompt);
517
- diamondUsage += (pmResult.usage?.totalTokenCount || 0);
518
- const pmVerdict = pmResult.text;
519
-
520
- if (pmVerdict.includes("[TERMINATE]")) {
521
- const fixInstruction = pmVerdict.replace("[TERMINATE]", "").trim();
522
- const resetHistory = [];
523
- const resetPrompt = `[SYSTEM]: Previous worker terminated. \nNew Objective: ${fixInstruction}`;
524
-
525
- // Worker -> Basic
526
- const workerResult = await AIEngine.callWorker(resetHistory, resetPrompt, []);
527
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
528
-
529
- resetHistory.push({ role: 'user', parts: [{ text: resetPrompt }] });
530
- resetHistory.push({ role: 'model', parts: [{ text: workerResult.text }] });
531
-
532
- await StateManager.updateProject(projectId, { workerHistory: resetHistory, failureCount: 0 });
533
- StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "print('SYSTEM: Worker Reset')" });
534
- await processAndQueueResponse(projectId, workerResult.text, userId);
535
-
536
- if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
537
- if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
538
- return res.json({ success: true, message: "Worker Terminated." });
539
- } else {
540
- const injection = `[PM GUIDANCE]: ${pmVerdict} \n\nApply this fix now.`;
541
-
542
- // Worker -> Basic
543
- const workerResult = await AIEngine.callWorker(project.workerHistory, injection, []);
544
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
545
-
546
- project.workerHistory.push({ role: 'user', parts: [{ text: injection }] });
547
- project.workerHistory.push({ role: 'model', parts: [{ text: workerResult.text }] });
548
-
549
- await StateManager.updateProject(projectId, { workerHistory: project.workerHistory, failureCount: 1 });
550
- await processAndQueueResponse(projectId, workerResult.text, userId);
551
-
552
- if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
553
- if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
554
- return res.json({ success: true, message: "PM Guidance Applied." });
555
- }
556
- }
557
-
558
- // Standard Interaction (Worker Only = Basic)
559
- const fullInput = `USER: ${prompt || "Automatic Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext });
560
-
561
- let workerResult = await AIEngine.callWorker(project.workerHistory, fullInput, images || []);
562
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
563
- let responseText = workerResult.text;
564
-
565
- // PM Consultation Check
566
- const pmQuestion = extractPMQuestion(responseText);
567
- if (pmQuestion) {
568
- // PM -> Diamond
569
- const pmConsultPrompt = `[WORKER CONSULTATION]: The Worker asks: "${pmQuestion}"\nProvide a technical answer to unblock them.`;
570
- const pmResult = await AIEngine.callPM(project.pmHistory, pmConsultPrompt);
571
- diamondUsage += (pmResult.usage?.totalTokenCount || 0);
572
- const pmAnswer = pmResult.text;
573
-
574
- project.pmHistory.push({ role: 'user', parts: [{ text: pmConsultPrompt }] });
575
- project.pmHistory.push({ role: 'model', parts: [{ text: pmAnswer }] });
576
-
577
- const injectionMsg = `[PM RESPONSE]: ${pmAnswer}`;
578
- project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
579
- project.workerHistory.push({ role: 'model', parts: [{ text: responseText }] });
580
-
581
- // Re-call Worker -> Basic
582
- workerResult = await AIEngine.callWorker(project.workerHistory, injectionMsg, []);
583
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
584
- responseText = workerResult.text;
585
-
586
- project.workerHistory.push({ role: 'user', parts: [{ text: injectionMsg }] });
587
- project.workerHistory.push({ role: 'model', parts: [{ text: responseText }] });
588
- } else {
589
- project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
590
- project.workerHistory.push({ role: 'model', parts: [{ text: responseText }] });
591
- }
592
-
593
- await StateManager.updateProject(projectId, {
594
- workerHistory: project.workerHistory,
595
- pmHistory: project.pmHistory,
596
- failureCount: project.failureCount
597
- });
598
-
599
- await processAndQueueResponse(projectId, responseText, userId);
600
-
601
- if(db) await db.ref(`projects/${projectId}/info`).update({ status: "idle", lastUpdated: Date.now() });
602
-
603
- // Final Billing
604
- if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
605
- if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
606
-
607
- res.json({ success: true });
608
-
609
- } catch (err) {
610
- console.error("AI Error:", err);
611
- if (err.message.includes("Insufficient")) {
612
- return res.status(402).json({ error: err.message });
613
- }
614
- res.status(500).json({ error: "AI Failed" });
615
- }
616
- });
617
-
618
- app.post('/project/ping', async (req, res) => {
619
- const { projectId, userId } = req.body;
620
- if (!projectId || !userId) return res.status(400).json({ error: "Missing ID fields" });
621
-
622
- const project = await StateManager.getProject(projectId);
623
- if (!project) return res.status(404).json({ action: "IDLE", error: "Project not found" });
624
- if (project.userId !== userId) return res.status(403).json({ error: "Unauthorized" });
625
-
626
- const command = await StateManager.popCommand(projectId);
627
-
628
- if (command) {
629
- if (command.payload === "CLEAR_CONSOLE") {
630
- res.json({ action: "CLEAR_LOGS" });
631
- } else {
632
- res.json({
633
- action: command.type,
634
- target: command.payload,
635
- code: command.type === 'EXECUTE' ? command.payload : null
636
- });
637
- }
638
- } else {
639
- res.json({ action: "IDLE" });
640
- }
641
- });
642
-
643
- app.post('/human/override', validateRequest, async (req, res) => {
644
- const { projectId, instruction, pruneHistory, userId } = req.body;
645
- let basicUsage = 0;
646
-
647
- try {
648
- await checkMinimumCredits(userId, 'basic');
649
-
650
- const project = await StateManager.getProject(projectId);
651
- const overrideMsg = `[SYSTEM OVERRIDE]: ${instruction}`;
652
-
653
- if (pruneHistory && project.workerHistory.length >= 2) {
654
- project.workerHistory.pop();
655
- project.workerHistory.pop();
656
- }
657
-
658
- // Worker -> Basic
659
- const workerResult = await AIEngine.callWorker(project.workerHistory, overrideMsg, []);
660
- basicUsage += (workerResult.usage?.totalTokenCount || 0);
661
-
662
- project.workerHistory.push({ role: 'user', parts: [{ text: overrideMsg }] });
663
- project.workerHistory.push({ role: 'model', parts: [{ text: workerResult.text }] });
664
-
665
- await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
666
- await processAndQueueResponse(projectId, workerResult.text, userId);
667
-
668
- if (basicUsage > 0) await deductUserCredits(userId, basicUsage, 'basic');
669
-
670
- res.json({ success: true });
671
- } catch (err) {
672
- if (err.message.includes("Insufficient")) {
673
- return res.status(402).json({ error: err.message });
674
- }
675
- res.status(500).json({ error: "Override Failed" });
676
- }
677
- });
678
-
679
- // Helper to handle Asset Parsing & Billing (Basic Credits)
680
- async function processAndQueueResponse(projectId, rawResponse, userId) {
681
- const imgPrompt = extractImagePrompt(rawResponse);
682
- if (imgPrompt) {
683
- console.log(`[${projectId}] 🎨 Generating Asset: ${imgPrompt}`);
684
-
685
- // 1. Generate Image (returns { image, usage } or null)
686
- const imgResult = await AIEngine.generateImage(imgPrompt);
687
-
688
- if (imgResult && imgResult.image) {
689
- // 2. Bill for image tokens immediately (Basic)
690
- const imgTokens = imgResult.usage?.totalTokenCount || 0;
691
- const totalCost = imgTokens; // imgTokens + IMAGE_COST_BASIC;
692
-
693
- if (userId && totalCost > 0) {
694
- await deductUserCredits(userId, totalCost, 'basic');
695
- }
696
-
697
- // 3. Queue creation command
698
- await StateManager.queueCommand(projectId, { type: "CREATE_ASSET", payload: imgResult.image });
699
- }
700
- }
701
- // Queue the raw text response for the frontend
702
- await StateManager.queueCommand(projectId, rawResponse);
703
- }
704
-
705
- app.listen(PORT, () => {
706
- console.log(`AI Backend Running on ${PORT}`);
707
- });