everydaycats commited on
Commit
63870b6
·
verified ·
1 Parent(s): 77a0e4b

Create app.js

Browse files
Files changed (1) hide show
  1. app.js +368 -0
app.js ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import bodyParser from 'body-parser';
3
+ import cors from 'cors';
4
+ import { StateManager, initDB } from './stateManager.js'; // Import initDB
5
+ import { AIEngine } from './aiEngine.js';
6
+ import fs from 'fs';
7
+ import admin from 'firebase-admin';
8
+
9
+ // --- FIREBASE SETUP ---
10
+ let db = null;
11
+ try {
12
+ if (process.env.FIREBASE_SERVICE_ACCOUNT_JSON) {
13
+ const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON);
14
+ if (admin.apps.length === 0) {
15
+ admin.initializeApp({
16
+ credential: admin.credential.cert(serviceAccount),
17
+ databaseURL: "https://your-firebase-project.firebaseio.com" // Update URL if needed or pass via Env
18
+ });
19
+ }
20
+ db = admin.database();
21
+ // Pass DB instance to StateManager
22
+ initDB(db);
23
+ console.log("🔥 Firebase Connected & StateManager Linked");
24
+ } else {
25
+ console.warn("⚠️ Memory-Only mode.");
26
+ }
27
+ } catch (e) { console.error("Firebase Init Error:", e); }
28
+
29
+ const app = express();
30
+ const PORT = process.env.PORT || 7860;
31
+ const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8'));
32
+
33
+ app.use(cors());
34
+ app.use(bodyParser.json({ limit: '50mb' }));
35
+
36
+ const validateRequest = (req, res, next) => {
37
+ if (req.path.includes('/onboarding') || req.path.includes('/admin/cleanup')) return next();
38
+ const { userId, projectId } = req.body;
39
+ if (!userId || !projectId) return res.status(400).json({ error: "Missing ID" });
40
+ next();
41
+ };
42
+
43
+ function extractWorkerPrompt(text) {
44
+ const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
45
+ return match ? match[1].trim() : null;
46
+ }
47
+
48
+ function formatContext({ hierarchyContext, scriptContext, logContext }) {
49
+ let out = "";
50
+ if (scriptContext) out += `\n[TARGET SCRIPT]: ${scriptContext.targetName}\n[SOURCE PREVIEW]: ${scriptContext.scriptSource?.substring(0, 1000)}...`;
51
+ if (logContext) out += `\n[LAST LOGS]: ${logContext.logs}`;
52
+ return out;
53
+ }
54
+
55
+ function extractPMQuestion(text) {
56
+ const match = text.match(/\[ASK_PM:\s*(.*?)\]/s);
57
+ return match ? match[1].trim() : null;
58
+ }
59
+
60
+ function extractImagePrompt(text) {
61
+ const match = text.match(/\[GENERATE_IMAGE:\s*(.*?)\]/s);
62
+ return match ? match[1].trim() : null;
63
+ }
64
+
65
+ // --- ADMIN ENDPOINTS ---
66
+
67
+ /**
68
+ * CLEANUP MEMORY
69
+ * Call this via Cron Job or manually to free up RAM
70
+ */
71
+ app.post('/admin/cleanup', async (req, res) => {
72
+ try {
73
+ const removed = StateManager.cleanupMemory();
74
+ res.json({ success: true, removedCount: removed, message: `Cleaned ${removed} inactive projects from memory.` });
75
+ } catch (err) {
76
+ res.status(500).json({ error: "Cleanup failed" });
77
+ }
78
+ });
79
+
80
+ // --- ONBOARDING ENDPOINTS ---
81
+
82
+ app.post('/onboarding/analyze', validateRequest, async (req, res) => {
83
+ const { description } = req.body;
84
+ if (!description) return res.status(400).json({ error: "Description required" });
85
+
86
+ try {
87
+ console.log(`[Onboarding] Analyzing idea...`);
88
+ const result = await AIEngine.generateEntryQuestions(description);
89
+
90
+ if (result.status === "REJECTED") {
91
+ return res.json({
92
+ rejected: true,
93
+ reason: result.reason || "Idea violates TOS or guidelines."
94
+ });
95
+ }
96
+ res.json({ questions: result.questions });
97
+ } catch (err) {
98
+ console.error(err);
99
+ res.status(500).json({ error: "Analysis failed" });
100
+ }
101
+ });
102
+
103
+ app.post('/onboarding/create', validateRequest, async (req, res) => {
104
+ const { userId, description, answers } = req.body;
105
+ const projectId = "proj_" + Date.now();
106
+
107
+ try {
108
+ console.log(`[Onboarding] Grading Project ${projectId}...`);
109
+
110
+ const grading = await AIEngine.gradeProject(description, answers);
111
+ const isFailure = grading.feasibility < 30 || grading.rating === 'F';
112
+
113
+ let thumbnailBase64 = null;
114
+
115
+ if (isFailure) {
116
+ console.log(`[Onboarding] ❌ Project Failed Grading (${grading.rating}). Skipping Image.`);
117
+ } else {
118
+ console.log(`[Onboarding] ✅ Passed. Generating Thumbnail...`);
119
+ const imagePrompt = `Game Title: ${grading.title}. Core Concept: ${description}`;
120
+ thumbnailBase64 = await AIEngine.generateImage(imagePrompt);
121
+ }
122
+
123
+ const projectData = {
124
+ id: projectId,
125
+ userId,
126
+ title: grading.title || "Untitled Project",
127
+ description,
128
+ answers,
129
+ stats: grading,
130
+ thumbnail: thumbnailBase64,// ? `data:image/png;base64,${thumbnailBase64}` : null,
131
+ createdAt: Date.now(),
132
+ status: isFailure ? "rejected" : "initialized"
133
+ };
134
+
135
+ // Initialize in StateManager (handles Memory + DB write)
136
+ if (!isFailure) {
137
+ await StateManager.updateProject(projectId, {
138
+ ...projectData,
139
+ workerHistory: [],
140
+ pmHistory: [],
141
+ failureCount: 0
142
+ });
143
+ } else if (db) {
144
+ // If failed, just write to DB for records, don't load to active memory
145
+ await db.ref(`projects/${projectId}`).set(projectData);
146
+ }
147
+
148
+ res.json({
149
+ success: !isFailure,
150
+ projectId,
151
+ stats: grading,
152
+ title: projectData.title,
153
+ thumbnail: projectData.thumbnail
154
+ });
155
+
156
+ } catch (err) {
157
+ console.error("Create Error:", err);
158
+ res.status(500).json({ error: "Creation failed" });
159
+ }
160
+ });
161
+
162
+ // --- CORE ENDPOINTS ---
163
+
164
+ app.post('/new/project', validateRequest, async (req, res) => {
165
+ const { userId, projectId, description } = req.body;
166
+ try {
167
+ const pmHistory = [];
168
+ const gddPrompt = `Create a comprehensive GDD for: ${description}`;
169
+ const gddResponse = await AIEngine.callPM(pmHistory, gddPrompt);
170
+
171
+ pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] });
172
+ pmHistory.push({ role: 'model', parts: [{ text: gddResponse }] });
173
+
174
+ const taskPrompt = "Based on the GDD, generate the first technical milestone.\nOutput format:\nTASK_NAME: <Name>\nWORKER_PROMPT: <Specific, isolated instructions for the worker>";
175
+ const taskResponse = await AIEngine.callPM(pmHistory, taskPrompt);
176
+
177
+ pmHistory.push({ role: 'user', parts: [{ text: taskPrompt }] });
178
+ pmHistory.push({ role: 'model', parts: [{ text: taskResponse }] });
179
+
180
+ const initialWorkerInstruction = extractWorkerPrompt(taskResponse) || `Initialize structure for: ${description}`;
181
+
182
+ const workerHistory = [];
183
+ const initialWorkerPrompt = `CONTEXT: New Project. \nINSTRUCTION: ${initialWorkerInstruction}`;
184
+ const workerResponse = await AIEngine.callWorker(workerHistory, initialWorkerPrompt, []);
185
+
186
+ workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
187
+ workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
188
+
189
+ await StateManager.updateProject(projectId, {
190
+ userId,
191
+ pmHistory,
192
+ workerHistory,
193
+ gdd: gddResponse,
194
+ failureCount: 0
195
+ });
196
+
197
+ await processAndQueueResponse(projectId, workerResponse);
198
+ res.json({ success: true, message: "Workspace Initialized", gddPreview: gddResponse.substring(0, 200) });
199
+ } catch (err) {
200
+ console.error("Init Error:", err);
201
+ res.status(500).json({ error: "Initialization Failed" });
202
+ }
203
+ });
204
+
205
+ app.post('/project/feedback', async (req, res) => {
206
+ const { projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete, images } = req.body;
207
+
208
+ // This now handles Hydration automatically
209
+ const project = await StateManager.getProject(projectId);
210
+
211
+ if (!project) return res.status(404).json({ error: "Project not found." });
212
+
213
+ if (taskComplete) {
214
+ console.log(`[${projectId}] ✅ TASK COMPLETE.`);
215
+ const summary = `Worker completed the previous task. Logs: ${logContext?.logs || "Clean"}. \nGenerate the NEXT task using 'WORKER_PROMPT:' format.`;
216
+ const pmResponse = await AIEngine.callPM(project.pmHistory, summary);
217
+
218
+ project.pmHistory.push({ role: 'user', parts: [{ text: summary }] });
219
+ project.pmHistory.push({ role: 'model', parts: [{ text: pmResponse }] });
220
+
221
+ const nextInstruction = extractWorkerPrompt(pmResponse);
222
+ if (!nextInstruction) {
223
+ await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, status: "IDLE" });
224
+ return res.json({ success: true, message: "No further tasks. Project Idle." });
225
+ }
226
+
227
+ const newWorkerHistory = [];
228
+ const newPrompt = `New Objective: ${nextInstruction}`;
229
+ const workerResponse = await AIEngine.callWorker(newWorkerHistory, newPrompt, []);
230
+
231
+ newWorkerHistory.push({ role: 'user', parts: [{ text: newPrompt }] });
232
+ newWorkerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
233
+
234
+ await StateManager.updateProject(projectId, {
235
+ pmHistory: project.pmHistory,
236
+ workerHistory: newWorkerHistory,
237
+ failureCount: 0
238
+ });
239
+
240
+ StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "warn('Starting Next Task...')" });
241
+ await processAndQueueResponse(projectId, workerResponse);
242
+ return res.json({ success: true, message: "Next Task Assigned" });
243
+ }
244
+
245
+ let isFailure = false;
246
+ if (logContext?.logs) {
247
+ const errs = ["Error", "Exception", "failed", "Stack Begin", "Infinite yield"];
248
+ if (errs.some(k => logContext.logs.includes(k))) {
249
+ isFailure = true;
250
+ project.failureCount = (project.failureCount || 0) + 1;
251
+ }
252
+ }
253
+
254
+ if (project.failureCount > 3) {
255
+ const pmPrompt = sysPrompts.pm_guidance_prompt.replace('{{LOGS}}', logContext?.logs);
256
+ const pmVerdict = await AIEngine.callPM(project.pmHistory, pmPrompt);
257
+
258
+ if (pmVerdict.includes("[TERMINATE]")) {
259
+ const fixInstruction = pmVerdict.replace("[TERMINATE]", "").trim();
260
+ const resetHistory = [];
261
+ const resetPrompt = `[SYSTEM]: Previous worker terminated. \nNew Objective: ${fixInstruction}`;
262
+ const workerResp = await AIEngine.callWorker(resetHistory, resetPrompt, []);
263
+
264
+ resetHistory.push({ role: 'user', parts: [{ text: resetPrompt }] });
265
+ resetHistory.push({ role: 'model', parts: [{ text: workerResp }] });
266
+
267
+ await StateManager.updateProject(projectId, { workerHistory: resetHistory, failureCount: 0 });
268
+ StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "print('SYSTEM: Worker Reset')" });
269
+ await processAndQueueResponse(projectId, workerResp);
270
+ return res.json({ success: true, message: "Worker Terminated." });
271
+ } else {
272
+ const injection = `[PM GUIDANCE]: ${pmVerdict} \n\nApply this fix now.`;
273
+ const workerResp = await AIEngine.callWorker(project.workerHistory, injection, []);
274
+
275
+ project.workerHistory.push({ role: 'user', parts: [{ text: injection }] });
276
+ project.workerHistory.push({ role: 'model', parts: [{ text: workerResp }] });
277
+
278
+ await StateManager.updateProject(projectId, { workerHistory: project.workerHistory, failureCount: 1 });
279
+ await processAndQueueResponse(projectId, workerResp);
280
+ return res.json({ success: true, message: "PM Guidance Applied." });
281
+ }
282
+ }
283
+
284
+ try {
285
+ const fullInput = `USER: ${prompt || "Automatic Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext });
286
+ let response = await AIEngine.callWorker(project.workerHistory, fullInput, images || []);
287
+
288
+ const pmQuestion = extractPMQuestion(response);
289
+ if (pmQuestion) {
290
+ const pmConsultPrompt = `[WORKER CONSULTATION]: The Worker asks: "${pmQuestion}"\nProvide a technical answer to unblock them.`;
291
+ const pmAnswer = await AIEngine.callPM(project.pmHistory, pmConsultPrompt);
292
+
293
+ project.pmHistory.push({ role: 'user', parts: [{ text: pmConsultPrompt }] });
294
+ project.pmHistory.push({ role: 'model', parts: [{ text: pmAnswer }] });
295
+
296
+ const injectionMsg = `[PM RESPONSE]: ${pmAnswer}`;
297
+ project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
298
+ project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
299
+
300
+ response = await AIEngine.callWorker(project.workerHistory, injectionMsg, []);
301
+ project.workerHistory.push({ role: 'user', parts: [{ text: injectionMsg }] });
302
+ project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
303
+ } else {
304
+ project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
305
+ project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
306
+ }
307
+
308
+ await StateManager.updateProject(projectId, {
309
+ workerHistory: project.workerHistory,
310
+ pmHistory: project.pmHistory,
311
+ failureCount: project.failureCount
312
+ });
313
+
314
+ await processAndQueueResponse(projectId, response);
315
+ res.json({ success: true });
316
+ } catch (err) {
317
+ console.error("AI Error:", err);
318
+ res.status(500).json({ error: "AI Failed" });
319
+ }
320
+ });
321
+
322
+ app.post('/project/ping', async (req, res) => {
323
+ const { projectId } = req.body;
324
+ // This will hydrate from DB if missing
325
+ const command = await StateManager.popCommand(projectId);
326
+
327
+ if (command) {
328
+ if (command.payload === "CLEAR_CONSOLE") {
329
+ res.json({ action: "CLEAR_LOGS" });
330
+ } else {
331
+ res.json({ action: command.type, target: command.payload, code: command.type === 'EXECUTE' ? command.payload : null });
332
+ }
333
+ } else {
334
+ res.json({ action: "IDLE" });
335
+ }
336
+ });
337
+
338
+ app.post('/human/override', validateRequest, async (req, res) => {
339
+ const { projectId, instruction, pruneHistory } = req.body;
340
+ const project = await StateManager.getProject(projectId);
341
+ const overrideMsg = `[SYSTEM OVERRIDE]: ${instruction}`;
342
+ if (pruneHistory && project.workerHistory.length >= 2) {
343
+ project.workerHistory.pop();
344
+ project.workerHistory.pop();
345
+ }
346
+ const response = await AIEngine.callWorker(project.workerHistory, overrideMsg, []);
347
+ project.workerHistory.push({ role: 'user', parts: [{ text: overrideMsg }] });
348
+ project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
349
+ await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
350
+ await processAndQueueResponse(projectId, response);
351
+ res.json({ success: true });
352
+ });
353
+
354
+ async function processAndQueueResponse(projectId, rawResponse) {
355
+ const imgPrompt = extractImagePrompt(rawResponse);
356
+ if (imgPrompt) {
357
+ console.log(`[${projectId}] 🎨 Generating Asset: ${imgPrompt}`);
358
+ const base64Image = await AIEngine.generateImage(imgPrompt);
359
+ if (base64Image) {
360
+ await StateManager.queueCommand(projectId, { type: "CREATE_ASSET", payload: base64Image });
361
+ }
362
+ }
363
+ await StateManager.queueCommand(projectId, rawResponse);
364
+ }
365
+
366
+ app.listen(PORT, () => {
367
+ console.log(`AI Backend Running on ${PORT}`);
368
+ });