everydaycats commited on
Commit
c9f2107
·
verified ·
1 Parent(s): 69ede17

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +216 -83
app.js CHANGED
@@ -1,100 +1,233 @@
1
  // server.js
2
- import express from "express";
3
- import bodyParser from "body-parser";
4
- import cors from "cors";
5
- import path from "path";
6
- import { fileURLToPath } from "url";
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- const PORT = process.env.PORT || 7860;
11
 
12
  const app = express();
 
 
 
 
 
13
  app.use(cors());
14
- app.use(bodyParser.json({ limit: "50mb" }));
15
-
16
- // === TEST MODES ===
17
- // 'BUILD_PART' -> Makes a part
18
- // 'FETCH_SCRIPT' -> Finds a script by name anywhere
19
- // 'SCAN_SERVER_STORAGE' -> Scans "ServerStorage" explicitly
20
- // 'SCAN_SELECTION' -> Scans whatever you clicked on in Roblox
21
- // 'FETCH_LOGS' -> Fetches the recent Output logs
22
- const DEMO_MODE = 'FETCH_LOGS';
23
-
24
- app.post("/api/ai-build", async (req, res) => {
25
- try {
26
- const { instruction, selection, scriptContext, logContext, hierarchyContext } = req.body;
27
-
28
- console.log("------------------------------------------------");
29
- console.log("📥 INSTRUCTION:", instruction);
30
-
31
- // 1. Script Found Response
32
- if (scriptContext) {
33
- console.log(`📘 SCRIPT FOUND [${scriptContext.targetName}]:`);
34
- console.log(` Parent: ${scriptContext.parentName}`);
35
- console.log(scriptContext.scriptSource.substring(0, 150) + "...\n");
36
- return res.json({ success: true, message: `Read ${scriptContext.targetName} successfully.` });
37
- }
38
 
39
- // 2. Hierarchy Scanned Response
40
- if (hierarchyContext) {
41
- console.log(`🌳 SCANNED HIERARCHY [${hierarchyContext.rootName}]:`);
42
- const treePreview = hierarchyContext.tree.split('\n').slice(0, 20).join('\n');
43
- console.log(treePreview);
44
- return res.json({ success: true, message: `Scanned ${hierarchyContext.rootName}.` });
45
  }
 
 
46
 
47
- // 3. Logs Received Response (RESTORED)
48
- if (logContext) {
49
- console.log("📝 LOGS RECEIVED:");
50
- console.log("------------------------------------------------");
51
- // Print last 500 chars to avoid flooding terminal
52
- const logPreview = logContext.logs.length > 500
53
- ? "..." + logContext.logs.substring(logContext.logs.length - 500)
54
- : logContext.logs;
55
- console.log(logPreview);
56
- console.log("------------------------------------------------");
57
- return res.json({ success: true, message: "Logs received and analyzed." });
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- // 4. Demo Triggers
61
- if (DEMO_MODE === 'BUILD_PART') {
62
- return res.send(`\`\`\`lua
63
- local p = Instance.new("Part", workspace)
64
- p.Position = Vector3.new(0,20,0)
65
- p.Anchored = true
66
- print("Created Part")
67
- \`\`\``);
68
- }
69
- else if (DEMO_MODE === 'FETCH_SCRIPT') {
70
- console.log(" 👉 Asking to find 'BikeLogic'...");
71
- return res.json({
72
- action: "read_script",
73
- targetName: "BikeLogic"
74
  });
 
 
 
 
 
 
 
 
 
75
  }
76
- else if (DEMO_MODE === 'SCAN_SERVER_STORAGE') {
77
- console.log(" 👉 Asking to scan ServerStorage...");
78
- return res.json({
79
- action: "read_hierarchy",
80
- targetName: "ServerStorage"
81
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
- else if (DEMO_MODE === 'SCAN_SELECTION') {
84
- console.log(" 👉 Asking to scan selection...");
85
- return res.json({ action: "read_hierarchy" });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
- else if (DEMO_MODE === 'FETCH_LOGS') {
88
- console.log(" 👉 Asking to fetch Console Logs...");
89
- return res.json({ action: "read_logs" });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
 
92
- return res.json({ success: true, message: "Thinking..." });
 
 
 
 
 
 
 
 
 
 
93
 
94
- } catch (err) {
95
- console.error("Server Error:", err);
96
- res.status(500).send("Server Error: " + err.message);
97
- }
 
 
98
  });
99
 
100
- app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}. Mode: ${DEMO_MODE}`));
 
 
 
1
  // server.js
2
+ import express from 'express';
3
+ import bodyParser from 'body-parser';
4
+ import cors from 'cors';
5
+ import { StateManager } from './stateManager.js';
6
+ import { AIEngine } from './aiEngine.js';
7
+ import fs from 'fs';
 
 
 
8
 
9
  const app = express();
10
+ const PORT = process.env.PORT || 3000;
11
+
12
+ // Load prompts for dynamic usage
13
+ const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8'));
14
+
15
  app.use(cors());
16
+ app.use(bodyParser.json({ limit: '50mb' })); // Increased limit for images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ // Middleware: Validate User/Project IDs
19
+ const validateRequest = (req, res, next) => {
20
+ const { userId, projectId } = req.body;
21
+ if (!userId || !projectId) {
22
+ return res.status(400).json({ error: "Missing userId or projectId" });
 
23
  }
24
+ next();
25
+ };
26
 
27
+ /**
28
+ * 1. NEW PROJECT
29
+ * Flow: PM (GDD) -> PM (Tasks) -> Worker (Task 1)
30
+ */
31
+ app.post('/new/project', validateRequest, async (req, res) => {
32
+ const { userId, projectId, description } = req.body;
33
+
34
+ try {
35
+ // 1. Generate GDD with PM (High Thinking)
36
+ const pmHistory = [];
37
+ const gddPrompt = `Create a comprehensive GDD for: ${description}`;
38
+ const gddResponse = await AIEngine.callPM(pmHistory, gddPrompt);
39
+
40
+ pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] });
41
+ pmHistory.push({ role: 'model', parts: [{ text: gddResponse }] });
42
+
43
+ // 2. Generate Initial Tasks
44
+ const taskPrompt = "Based on the GDD, generate a prioritized list of initial technical tasks. Output them as a JSON list.";
45
+ const taskResponse = await AIEngine.callPM(pmHistory, taskPrompt);
46
+
47
+ pmHistory.push({ role: 'user', parts: [{ text: taskPrompt }] });
48
+ pmHistory.push({ role: 'model', parts: [{ text: taskResponse }] });
49
+
50
+ // 3. Initialize Worker with GDD Context and Execute First Task
51
+ const workerHistory = [];
52
+ const initialWorkerPrompt = `Here is the GDD: ${gddResponse}. \n\n Here are the tasks: ${taskResponse}. \n\n EXECUTE THE FIRST TASK NOW.`;
53
+ const workerResponse = await AIEngine.callWorker(workerHistory, initialWorkerPrompt);
54
 
55
+ workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
56
+ workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
57
+
58
+ // Save State
59
+ await StateManager.updateProject(projectId, {
60
+ userId,
61
+ pmHistory,
62
+ workerHistory,
63
+ gdd: gddResponse,
64
+ failureCount: 0
 
 
 
 
65
  });
66
+
67
+ // Queue the result for the plugin
68
+ StateManager.queueCommand(projectId, workerResponse);
69
+
70
+ res.json({ success: true, message: "Project initialized", gddPreview: gddResponse.substring(0, 200) });
71
+
72
+ } catch (err) {
73
+ console.error(err);
74
+ res.status(500).json({ error: "AI Processing Failed" });
75
  }
76
+ });
77
+
78
+ /**
79
+ * 2. FEEDBACK (The Main Loop Endpoint)
80
+ * Accepts text + optional image. Hits Worker. Checks for escalation.
81
+ */
82
+ app.post('/project/feedback', validateRequest, async (req, res) => {
83
+ const { projectId, prompt, imageBase64 } = req.body;
84
+
85
+ try {
86
+ const project = await StateManager.getProject(projectId);
87
+ if (!project) return res.status(404).json({ error: "Project not found" });
88
+
89
+ let imagePart = null;
90
+ if (imageBase64) {
91
+ imagePart = { inlineData: { mimeType: 'image/png', data: imageBase64 } };
92
+ }
93
+
94
+ // Call Worker
95
+ const response = await AIEngine.callWorker(project.workerHistory, prompt, imagePart);
96
+
97
+ // Update History
98
+ project.workerHistory.push({ role: 'user', parts: [{ text: prompt }] });
99
+ project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
100
+
101
+ // Check for Escalation Condition (Keyword or Failure Count)
102
+ if (response.includes("STATUS: ESCALATE_TO_PM") || project.failureCount >= 2) {
103
+ console.log(`Project ${projectId} escalating to PM.`);
104
+
105
+ // Auto-trigger internal PM ping
106
+ // We reuse the logic from /internal/ping/pm here for efficiency
107
+ const distressSignal = `Worker failed to solve: ${prompt}. Last response: ${response}`;
108
+ const pmGuidance = await AIEngine.callPM(
109
+ project.pmHistory,
110
+ sysPrompts.pm_guidance_prompt.replace('{{DISTRESS_SIGNAL}}', distressSignal)
111
+ );
112
+
113
+ // Send guidance back to worker
114
+ const fixedWorkerResponse = await AIEngine.callWorker(project.workerHistory, `PM GUIDANCE: ${pmGuidance}. Try again.`);
115
+
116
+ project.failureCount = 0; // Reset failure count
117
+ StateManager.queueCommand(projectId, fixedWorkerResponse);
118
+ await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, workerHistory: project.workerHistory, failureCount: 0 });
119
+
120
+ return res.json({ success: true, message: "Escalated to PM and Resolved", type: "ESCALATION" });
121
+ } else {
122
+ // Standard Success
123
+ StateManager.queueCommand(projectId, response);
124
+ await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
125
+ return res.json({ success: true, message: "Processed by Worker" });
126
+ }
127
+
128
+ } catch (err) {
129
+ const project = await StateManager.getProject(projectId);
130
+ if (project) {
131
+ await StateManager.updateProject(projectId, { failureCount: (project.failureCount || 0) + 1 });
132
+ }
133
+ res.status(500).json({ error: err.message });
134
  }
135
+ });
136
+
137
+ /**
138
+ * 3. INTERNAL PING WORKER
139
+ * PM sends guidance to Worker.
140
+ */
141
+ app.post('/internal/ping/worker', validateRequest, async (req, res) => {
142
+ const { projectId, guidancePrompt } = req.body;
143
+ const project = await StateManager.getProject(projectId);
144
+ if (!project) return res.status(404).json({ error: "Project not found" });
145
+
146
+ // Inject PM Context (GDD/History) into the message if requested
147
+ const fullContextPrompt = `PM CONTEXT: ${JSON.stringify(project.pmHistory)}. \n GUIDANCE: ${guidancePrompt}`;
148
+
149
+ const response = await AIEngine.callWorker(project.workerHistory, fullContextPrompt);
150
+ StateManager.queueCommand(projectId, response);
151
+
152
+ res.json({ success: true, message: "Guidance sent to Worker" });
153
+ });
154
+
155
+ /**
156
+ * 4. INTERNAL PING PM
157
+ * Worker signals distress -> PM responds.
158
+ */
159
+ app.post('/internal/ping/pm', validateRequest, async (req, res) => {
160
+ const { projectId, distressMessage } = req.body;
161
+ const project = await StateManager.getProject(projectId);
162
+
163
+ // PM analyzes distress
164
+ const pmResponse = await AIEngine.callPM(project.pmHistory, `Worker Distress: ${distressMessage}. Provide solution.`);
165
+
166
+ // Logic: Decide to reset worker OR guide worker
167
+ if (pmResponse.includes("RESET_RECOMMENDED")) {
168
+ // Call reset endpoint logic (internal function call)
169
+ project.workerHistory = [{ role: 'user', parts: [{ text: sysPrompts.worker_reset_prompt.replace('{{INSTRUCTION}}', pmResponse) }] }];
170
+ await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
171
+ res.json({ action: "RESET", guidance: pmResponse });
172
+ } else {
173
+ // Just guide
174
+ const workerResp = await AIEngine.callWorker(project.workerHistory, `PM FIX: ${pmResponse}`);
175
+ StateManager.queueCommand(projectId, workerResp);
176
+ res.json({ action: "GUIDANCE", guidance: pmResponse });
177
  }
178
+ });
179
+
180
+ /**
181
+ * 5. PROJECT RESET
182
+ * Resets the worker thread completely.
183
+ */
184
+ app.post('/project/reset', validateRequest, async (req, res) => {
185
+ const { projectId } = req.body;
186
+ const project = await StateManager.getProject(projectId);
187
+
188
+ // Clear history, keep GDD context
189
+ const resetPrompt = `System reset. Current GDD: ${project.gdd}. Await instructions.`;
190
+
191
+ await StateManager.updateProject(projectId, {
192
+ workerHistory: [{ role: 'user', parts: [{ text: resetPrompt }] }],
193
+ failureCount: 0
194
+ });
195
+
196
+ res.json({ success: true, message: "Worker thread reset" });
197
+ });
198
+
199
+ /**
200
+ * 6. PROJECT PING (The Roblox Plugin Endpoint)
201
+ * Plugin calls this to fetch executable code.
202
+ */
203
+ app.post('/project/ping', validateRequest, async (req, res) => {
204
+ const { projectId } = req.body;
205
+ const project = await StateManager.getProject(projectId);
206
+
207
+ if (!project || !project.commandQueue || project.commandQueue.length === 0) {
208
+ return res.json({ status: "IDLE" });
209
  }
210
 
211
+ // Pop the oldest command
212
+ const command = project.commandQueue.shift();
213
+ await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
214
+
215
+ // Return the whole response, but specifically parsed code for execution
216
+ res.json({
217
+ status: "EXECUTE",
218
+ fullResponse: command.raw,
219
+ codeSnippet: command.code // The markdown snippet extracted in StateManager
220
+ });
221
+ });
222
 
223
+ /**
224
+ * 7. INACTIVITY CLEANUP
225
+ */
226
+ app.delete('/internal/cleanup', async (req, res) => {
227
+ const count = StateManager.cleanupInactivity();
228
+ res.json({ success: true, removed: count });
229
  });
230
 
231
+ app.listen(PORT, () => {
232
+ console.log(`AI Builder Backend running on port ${PORT}`);
233
+ });