Update app.js
Browse files
app.js
CHANGED
|
@@ -158,7 +158,7 @@ async function runBackgroundInitialization(projectId, userId, description) {
|
|
| 158 |
await StateManager.getProject(projectId);
|
| 159 |
|
| 160 |
await StateManager.updateProject(projectId, {
|
| 161 |
-
status: "
|
| 162 |
gdd: "",
|
| 163 |
failureCount: 0
|
| 164 |
});
|
|
@@ -228,7 +228,7 @@ async function runBackgroundInitialization(projectId, userId, description) {
|
|
| 228 |
|
| 229 |
await StateManager.updateProject(projectId, {
|
| 230 |
gdd: gddText,
|
| 231 |
-
status: "
|
| 232 |
});
|
| 233 |
|
| 234 |
await processAndQueueResponse(projectId, workerTextAccumulated, userId);
|
|
@@ -240,31 +240,42 @@ async function runBackgroundInitialization(projectId, userId, description) {
|
|
| 240 |
|
| 241 |
} catch (err) {
|
| 242 |
console.error(`[Background] Init Error for ${projectId}:`, err.message);
|
| 243 |
-
await StateManager.updateProject(projectId, { status: "
|
| 244 |
} finally {
|
| 245 |
StateManager.unlock(projectId);
|
| 246 |
}
|
| 247 |
}
|
| 248 |
|
| 249 |
-
// ---
|
| 250 |
async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
| 251 |
let diamondUsage = 0;
|
| 252 |
let basicUsage = 0;
|
| 253 |
|
| 254 |
try {
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
const project = await StateManager.getProject(projectId);
|
| 257 |
-
StateManager.clearSnapshot(projectId);
|
| 258 |
|
|
|
|
|
|
|
| 259 |
let stopStatus = startStatusLoop(projectId, 'worker');
|
| 260 |
|
| 261 |
-
// 2. Initial Worker Execution (The "Question" or "Attempt" phase)
|
| 262 |
let firstTurnResponse = "";
|
| 263 |
let thoughtText = "";
|
| 264 |
|
| 265 |
-
// Use
|
|
|
|
| 266 |
const currentWorkerHistory = [...(project.workerHistory || [])];
|
| 267 |
|
|
|
|
| 268 |
await AIEngine.callWorkerStream(
|
| 269 |
currentWorkerHistory,
|
| 270 |
fullInput,
|
|
@@ -283,19 +294,17 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
|
| 283 |
images
|
| 284 |
);
|
| 285 |
|
| 286 |
-
//
|
| 287 |
const routeTask = extractRouteToPM(firstTurnResponse);
|
| 288 |
const pmQuestion = extractPMQuestion(firstTurnResponse);
|
| 289 |
|
| 290 |
let finalResponseToSave = firstTurnResponse;
|
| 291 |
|
| 292 |
if (routeTask || pmQuestion) {
|
| 293 |
-
//
|
| 294 |
-
await StateManager.addHistory(projectId, 'worker', 'user', fullInput);
|
| 295 |
await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
|
| 296 |
|
| 297 |
-
//
|
| 298 |
-
currentWorkerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
|
| 299 |
currentWorkerHistory.push({ role: 'model', parts: [{ text: firstTurnResponse }] });
|
| 300 |
|
| 301 |
let pmPrompt = "";
|
|
@@ -311,7 +320,7 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
|
| 311 |
pmContextPrefix = "[PM ANSWER]:";
|
| 312 |
}
|
| 313 |
|
| 314 |
-
//
|
| 315 |
StateManager.clearSnapshot(projectId);
|
| 316 |
stopStatus = startStatusLoop(projectId, 'pm');
|
| 317 |
|
|
@@ -333,20 +342,21 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
|
| 333 |
}
|
| 334 |
);
|
| 335 |
|
| 336 |
-
diamondUsage += 0;
|
| 337 |
-
|
| 338 |
// Save PM History
|
| 339 |
await StateManager.addHistory(projectId, 'pm', 'user', pmPrompt);
|
| 340 |
await StateManager.addHistory(projectId, 'pm', 'model', pmResponseText);
|
| 341 |
|
| 342 |
-
//
|
| 343 |
-
// If the PM wrote code (backend logic), execute it now.
|
| 344 |
await processAndQueueResponse(projectId, pmResponseText, userId);
|
| 345 |
|
| 346 |
-
//
|
| 347 |
const nextInstruction = extractWorkerPrompt(pmResponseText) || pmResponseText;
|
| 348 |
const workerContinuationPrompt = `${pmContextPrefix} ${nextInstruction}\n\nBased on this, continue the task and output the code.`;
|
| 349 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
StateManager.clearSnapshot(projectId);
|
| 351 |
stopStatus = startStatusLoop(projectId, 'worker');
|
| 352 |
|
|
@@ -368,25 +378,21 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
|
| 368 |
}
|
| 369 |
);
|
| 370 |
|
| 371 |
-
// Save
|
| 372 |
-
await StateManager.addHistory(projectId, 'worker', 'user', workerContinuationPrompt);
|
| 373 |
await StateManager.addHistory(projectId, 'worker', 'model', secondTurnResponse);
|
| 374 |
|
| 375 |
-
// Update the variable for the FINAL execution (Worker's part)
|
| 376 |
finalResponseToSave = secondTurnResponse;
|
| 377 |
} else {
|
| 378 |
-
//
|
| 379 |
-
await StateManager.addHistory(projectId, 'worker', 'user', fullInput);
|
| 380 |
await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
|
| 381 |
}
|
| 382 |
|
| 383 |
StateManager.setStatus(projectId, "Idle");
|
| 384 |
|
| 385 |
-
//
|
| 386 |
-
// If PM ran before this, their code is already queued. Now we queue the Worker's code.
|
| 387 |
await processAndQueueResponse(projectId, finalResponseToSave, userId);
|
| 388 |
-
|
| 389 |
-
//
|
| 390 |
await StateManager.updateProject(projectId, { status: "idle" });
|
| 391 |
|
| 392 |
if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
|
|
@@ -395,6 +401,7 @@ async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
|
| 395 |
} catch (err) {
|
| 396 |
console.error("Async Feedback Error:", err);
|
| 397 |
StateManager.setStatus(projectId, "Error: " + err.message);
|
|
|
|
| 398 |
await StateManager.updateProject(projectId, { status: "error" });
|
| 399 |
}
|
| 400 |
}
|
|
@@ -500,12 +507,14 @@ app.post('/project/feedback', async (req, res) => {
|
|
| 500 |
|
| 501 |
await checkMinimumCredits(userId, 'basic');
|
| 502 |
|
|
|
|
|
|
|
| 503 |
await StateManager.updateProject(projectId, { status: "working" });
|
| 504 |
|
| 505 |
const context = formatContext({ hierarchyContext, scriptContext, logContext });
|
| 506 |
const fullInput = `USER: ${prompt || "Automatic Feedback"}${context}`;
|
| 507 |
|
| 508 |
-
//
|
| 509 |
runAsyncFeedback(projectId, userId, fullInput, images || []);
|
| 510 |
|
| 511 |
res.json({ success: true, message: "Processing started" });
|
|
@@ -527,6 +536,7 @@ app.post('/project/ping', async (req, res) => {
|
|
| 527 |
const currentStatus = StateManager.getStatus(projectId);
|
| 528 |
|
| 529 |
if (isFrontend) {
|
|
|
|
| 530 |
const snapshot = StateManager.getSnapshot(projectId);
|
| 531 |
return res.json({ status: currentStatus, snapshot });
|
| 532 |
}
|
|
|
|
| 158 |
await StateManager.getProject(projectId);
|
| 159 |
|
| 160 |
await StateManager.updateProject(projectId, {
|
| 161 |
+
status: "working",
|
| 162 |
gdd: "",
|
| 163 |
failureCount: 0
|
| 164 |
});
|
|
|
|
| 228 |
|
| 229 |
await StateManager.updateProject(projectId, {
|
| 230 |
gdd: gddText,
|
| 231 |
+
status: "idle"
|
| 232 |
});
|
| 233 |
|
| 234 |
await processAndQueueResponse(projectId, workerTextAccumulated, userId);
|
|
|
|
| 240 |
|
| 241 |
} catch (err) {
|
| 242 |
console.error(`[Background] Init Error for ${projectId}:`, err.message);
|
| 243 |
+
await StateManager.updateProject(projectId, { status: "error" });
|
| 244 |
} finally {
|
| 245 |
StateManager.unlock(projectId);
|
| 246 |
}
|
| 247 |
}
|
| 248 |
|
| 249 |
+
// --- CORE FIX: Robust Async Feedback with Immediate Persistence ---
|
| 250 |
async function runAsyncFeedback(projectId, userId, fullInput, images = []) {
|
| 251 |
let diamondUsage = 0;
|
| 252 |
let basicUsage = 0;
|
| 253 |
|
| 254 |
try {
|
| 255 |
+
console.log(`[${projectId}] Feedback received. Persisting state immediately.`);
|
| 256 |
+
|
| 257 |
+
// 1. INSTANT PERSISTENCE: Save status and User Message BEFORE anything else.
|
| 258 |
+
// This ensures if the user reloads now, they see "Working" and their message.
|
| 259 |
+
await Promise.all([
|
| 260 |
+
StateManager.updateProject(projectId, { status: "working" }),
|
| 261 |
+
StateManager.addHistory(projectId, 'worker', 'user', fullInput)
|
| 262 |
+
]);
|
| 263 |
+
|
| 264 |
+
// 2. Load latest state
|
| 265 |
const project = await StateManager.getProject(projectId);
|
|
|
|
| 266 |
|
| 267 |
+
// Reset buffers for frontend streaming
|
| 268 |
+
StateManager.clearSnapshot(projectId);
|
| 269 |
let stopStatus = startStatusLoop(projectId, 'worker');
|
| 270 |
|
|
|
|
| 271 |
let firstTurnResponse = "";
|
| 272 |
let thoughtText = "";
|
| 273 |
|
| 274 |
+
// Use local history clone so we can manipulate it for multi-turn without extra DB fetches
|
| 275 |
+
// Note: 'project.workerHistory' now already contains the user input we just saved in Step 1.
|
| 276 |
const currentWorkerHistory = [...(project.workerHistory || [])];
|
| 277 |
|
| 278 |
+
// 3. First Turn (Worker Execution)
|
| 279 |
await AIEngine.callWorkerStream(
|
| 280 |
currentWorkerHistory,
|
| 281 |
fullInput,
|
|
|
|
| 294 |
images
|
| 295 |
);
|
| 296 |
|
| 297 |
+
// 4. Analyze Output (Delegation Check)
|
| 298 |
const routeTask = extractRouteToPM(firstTurnResponse);
|
| 299 |
const pmQuestion = extractPMQuestion(firstTurnResponse);
|
| 300 |
|
| 301 |
let finalResponseToSave = firstTurnResponse;
|
| 302 |
|
| 303 |
if (routeTask || pmQuestion) {
|
| 304 |
+
// Save the Worker's preliminary response to DB so it doesn't vanish
|
|
|
|
| 305 |
await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
|
| 306 |
|
| 307 |
+
// Add to local context
|
|
|
|
| 308 |
currentWorkerHistory.push({ role: 'model', parts: [{ text: firstTurnResponse }] });
|
| 309 |
|
| 310 |
let pmPrompt = "";
|
|
|
|
| 320 |
pmContextPrefix = "[PM ANSWER]:";
|
| 321 |
}
|
| 322 |
|
| 323 |
+
// 5. Run PM
|
| 324 |
StateManager.clearSnapshot(projectId);
|
| 325 |
stopStatus = startStatusLoop(projectId, 'pm');
|
| 326 |
|
|
|
|
| 342 |
}
|
| 343 |
);
|
| 344 |
|
|
|
|
|
|
|
| 345 |
// Save PM History
|
| 346 |
await StateManager.addHistory(projectId, 'pm', 'user', pmPrompt);
|
| 347 |
await StateManager.addHistory(projectId, 'pm', 'model', pmResponseText);
|
| 348 |
|
| 349 |
+
// Execute PM Code (Server Logic)
|
|
|
|
| 350 |
await processAndQueueResponse(projectId, pmResponseText, userId);
|
| 351 |
|
| 352 |
+
// 6. Run Worker Continuation
|
| 353 |
const nextInstruction = extractWorkerPrompt(pmResponseText) || pmResponseText;
|
| 354 |
const workerContinuationPrompt = `${pmContextPrefix} ${nextInstruction}\n\nBased on this, continue the task and output the code.`;
|
| 355 |
|
| 356 |
+
// Save System Instruction
|
| 357 |
+
await StateManager.addHistory(projectId, 'worker', 'user', workerContinuationPrompt);
|
| 358 |
+
currentWorkerHistory.push({ role: 'user', parts: [{ text: workerContinuationPrompt }] });
|
| 359 |
+
|
| 360 |
StateManager.clearSnapshot(projectId);
|
| 361 |
stopStatus = startStatusLoop(projectId, 'worker');
|
| 362 |
|
|
|
|
| 378 |
}
|
| 379 |
);
|
| 380 |
|
| 381 |
+
// Save Final Response
|
|
|
|
| 382 |
await StateManager.addHistory(projectId, 'worker', 'model', secondTurnResponse);
|
| 383 |
|
|
|
|
| 384 |
finalResponseToSave = secondTurnResponse;
|
| 385 |
} else {
|
| 386 |
+
// Normal path: We already saved 'user' in Step 1. Now save 'model'.
|
|
|
|
| 387 |
await StateManager.addHistory(projectId, 'worker', 'model', firstTurnResponse);
|
| 388 |
}
|
| 389 |
|
| 390 |
StateManager.setStatus(projectId, "Idle");
|
| 391 |
|
| 392 |
+
// 7. Final Execution & Cleanup
|
|
|
|
| 393 |
await processAndQueueResponse(projectId, finalResponseToSave, userId);
|
| 394 |
+
|
| 395 |
+
// Ensure status goes back to idle AND timestamp is updated
|
| 396 |
await StateManager.updateProject(projectId, { status: "idle" });
|
| 397 |
|
| 398 |
if (diamondUsage > 0) await deductUserCredits(userId, diamondUsage, 'diamond');
|
|
|
|
| 401 |
} catch (err) {
|
| 402 |
console.error("Async Feedback Error:", err);
|
| 403 |
StateManager.setStatus(projectId, "Error: " + err.message);
|
| 404 |
+
// Ensure we don't get stuck in 'working'
|
| 405 |
await StateManager.updateProject(projectId, { status: "error" });
|
| 406 |
}
|
| 407 |
}
|
|
|
|
| 507 |
|
| 508 |
await checkMinimumCredits(userId, 'basic');
|
| 509 |
|
| 510 |
+
// Response started -> Status is now WORKING (In Memory)
|
| 511 |
+
// Actual DB persistence happens inside runAsyncFeedback first thing
|
| 512 |
await StateManager.updateProject(projectId, { status: "working" });
|
| 513 |
|
| 514 |
const context = formatContext({ hierarchyContext, scriptContext, logContext });
|
| 515 |
const fullInput = `USER: ${prompt || "Automatic Feedback"}${context}`;
|
| 516 |
|
| 517 |
+
// Trigger Async
|
| 518 |
runAsyncFeedback(projectId, userId, fullInput, images || []);
|
| 519 |
|
| 520 |
res.json({ success: true, message: "Processing started" });
|
|
|
|
| 536 |
const currentStatus = StateManager.getStatus(projectId);
|
| 537 |
|
| 538 |
if (isFrontend) {
|
| 539 |
+
// Return Snapshot for streaming
|
| 540 |
const snapshot = StateManager.getSnapshot(projectId);
|
| 541 |
return res.json({ status: currentStatus, snapshot });
|
| 542 |
}
|