everydaycats commited on
Commit
ef9fa39
·
verified ·
1 Parent(s): f98b943

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +142 -52
app.js CHANGED
@@ -4,22 +4,46 @@ import cors from 'cors';
4
  import { StateManager } from './stateManager.js';
5
  import { AIEngine } from './aiEngine.js';
6
  import fs from 'fs';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  const app = express();
9
  const PORT = process.env.PORT || 7860;
10
 
 
11
  const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8'));
12
 
13
  app.use(cors());
14
  app.use(bodyParser.json({ limit: '50mb' }));
15
 
 
16
  const validateRequest = (req, res, next) => {
 
 
 
17
  const { userId, projectId } = req.body;
18
  if (!userId || !projectId) return res.status(400).json({ error: "Missing ID" });
19
  next();
20
  };
21
 
22
- // --- HELPERS ---
23
  function extractWorkerPrompt(text) {
24
  const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
25
  return match ? match[1].trim() : null;
@@ -42,10 +66,77 @@ function extractImagePrompt(text) {
42
  return match ? match[1].trim() : null;
43
  }
44
 
45
- // --- ENDPOINTS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  /**
48
- * 1. NEW PROJECT
49
  */
50
  app.post('/new/project', validateRequest, async (req, res) => {
51
  const { userId, projectId, description } = req.body;
@@ -73,6 +164,7 @@ app.post('/new/project', validateRequest, async (req, res) => {
73
  workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
74
  workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
75
 
 
76
  await StateManager.updateProject(projectId, {
77
  userId,
78
  pmHistory,
@@ -81,19 +173,19 @@ app.post('/new/project', validateRequest, async (req, res) => {
81
  failureCount: 0
82
  });
83
 
84
- // Queue Initial Response
85
  await processAndQueueResponse(projectId, workerResponse);
86
 
87
- res.json({ success: true, message: "Project Initialized", gddPreview: gddResponse.substring(0, 200) });
88
 
89
  } catch (err) {
90
- console.error(err);
91
- res.status(500).json({ error: "Init Failed" });
92
  }
93
  });
94
 
95
  /**
96
- * 2. FEEDBACK LOOP
97
  */
98
  app.post('/project/feedback', async (req, res) => {
99
  const { projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete, images } = req.body;
@@ -101,7 +193,7 @@ app.post('/project/feedback', async (req, res) => {
101
  const project = await StateManager.getProject(projectId);
102
  if (!project) return res.status(404).json({ error: "Project not found." });
103
 
104
- // A. TASK COMPLETE
105
  if (taskComplete) {
106
  console.log(`[${projectId}] ✅ TASK COMPLETE.`);
107
 
@@ -117,6 +209,7 @@ app.post('/project/feedback', async (req, res) => {
117
  return res.json({ success: true, message: "No further tasks. Project Idle." });
118
  }
119
 
 
120
  const newWorkerHistory = [];
121
  const newPrompt = `New Objective: ${nextInstruction}`;
122
  const workerResponse = await AIEngine.callWorker(newWorkerHistory, newPrompt, []);
@@ -130,13 +223,13 @@ app.post('/project/feedback', async (req, res) => {
130
  failureCount: 0
131
  });
132
 
133
- StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "warn('Starting Next Task')" }); // Reset console
134
  await processAndQueueResponse(projectId, workerResponse);
135
 
136
  return res.json({ success: true, message: "Next Task Assigned" });
137
  }
138
 
139
- // B. FAILURE DETECTION
140
  let isFailure = false;
141
  if (logContext?.logs) {
142
  const errs = ["Error", "Exception", "failed", "Stack Begin", "Infinite yield"];
@@ -146,7 +239,6 @@ app.post('/project/feedback', async (req, res) => {
146
  }
147
  }
148
 
149
- // C. ESCALATION
150
  if (project.failureCount > 3) {
151
  console.log(`[${projectId}] 🚨 Escalating to PM...`);
152
  const pmPrompt = sysPrompts.pm_guidance_prompt.replace('{{LOGS}}', logContext?.logs);
@@ -162,7 +254,9 @@ app.post('/project/feedback', async (req, res) => {
162
  resetHistory.push({ role: 'model', parts: [{ text: workerResp }] });
163
 
164
  await StateManager.updateProject(projectId, { workerHistory: resetHistory, failureCount: 0 });
 
165
  await processAndQueueResponse(projectId, workerResp);
 
166
  return res.json({ success: true, message: "Worker Terminated." });
167
  } else {
168
  const injection = `[PM GUIDANCE]: ${pmVerdict} \n\nApply this fix now.`;
@@ -173,29 +267,31 @@ app.post('/project/feedback', async (req, res) => {
173
 
174
  await StateManager.updateProject(projectId, { workerHistory: project.workerHistory, failureCount: 1 });
175
  await processAndQueueResponse(projectId, workerResp);
 
176
  return res.json({ success: true, message: "PM Guidance Applied." });
177
  }
178
  }
179
 
180
- // D. STANDARD WORKER FLOW
181
  try {
182
  const fullInput = `USER: ${prompt || "Automatic Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext });
183
 
184
  let response = await AIEngine.callWorker(project.workerHistory, fullInput, images || []);
185
 
186
- // CHECK: PM CONSULTATION
187
  const pmQuestion = extractPMQuestion(response);
188
  if (pmQuestion) {
189
  console.log(`[${projectId}] 🙋 Worker asking PM: "${pmQuestion}"`);
190
- const pmConsultPrompt = `[WORKER CONSULTATION]: The Worker asks: "${pmQuestion}"\nProvide a technical answer.`;
 
191
  const pmAnswer = await AIEngine.callPM(project.pmHistory, pmConsultPrompt);
192
 
 
193
  project.pmHistory.push({ role: 'user', parts: [{ text: pmConsultPrompt }] });
194
  project.pmHistory.push({ role: 'model', parts: [{ text: pmAnswer }] });
195
 
 
196
  const injectionMsg = `[PM RESPONSE]: ${pmAnswer}`;
197
-
198
- // Push question-answer to history
199
  project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
200
  project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
201
 
@@ -214,7 +310,6 @@ app.post('/project/feedback', async (req, res) => {
214
  failureCount: project.failureCount
215
  });
216
 
217
- // Process response for Code OR Images
218
  await processAndQueueResponse(projectId, response);
219
  res.json({ success: true });
220
 
@@ -225,7 +320,29 @@ app.post('/project/feedback', async (req, res) => {
225
  });
226
 
227
  /**
228
- * HUMAN OVERRIDE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  */
230
  app.post('/human/override', validateRequest, async (req, res) => {
231
  const { projectId, instruction, pruneHistory } = req.body;
@@ -247,52 +364,25 @@ app.post('/human/override', validateRequest, async (req, res) => {
247
  res.json({ success: true });
248
  });
249
 
250
- /**
251
- * PING (Plugin Polling)
252
- */
253
- app.post('/project/ping', async (req, res) => {
254
- const { projectId } = req.body;
255
- const command = await StateManager.popCommand(projectId);
256
-
257
- if (command) {
258
- if (command.payload === "CLEAR_CONSOLE") {
259
- res.json({ action: "CLEAR_LOGS" });
260
- } else {
261
- // Can be: EXECUTE, READ_SCRIPT, READ_HIERARCHY, CREATE_ASSET
262
- res.json({
263
- action: command.type,
264
- target: command.payload,
265
- code: command.type === 'EXECUTE' ? command.payload : null
266
- });
267
- }
268
- } else {
269
- res.json({ action: "IDLE" });
270
- }
271
- });
272
 
273
-
274
- // --- CORE LOGIC: PROCESS RESPONSE & HANDLE IMAGES ---
275
  async function processAndQueueResponse(projectId, rawResponse) {
276
- // 1. Check for Image Generation Tag
277
  const imgPrompt = extractImagePrompt(rawResponse);
278
  if (imgPrompt) {
279
- console.log(`[${projectId}] 🎨 Generating Image for: ${imgPrompt}`);
280
  const base64Image = await AIEngine.generateImage(imgPrompt);
281
 
282
  if (base64Image) {
283
- // Queue the creation command for the plugin
284
  await StateManager.queueCommand(projectId, {
285
  type: "CREATE_ASSET",
286
- payload: base64Image // Plugin receives Base64
287
  });
288
- console.log(`[${projectId}] Image Asset Queued.`);
289
- } else {
290
- console.error(`[${projectId}] Image Generation Failed.`);
291
  }
292
  }
293
 
294
- // 2. Queue standard commands (Code, Reads, etc.)
295
- // We pass the RAW response to StateManager to parse code blocks
296
  await StateManager.queueCommand(projectId, rawResponse);
297
  }
298
 
 
4
  import { StateManager } from './stateManager.js';
5
  import { AIEngine } from './aiEngine.js';
6
  import fs from 'fs';
7
+ import admin from 'firebase-admin';
8
+
9
+ // --- FIREBASE SETUP ---
10
+ // If you do not have 'serviceAccountKey.json', this will gracefully degrade to memory-only
11
+ let db = null;
12
+ try {
13
+ if (fs.existsSync('./serviceAccountKey.json')) {
14
+ const serviceAccount = JSON.parse(fs.readFileSync('./serviceAccountKey.json', 'utf8'));
15
+ admin.initializeApp({
16
+ credential: admin.credential.cert(serviceAccount),
17
+ databaseURL: "https://YOUR-FIREBASE-PROJECT.firebaseio.com" // REPLACE THIS
18
+ });
19
+ db = admin.database();
20
+ console.log("🔥 Firebase Connected");
21
+ } else {
22
+ console.warn("⚠️ No serviceAccountKey.json found. Running in Memory-Only mode.");
23
+ }
24
+ } catch (e) {
25
+ console.error("Firebase Init Error:", e);
26
+ }
27
 
28
  const app = express();
29
  const PORT = process.env.PORT || 7860;
30
 
31
+ // Load Prompts for Server-Side Logic Checks
32
  const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8'));
33
 
34
  app.use(cors());
35
  app.use(bodyParser.json({ limit: '50mb' }));
36
 
37
+ // --- HELPERS ---
38
  const validateRequest = (req, res, next) => {
39
+ // For Onboarding, IDs might be generated on the fly, so we skip rigid validation there
40
+ if (req.path.includes('/onboarding')) return next();
41
+
42
  const { userId, projectId } = req.body;
43
  if (!userId || !projectId) return res.status(400).json({ error: "Missing ID" });
44
  next();
45
  };
46
 
 
47
  function extractWorkerPrompt(text) {
48
  const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
49
  return match ? match[1].trim() : null;
 
66
  return match ? match[1].trim() : null;
67
  }
68
 
69
+ // --- ONBOARDING ENDPOINTS ---
70
+
71
+ /**
72
+ * 1. ANALYZE (User Idea -> Questions)
73
+ */
74
+ app.post('/onboarding/analyze', validateRequest, async (req, res) => {
75
+ const { description } = req.body;
76
+ if (!description) return res.status(400).json({ error: "Description required" });
77
+
78
+ try {
79
+ console.log(`[Onboarding] Analyzing idea...`);
80
+ const questions = await AIEngine.generateEntryQuestions(description);
81
+ res.json({ questions });
82
+ } catch (err) {
83
+ console.error(err);
84
+ res.status(500).json({ error: "Analysis failed" });
85
+ }
86
+ });
87
+
88
+ /**
89
+ * 2. CREATE (User Answers -> Grade + Thumbnail -> Save)
90
+ */
91
+ app.post('/onboarding/create', validateRequest, async (req, res) => {
92
+ const { userId, description, answers } = req.body;
93
+ const projectId = "proj_" + Date.now();
94
+
95
+ try {
96
+ console.log(`[Onboarding] Creating Project ${projectId}...`);
97
+
98
+ // Run Grading and Image Gen in Parallel
99
+ const [grading, thumbnailBase64] = await Promise.all([
100
+ AIEngine.gradeProject(description, answers),
101
+ AIEngine.generateImage(`Roblox game thumbnail, cinematic, high quality: ${description}`)
102
+ ]);
103
+
104
+ const projectData = {
105
+ id: projectId,
106
+ userId,
107
+ description,
108
+ answers,
109
+ stats: grading,
110
+ thumbnail: thumbnailBase64 ? `data:image/png;base64,${thumbnailBase64}` : null,
111
+ createdAt: Date.now(),
112
+ status: "initialized"
113
+ };
114
+
115
+ // Save to Firebase
116
+ if (db) {
117
+ await db.ref(`projects/${projectId}`).set(projectData);
118
+ }
119
+
120
+ // Also Init StateManager so it's ready for /new/project call later
121
+ await StateManager.updateProject(projectId, {
122
+ ...projectData,
123
+ workerHistory: [],
124
+ pmHistory: [],
125
+ failureCount: 0
126
+ });
127
+
128
+ res.json({ success: true, projectId, stats: grading, thumbnail: projectData.thumbnail });
129
+
130
+ } catch (err) {
131
+ console.error("Create Error:", err);
132
+ res.status(500).json({ error: "Creation failed" });
133
+ }
134
+ });
135
+
136
+ // --- CORE WORKFLOW ENDPOINTS ---
137
 
138
  /**
139
+ * 3. INITIALIZE WORKSPACE (PM Generates GDD -> First Task)
140
  */
141
  app.post('/new/project', validateRequest, async (req, res) => {
142
  const { userId, projectId, description } = req.body;
 
164
  workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
165
  workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
166
 
167
+ // Update State
168
  await StateManager.updateProject(projectId, {
169
  userId,
170
  pmHistory,
 
173
  failureCount: 0
174
  });
175
 
176
+ // Queue Actions
177
  await processAndQueueResponse(projectId, workerResponse);
178
 
179
+ res.json({ success: true, message: "Workspace Initialized", gddPreview: gddResponse.substring(0, 200) });
180
 
181
  } catch (err) {
182
+ console.error("Init Error:", err);
183
+ res.status(500).json({ error: "Initialization Failed" });
184
  }
185
  });
186
 
187
  /**
188
+ * 4. FEEDBACK LOOP (The "Brain")
189
  */
190
  app.post('/project/feedback', async (req, res) => {
191
  const { projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete, images } = req.body;
 
193
  const project = await StateManager.getProject(projectId);
194
  if (!project) return res.status(404).json({ error: "Project not found." });
195
 
196
+ // A. TASK COMPLETE -> SCOPE SWITCH
197
  if (taskComplete) {
198
  console.log(`[${projectId}] ✅ TASK COMPLETE.`);
199
 
 
209
  return res.json({ success: true, message: "No further tasks. Project Idle." });
210
  }
211
 
212
+ console.log(`[${projectId}] 🧹 Nuking Worker for next task.`);
213
  const newWorkerHistory = [];
214
  const newPrompt = `New Objective: ${nextInstruction}`;
215
  const workerResponse = await AIEngine.callWorker(newWorkerHistory, newPrompt, []);
 
223
  failureCount: 0
224
  });
225
 
226
+ StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "warn('Starting Next Task...')" });
227
  await processAndQueueResponse(projectId, workerResponse);
228
 
229
  return res.json({ success: true, message: "Next Task Assigned" });
230
  }
231
 
232
+ // B. ERROR ESCALATION
233
  let isFailure = false;
234
  if (logContext?.logs) {
235
  const errs = ["Error", "Exception", "failed", "Stack Begin", "Infinite yield"];
 
239
  }
240
  }
241
 
 
242
  if (project.failureCount > 3) {
243
  console.log(`[${projectId}] 🚨 Escalating to PM...`);
244
  const pmPrompt = sysPrompts.pm_guidance_prompt.replace('{{LOGS}}', logContext?.logs);
 
254
  resetHistory.push({ role: 'model', parts: [{ text: workerResp }] });
255
 
256
  await StateManager.updateProject(projectId, { workerHistory: resetHistory, failureCount: 0 });
257
+ StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "print('SYSTEM: Worker Reset')" });
258
  await processAndQueueResponse(projectId, workerResp);
259
+
260
  return res.json({ success: true, message: "Worker Terminated." });
261
  } else {
262
  const injection = `[PM GUIDANCE]: ${pmVerdict} \n\nApply this fix now.`;
 
267
 
268
  await StateManager.updateProject(projectId, { workerHistory: project.workerHistory, failureCount: 1 });
269
  await processAndQueueResponse(projectId, workerResp);
270
+
271
  return res.json({ success: true, message: "PM Guidance Applied." });
272
  }
273
  }
274
 
275
+ // C. STANDARD LOOP + CONSULTATION
276
  try {
277
  const fullInput = `USER: ${prompt || "Automatic Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext });
278
 
279
  let response = await AIEngine.callWorker(project.workerHistory, fullInput, images || []);
280
 
281
+ // CHECK: Consultant Question
282
  const pmQuestion = extractPMQuestion(response);
283
  if (pmQuestion) {
284
  console.log(`[${projectId}] 🙋 Worker asking PM: "${pmQuestion}"`);
285
+
286
+ const pmConsultPrompt = `[WORKER CONSULTATION]: The Worker asks: "${pmQuestion}"\nProvide a technical answer to unblock them.`;
287
  const pmAnswer = await AIEngine.callPM(project.pmHistory, pmConsultPrompt);
288
 
289
+ // Update PM Context
290
  project.pmHistory.push({ role: 'user', parts: [{ text: pmConsultPrompt }] });
291
  project.pmHistory.push({ role: 'model', parts: [{ text: pmAnswer }] });
292
 
293
+ // Feed Answer Back
294
  const injectionMsg = `[PM RESPONSE]: ${pmAnswer}`;
 
 
295
  project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
296
  project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
297
 
 
310
  failureCount: project.failureCount
311
  });
312
 
 
313
  await processAndQueueResponse(projectId, response);
314
  res.json({ success: true });
315
 
 
320
  });
321
 
322
  /**
323
+ * 5. PING (Plugin Polling)
324
+ */
325
+ app.post('/project/ping', async (req, res) => {
326
+ const { projectId } = req.body;
327
+ const command = await StateManager.popCommand(projectId);
328
+
329
+ if (command) {
330
+ if (command.payload === "CLEAR_CONSOLE") {
331
+ res.json({ action: "CLEAR_LOGS" });
332
+ } else {
333
+ res.json({
334
+ action: command.type,
335
+ target: command.payload,
336
+ code: command.type === 'EXECUTE' ? command.payload : null
337
+ });
338
+ }
339
+ } else {
340
+ res.json({ action: "IDLE" });
341
+ }
342
+ });
343
+
344
+ /**
345
+ * 6. MANUAL OVERRIDE
346
  */
347
  app.post('/human/override', validateRequest, async (req, res) => {
348
  const { projectId, instruction, pruneHistory } = req.body;
 
364
  res.json({ success: true });
365
  });
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
+ // --- RESPONSE PROCESSOR ---
 
369
  async function processAndQueueResponse(projectId, rawResponse) {
370
+ // 1. Intercept Image Requests
371
  const imgPrompt = extractImagePrompt(rawResponse);
372
  if (imgPrompt) {
373
+ console.log(`[${projectId}] 🎨 Generating Asset: ${imgPrompt}`);
374
  const base64Image = await AIEngine.generateImage(imgPrompt);
375
 
376
  if (base64Image) {
377
+ // Queue asset creation for Plugin
378
  await StateManager.queueCommand(projectId, {
379
  type: "CREATE_ASSET",
380
+ payload: base64Image
381
  });
 
 
 
382
  }
383
  }
384
 
385
+ // 2. Queue the Raw Response (StateManager parses code)
 
386
  await StateManager.queueCommand(projectId, rawResponse);
387
  }
388