Spaces:
Sleeping
Sleeping
Update app.js
Browse files
app.js
CHANGED
|
@@ -17,7 +17,6 @@ try {
|
|
| 17 |
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON);
|
| 18 |
|
| 19 |
// We assume the project ID is available in the service account to construct the bucket name
|
| 20 |
-
// Or you can hardcode your bucket: "your-project-id.firebasestorage.app"
|
| 21 |
const bucketName = `shago-web.firebasestorage.app`;
|
| 22 |
|
| 23 |
if (admin.apps.length === 0) {
|
|
@@ -119,7 +118,6 @@ app.post('/onboarding/analyze', validateRequest, async (req, res) => {
|
|
| 119 |
app.post('/onboarding/create', validateRequest, async (req, res) => {
|
| 120 |
const { userId, description, answers } = req.body;
|
| 121 |
|
| 122 |
-
// helper: random hex of N chars (N can be 6 or 7)
|
| 123 |
const randomHex = (n = 6) => {
|
| 124 |
const bytes = Math.ceil(n / 2);
|
| 125 |
return crypto.randomBytes(bytes).toString("hex").slice(0, n);
|
|
@@ -144,12 +142,10 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 144 |
thumbnailBase64 = await AIEngine.generateImage(imagePrompt);
|
| 145 |
}
|
| 146 |
|
| 147 |
-
// --- UPLOAD TO STORAGE & PREPARE URL ---
|
| 148 |
let thumbnailUrl = null;
|
| 149 |
if (thumbnailBase64 && storage) {
|
| 150 |
try {
|
| 151 |
console.log(`[Onboarding] 📤 Uploading Thumbnail to Storage...`);
|
| 152 |
-
// Remove header if present to get pure buffer
|
| 153 |
const base64Data = thumbnailBase64.replace(/^data:image\/\w+;base64,/, "");
|
| 154 |
const buffer = Buffer.from(base64Data, 'base64');
|
| 155 |
const bucket = storage.bucket();
|
|
@@ -159,22 +155,17 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 159 |
metadata: { contentType: 'image/png' }
|
| 160 |
});
|
| 161 |
|
| 162 |
-
// Make public and get URL
|
| 163 |
await file.makePublic();
|
| 164 |
-
// Construct the public URL manually or use file.publicUrl()
|
| 165 |
thumbnailUrl = `https://storage.googleapis.com/${bucket.name}/${projectId}/thumbnail.png`;
|
| 166 |
console.log(`[Onboarding] 🖼️ Thumbnail URL: ${thumbnailUrl}`);
|
| 167 |
} catch (uploadErr) {
|
| 168 |
console.error("Storage Upload Failed:", uploadErr);
|
| 169 |
-
// Fallback: thumbnailUrl remains null, logic continues without crashing
|
| 170 |
}
|
| 171 |
}
|
| 172 |
|
| 173 |
const timestamp = Date.now();
|
| 174 |
const status = isFailure ? "rejected" : "Idle";
|
| 175 |
|
| 176 |
-
// 1. Prepare Memory Object
|
| 177 |
-
// Note: storing URL instead of raw Base64 now
|
| 178 |
const memoryObject = {
|
| 179 |
id: projectId,
|
| 180 |
userId,
|
|
@@ -191,7 +182,6 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 191 |
failureCount: 0
|
| 192 |
};
|
| 193 |
|
| 194 |
-
// 2. FIRESTORE WRITE (Project Indexing)
|
| 195 |
if (firestore && !isFailure) {
|
| 196 |
try {
|
| 197 |
await firestore.collection('projects').doc(projectId).set({
|
|
@@ -206,11 +196,8 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 206 |
}
|
| 207 |
}
|
| 208 |
|
| 209 |
-
// 3. REALTIME DB WRITE (Deep State)
|
| 210 |
if (db && !isFailure) {
|
| 211 |
const updates = {};
|
| 212 |
-
|
| 213 |
-
// Bucket 1: Info (Lightweight)
|
| 214 |
updates[`projects/${projectId}/info`] = {
|
| 215 |
id: projectId,
|
| 216 |
userId,
|
|
@@ -222,12 +209,10 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 222 |
status
|
| 223 |
};
|
| 224 |
|
| 225 |
-
// Bucket 2: Thumbnail (Store URL object, not Blob)
|
| 226 |
if (thumbnailUrl) {
|
| 227 |
updates[`projects/${projectId}/thumbnail`] = { url: thumbnailUrl };
|
| 228 |
}
|
| 229 |
|
| 230 |
-
// Bucket 3: State (History)
|
| 231 |
updates[`projects/${projectId}/state`] = {
|
| 232 |
workerHistory: [],
|
| 233 |
pmHistory: [],
|
|
@@ -238,7 +223,6 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 238 |
await db.ref().update(updates);
|
| 239 |
}
|
| 240 |
|
| 241 |
-
// 4. Update Memory
|
| 242 |
if (!isFailure) {
|
| 243 |
await StateManager.updateProject(projectId, memoryObject);
|
| 244 |
}
|
|
@@ -248,7 +232,7 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 248 |
projectId,
|
| 249 |
stats: grading,
|
| 250 |
title: grading.title || "Untitled Project",
|
| 251 |
-
thumbnail: thumbnailBase64
|
| 252 |
});
|
| 253 |
|
| 254 |
} catch (err) {
|
|
@@ -259,26 +243,28 @@ app.post('/onboarding/create', validateRequest, async (req, res) => {
|
|
| 259 |
|
| 260 |
// --- CORE ENDPOINTS ---
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
|
|
|
| 264 |
try {
|
| 265 |
const pmHistory = [];
|
| 266 |
-
|
| 267 |
-
|
| 268 |
const gddPrompt = `Create a comprehensive GDD for: ${description}`;
|
| 269 |
const gddResponse = await AIEngine.callPM(pmHistory, gddPrompt);
|
| 270 |
|
| 271 |
pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] });
|
| 272 |
pmHistory.push({ role: 'model', parts: [{ text: gddResponse }] });
|
| 273 |
|
|
|
|
| 274 |
const taskPrompt = "Based on the GDD, generate the first technical milestone.\nOutput format:\nTASK_NAME: <Name>\nWORKER_PROMPT: <Specific, isolated instructions for the worker>";
|
| 275 |
const taskResponse = await AIEngine.callPM(pmHistory, taskPrompt);
|
| 276 |
|
| 277 |
pmHistory.push({ role: 'user', parts: [{ text: taskPrompt }] });
|
| 278 |
pmHistory.push({ role: 'model', parts: [{ text: taskResponse }] });
|
| 279 |
|
|
|
|
| 280 |
const initialWorkerInstruction = extractWorkerPrompt(taskResponse) || `Initialize structure for: ${description}`;
|
| 281 |
-
|
| 282 |
const workerHistory = [];
|
| 283 |
const initialWorkerPrompt = `CONTEXT: New Project. \nINSTRUCTION: ${initialWorkerInstruction}`;
|
| 284 |
const workerResponse = await AIEngine.callWorker(workerHistory, initialWorkerPrompt, []);
|
|
@@ -286,6 +272,7 @@ app.post('/new/project', validateRequest, async (req, res) => {
|
|
| 286 |
workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
|
| 287 |
workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
|
| 288 |
|
|
|
|
| 289 |
await StateManager.updateProject(projectId, {
|
| 290 |
userId,
|
| 291 |
pmHistory,
|
|
@@ -295,28 +282,48 @@ app.post('/new/project', validateRequest, async (req, res) => {
|
|
| 295 |
});
|
| 296 |
|
| 297 |
await processAndQueueResponse(projectId, workerResponse);
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
| 301 |
} catch (err) {
|
| 302 |
-
console.error(
|
| 303 |
-
|
| 304 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
});
|
| 306 |
|
| 307 |
app.post('/project/feedback', async (req, res) => {
|
| 308 |
const { userId, projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete, images } = req.body;
|
| 309 |
|
| 310 |
-
// This now handles Hydration automatically
|
| 311 |
const project = await StateManager.getProject(projectId);
|
| 312 |
|
| 313 |
if (!project) return res.status(404).json({ error: "Project not found." });
|
| 314 |
|
| 315 |
-
|
| 316 |
console.warn(`[Security] Unauthorized ping for ${projectId}. Owner: ${project.userId}, Request: ${userId}`);
|
| 317 |
return res.status(403).json({ error: "Unauthorized: You do not own this project." });
|
| 318 |
-
|
| 319 |
-
|
|
|
|
|
|
|
| 320 |
|
| 321 |
if (taskComplete) {
|
| 322 |
console.log(`[${projectId}] ✅ TASK COMPLETE.`);
|
|
@@ -329,6 +336,7 @@ app.post('/project/feedback', async (req, res) => {
|
|
| 329 |
const nextInstruction = extractWorkerPrompt(pmResponse);
|
| 330 |
if (!nextInstruction) {
|
| 331 |
await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, status: "IDLE" });
|
|
|
|
| 332 |
return res.json({ success: true, message: "No further tasks. Project Idle." });
|
| 333 |
}
|
| 334 |
|
|
@@ -347,6 +355,7 @@ app.post('/project/feedback', async (req, res) => {
|
|
| 347 |
|
| 348 |
StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "warn('Starting Next Task...')" });
|
| 349 |
await processAndQueueResponse(projectId, workerResponse);
|
|
|
|
| 350 |
return res.json({ success: true, message: "Next Task Assigned" });
|
| 351 |
}
|
| 352 |
|
|
@@ -420,9 +429,13 @@ app.post('/project/feedback', async (req, res) => {
|
|
| 420 |
});
|
| 421 |
|
| 422 |
await processAndQueueResponse(projectId, response);
|
| 423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
|
| 425 |
-
|
| 426 |
} catch (err) {
|
| 427 |
console.error("AI Error:", err);
|
| 428 |
res.status(500).json({ error: "AI Failed" });
|
|
@@ -430,31 +443,23 @@ app.post('/project/feedback', async (req, res) => {
|
|
| 430 |
});
|
| 431 |
|
| 432 |
app.post('/project/ping', async (req, res) => {
|
| 433 |
-
// 1. Accept userId along with projectId
|
| 434 |
const { projectId, userId } = req.body;
|
| 435 |
-
// console.log("1");
|
| 436 |
|
| 437 |
if (!projectId || !userId) {
|
| 438 |
return res.status(400).json({ error: "Missing ID fields" });
|
| 439 |
}
|
| 440 |
-
|
| 441 |
-
// 2. Retrieve Project State (Hydrates from DB if not in memory)
|
| 442 |
const project = await StateManager.getProject(projectId);
|
| 443 |
|
| 444 |
if (!project) {
|
| 445 |
-
// If project doesn't exist in Memory or DB
|
| 446 |
-
console.log("project not found in db, id: ", projectId);
|
| 447 |
return res.status(404).json({ action: "IDLE", error: "Project not found" });
|
| 448 |
}
|
| 449 |
-
// console.log("3");
|
| 450 |
|
| 451 |
-
// 3. SECURITY CHECK: Ensure the user matches the project owner
|
| 452 |
if (project.userId !== userId) {
|
| 453 |
console.warn(`[Security] Unauthorized ping for ${projectId}. Owner: ${project.userId}, Request: ${userId}`);
|
| 454 |
return res.status(403).json({ error: "Unauthorized: You do not own this project." });
|
| 455 |
}
|
| 456 |
-
|
| 457 |
-
// 4. Retrieve Command (Only if authorized)
|
| 458 |
const command = await StateManager.popCommand(projectId);
|
| 459 |
|
| 460 |
if (command) {
|
|
@@ -467,29 +472,10 @@ app.post('/project/ping', async (req, res) => {
|
|
| 467 |
code: command.type === 'EXECUTE' ? command.payload : null
|
| 468 |
});
|
| 469 |
}
|
| 470 |
-
} else {
|
| 471 |
-
// console.log("5");
|
| 472 |
-
res.json({ action: "IDLE" });
|
| 473 |
-
}
|
| 474 |
-
});
|
| 475 |
-
|
| 476 |
-
/*
|
| 477 |
-
app.post('/project/ping', async (req, res) => {
|
| 478 |
-
const { projectId } = req.body;
|
| 479 |
-
// This will hydrate from DB if missing
|
| 480 |
-
const command = await StateManager.popCommand(projectId);
|
| 481 |
-
|
| 482 |
-
if (command) {
|
| 483 |
-
if (command.payload === "CLEAR_CONSOLE") {
|
| 484 |
-
res.json({ action: "CLEAR_LOGS" });
|
| 485 |
-
} else {
|
| 486 |
-
res.json({ action: command.type, target: command.payload, code: command.type === 'EXECUTE' ? command.payload : null });
|
| 487 |
-
}
|
| 488 |
} else {
|
| 489 |
res.json({ action: "IDLE" });
|
| 490 |
}
|
| 491 |
});
|
| 492 |
-
*/
|
| 493 |
|
| 494 |
app.post('/human/override', validateRequest, async (req, res) => {
|
| 495 |
const { projectId, instruction, pruneHistory } = req.body;
|
|
|
|
| 17 |
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON);
|
| 18 |
|
| 19 |
// We assume the project ID is available in the service account to construct the bucket name
|
|
|
|
| 20 |
const bucketName = `shago-web.firebasestorage.app`;
|
| 21 |
|
| 22 |
if (admin.apps.length === 0) {
|
|
|
|
| 118 |
app.post('/onboarding/create', validateRequest, async (req, res) => {
|
| 119 |
const { userId, description, answers } = req.body;
|
| 120 |
|
|
|
|
| 121 |
const randomHex = (n = 6) => {
|
| 122 |
const bytes = Math.ceil(n / 2);
|
| 123 |
return crypto.randomBytes(bytes).toString("hex").slice(0, n);
|
|
|
|
| 142 |
thumbnailBase64 = await AIEngine.generateImage(imagePrompt);
|
| 143 |
}
|
| 144 |
|
|
|
|
| 145 |
let thumbnailUrl = null;
|
| 146 |
if (thumbnailBase64 && storage) {
|
| 147 |
try {
|
| 148 |
console.log(`[Onboarding] 📤 Uploading Thumbnail to Storage...`);
|
|
|
|
| 149 |
const base64Data = thumbnailBase64.replace(/^data:image\/\w+;base64,/, "");
|
| 150 |
const buffer = Buffer.from(base64Data, 'base64');
|
| 151 |
const bucket = storage.bucket();
|
|
|
|
| 155 |
metadata: { contentType: 'image/png' }
|
| 156 |
});
|
| 157 |
|
|
|
|
| 158 |
await file.makePublic();
|
|
|
|
| 159 |
thumbnailUrl = `https://storage.googleapis.com/${bucket.name}/${projectId}/thumbnail.png`;
|
| 160 |
console.log(`[Onboarding] 🖼️ Thumbnail URL: ${thumbnailUrl}`);
|
| 161 |
} catch (uploadErr) {
|
| 162 |
console.error("Storage Upload Failed:", uploadErr);
|
|
|
|
| 163 |
}
|
| 164 |
}
|
| 165 |
|
| 166 |
const timestamp = Date.now();
|
| 167 |
const status = isFailure ? "rejected" : "Idle";
|
| 168 |
|
|
|
|
|
|
|
| 169 |
const memoryObject = {
|
| 170 |
id: projectId,
|
| 171 |
userId,
|
|
|
|
| 182 |
failureCount: 0
|
| 183 |
};
|
| 184 |
|
|
|
|
| 185 |
if (firestore && !isFailure) {
|
| 186 |
try {
|
| 187 |
await firestore.collection('projects').doc(projectId).set({
|
|
|
|
| 196 |
}
|
| 197 |
}
|
| 198 |
|
|
|
|
| 199 |
if (db && !isFailure) {
|
| 200 |
const updates = {};
|
|
|
|
|
|
|
| 201 |
updates[`projects/${projectId}/info`] = {
|
| 202 |
id: projectId,
|
| 203 |
userId,
|
|
|
|
| 209 |
status
|
| 210 |
};
|
| 211 |
|
|
|
|
| 212 |
if (thumbnailUrl) {
|
| 213 |
updates[`projects/${projectId}/thumbnail`] = { url: thumbnailUrl };
|
| 214 |
}
|
| 215 |
|
|
|
|
| 216 |
updates[`projects/${projectId}/state`] = {
|
| 217 |
workerHistory: [],
|
| 218 |
pmHistory: [],
|
|
|
|
| 223 |
await db.ref().update(updates);
|
| 224 |
}
|
| 225 |
|
|
|
|
| 226 |
if (!isFailure) {
|
| 227 |
await StateManager.updateProject(projectId, memoryObject);
|
| 228 |
}
|
|
|
|
| 232 |
projectId,
|
| 233 |
stats: grading,
|
| 234 |
title: grading.title || "Untitled Project",
|
| 235 |
+
thumbnail: thumbnailBase64
|
| 236 |
});
|
| 237 |
|
| 238 |
} catch (err) {
|
|
|
|
| 243 |
|
| 244 |
// --- CORE ENDPOINTS ---
|
| 245 |
|
| 246 |
+
// NEW: Helper function to run initialization in background
|
| 247 |
+
async function runBackgroundInitialization(projectId, userId, description) {
|
| 248 |
+
console.log(`[Background] Starting initialization for ${projectId}`);
|
| 249 |
try {
|
| 250 |
const pmHistory = [];
|
| 251 |
+
|
| 252 |
+
// 1. Generate GDD
|
| 253 |
const gddPrompt = `Create a comprehensive GDD for: ${description}`;
|
| 254 |
const gddResponse = await AIEngine.callPM(pmHistory, gddPrompt);
|
| 255 |
|
| 256 |
pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] });
|
| 257 |
pmHistory.push({ role: 'model', parts: [{ text: gddResponse }] });
|
| 258 |
|
| 259 |
+
// 2. Generate First Task
|
| 260 |
const taskPrompt = "Based on the GDD, generate the first technical milestone.\nOutput format:\nTASK_NAME: <Name>\nWORKER_PROMPT: <Specific, isolated instructions for the worker>";
|
| 261 |
const taskResponse = await AIEngine.callPM(pmHistory, taskPrompt);
|
| 262 |
|
| 263 |
pmHistory.push({ role: 'user', parts: [{ text: taskPrompt }] });
|
| 264 |
pmHistory.push({ role: 'model', parts: [{ text: taskResponse }] });
|
| 265 |
|
| 266 |
+
// 3. Initialize Worker
|
| 267 |
const initialWorkerInstruction = extractWorkerPrompt(taskResponse) || `Initialize structure for: ${description}`;
|
|
|
|
| 268 |
const workerHistory = [];
|
| 269 |
const initialWorkerPrompt = `CONTEXT: New Project. \nINSTRUCTION: ${initialWorkerInstruction}`;
|
| 270 |
const workerResponse = await AIEngine.callWorker(workerHistory, initialWorkerPrompt, []);
|
|
|
|
| 272 |
workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
|
| 273 |
workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
|
| 274 |
|
| 275 |
+
// 4. Update Memory & DB
|
| 276 |
await StateManager.updateProject(projectId, {
|
| 277 |
userId,
|
| 278 |
pmHistory,
|
|
|
|
| 282 |
});
|
| 283 |
|
| 284 |
await processAndQueueResponse(projectId, workerResponse);
|
| 285 |
+
|
| 286 |
+
// 5. Update Status to IDLE (Ready for user input)
|
| 287 |
+
if(db) await db.ref(`projects/${projectId}/info/status`).set("IDLE");
|
| 288 |
+
|
| 289 |
+
console.log(`[Background] Initialization complete for ${projectId}`);
|
| 290 |
+
|
| 291 |
} catch (err) {
|
| 292 |
+
console.error(`[Background] Init Error for ${projectId}:`, err);
|
| 293 |
+
if(db) await db.ref(`projects/${projectId}/info/status`).set("error");
|
| 294 |
}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
app.post('/new/project', validateRequest, (req, res) => {
|
| 298 |
+
const { userId, projectId, description } = req.body;
|
| 299 |
+
|
| 300 |
+
// 1. Immediately acknowledge request
|
| 301 |
+
// We set status to 'working' or 'initializing' immediately so frontend shows a loader
|
| 302 |
+
if(db) db.ref(`projects/${projectId}/info/status`).set("initializing");
|
| 303 |
+
|
| 304 |
+
res.json({
|
| 305 |
+
success: true,
|
| 306 |
+
message: "Project initialization started in background."
|
| 307 |
+
});
|
| 308 |
+
|
| 309 |
+
// 2. Trigger background process (no await)
|
| 310 |
+
runBackgroundInitialization(projectId, userId, description);
|
| 311 |
});
|
| 312 |
|
| 313 |
app.post('/project/feedback', async (req, res) => {
|
| 314 |
const { userId, projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete, images } = req.body;
|
| 315 |
|
|
|
|
| 316 |
const project = await StateManager.getProject(projectId);
|
| 317 |
|
| 318 |
if (!project) return res.status(404).json({ error: "Project not found." });
|
| 319 |
|
| 320 |
+
if (project.userId !== userId) {
|
| 321 |
console.warn(`[Security] Unauthorized ping for ${projectId}. Owner: ${project.userId}, Request: ${userId}`);
|
| 322 |
return res.status(403).json({ error: "Unauthorized: You do not own this project." });
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
// Update status to working immediately
|
| 326 |
+
if(db) await db.ref(`projects/${projectId}/info/status`).set("working");
|
| 327 |
|
| 328 |
if (taskComplete) {
|
| 329 |
console.log(`[${projectId}] ✅ TASK COMPLETE.`);
|
|
|
|
| 336 |
const nextInstruction = extractWorkerPrompt(pmResponse);
|
| 337 |
if (!nextInstruction) {
|
| 338 |
await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, status: "IDLE" });
|
| 339 |
+
if(db) await db.ref(`projects/${projectId}/info/status`).set("IDLE");
|
| 340 |
return res.json({ success: true, message: "No further tasks. Project Idle." });
|
| 341 |
}
|
| 342 |
|
|
|
|
| 355 |
|
| 356 |
StateManager.queueCommand(projectId, { type: "EXECUTE", payload: "warn('Starting Next Task...')" });
|
| 357 |
await processAndQueueResponse(projectId, workerResponse);
|
| 358 |
+
if(db) await db.ref(`projects/${projectId}/info/status`).set("working"); // Keep working while plugin picks it up? Or IDLE to wait for plugin?
|
| 359 |
return res.json({ success: true, message: "Next Task Assigned" });
|
| 360 |
}
|
| 361 |
|
|
|
|
| 429 |
});
|
| 430 |
|
| 431 |
await processAndQueueResponse(projectId, response);
|
| 432 |
+
// Ensure status goes back to working or IDLE depending on your loop preference.
|
| 433 |
+
// Usually, if we just gave a command, we are 'working' until the plugin polls and finishes.
|
| 434 |
+
// But for UI feedback, 'working' usually means "AI is generating".
|
| 435 |
+
// Once generated (here), we might want to set it to 'IDLE' or 'waiting_for_plugin'.
|
| 436 |
+
if(db) await db.ref(`projects/${projectId}/info/status`).set("working");
|
| 437 |
|
| 438 |
+
res.json({ success: true });
|
| 439 |
} catch (err) {
|
| 440 |
console.error("AI Error:", err);
|
| 441 |
res.status(500).json({ error: "AI Failed" });
|
|
|
|
| 443 |
});
|
| 444 |
|
| 445 |
app.post('/project/ping', async (req, res) => {
|
|
|
|
| 446 |
const { projectId, userId } = req.body;
|
|
|
|
| 447 |
|
| 448 |
if (!projectId || !userId) {
|
| 449 |
return res.status(400).json({ error: "Missing ID fields" });
|
| 450 |
}
|
| 451 |
+
|
|
|
|
| 452 |
const project = await StateManager.getProject(projectId);
|
| 453 |
|
| 454 |
if (!project) {
|
|
|
|
|
|
|
| 455 |
return res.status(404).json({ action: "IDLE", error: "Project not found" });
|
| 456 |
}
|
|
|
|
| 457 |
|
|
|
|
| 458 |
if (project.userId !== userId) {
|
| 459 |
console.warn(`[Security] Unauthorized ping for ${projectId}. Owner: ${project.userId}, Request: ${userId}`);
|
| 460 |
return res.status(403).json({ error: "Unauthorized: You do not own this project." });
|
| 461 |
}
|
| 462 |
+
|
|
|
|
| 463 |
const command = await StateManager.popCommand(projectId);
|
| 464 |
|
| 465 |
if (command) {
|
|
|
|
| 472 |
code: command.type === 'EXECUTE' ? command.payload : null
|
| 473 |
});
|
| 474 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
} else {
|
| 476 |
res.json({ action: "IDLE" });
|
| 477 |
}
|
| 478 |
});
|
|
|
|
| 479 |
|
| 480 |
app.post('/human/override', validateRequest, async (req, res) => {
|
| 481 |
const { projectId, instruction, pruneHistory } = req.body;
|