fix: tool page race condition on refresh and install error reporting

- Subscribe to features store reactively in ToolPage so refresh shows
  install prompt correctly instead of the tool UI
- Show loading state while features are being fetched for AI tools
- Capture stdout from install script for better error messages
- Keep last 20 stderr lines for error context instead of just exit code
- Clear install progress on success (was leaving stale state)
- Pass PIP_CACHE_DIR to install subprocess
This commit is contained in:
ashim-hq
2026-04-18 10:55:22 +08:00
parent 3b2612ac72
commit 3eab3d5391
2 changed files with 41 additions and 11 deletions
+16 -6
View File
@@ -133,27 +133,35 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise<void>
const modelsDir = getModelsDir();
const child = spawn(pythonPath, [scriptPath, bundleId, manifestPath, modelsDir], {
stdio: ["ignore", "ignore", "pipe"],
stdio: ["ignore", "pipe", "pipe"],
env: {
...process.env,
BUNDLE_ID: bundleId,
PIP_CACHE_DIR: join(getAiDir(), "pip-cache"),
},
});
let stderrBuffer = "";
let stdoutBuffer = "";
const lastStderrLines: string[] = [];
child.stdout.on("data", (chunk: Buffer) => {
stdoutBuffer += chunk.toString();
});
child.stderr.on("data", (chunk: Buffer) => {
stderrBuffer += chunk.toString();
// Process complete lines
const lines = stderrBuffer.split("\n");
// Keep the last incomplete line in the buffer
stderrBuffer = lines.pop() ?? "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
lastStderrLines.push(trimmed);
if (lastStderrLines.length > 20) lastStderrLines.shift();
try {
const parsed = JSON.parse(trimmed) as { progress?: number; stage?: string };
if (typeof parsed.progress === "number") {
@@ -170,7 +178,7 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise<void>
});
}
} catch {
// Not JSON — ignore non-progress stderr output
// Not JSON progress — rembg/pip output noise, keep in lastStderrLines for error reporting
}
}
});
@@ -181,10 +189,12 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise<void>
if (code === 0) {
invalidateCache();
shutdownDispatcher();
setInstallProgress(bundleId, { percent: 100, stage: "Complete" }, null);
setInstallProgress(null, null, null);
updateSingleFileProgress({ jobId, phase: "complete", percent: 100, stage: "Complete" });
} else {
const errorMsg = `Install failed with exit code ${code}`;
const errorDetail =
lastStderrLines.filter((l) => !l.startsWith("{")).join("\n") || stdoutBuffer.trim();
const errorMsg = errorDetail || `Install failed with exit code ${code}`;
setInstallProgress(bundleId, null, errorMsg);
updateSingleFileProgress({ jobId, phase: "failed", percent: 0, error: errorMsg });
}