Enhance logging and error handling across tools; add full tool audit and Playwright tests

- Added model mismatch warnings in colorize, enhance-faces, and upscale routes.
- Improved error handling in colorize, enhance_faces, remove_bg, restore, and upscale scripts with detailed logging.
- Updated Dockerfile to align NCCL versions for compatibility.
- Introduced a new full tool audit script to test all tools for functionality and GPU usage.
- Created Playwright E2E tests for GPU-dependent tools to ensure proper functionality and performance.
This commit is contained in:
Ashim
2026-04-17 23:06:31 +08:00
parent 51f60a8269
commit 08a7ffe403
16 changed files with 607 additions and 42 deletions
+48 -10
View File
@@ -32,11 +32,26 @@ function extractPythonError(error: unknown): string {
if (trimmed && !trimmed.startsWith("Traceback")) {
return trimmed;
}
// Extract the last meaningful line from a Python Traceback
if (trimmed) {
const lines = trimmed
.split("\n")
.map((l) => l.trim())
.filter(Boolean);
const lastLine = lines[lines.length - 1];
if (lastLine && lastLine !== "Traceback (most recent call last):") {
return lastLine;
}
}
}
}
}
if (execError.message) return execError.message;
// Return empty string for {stdout, stderr} objects with no useful content
// so the caller's fallback message (e.g. exit code) kicks in.
return "";
}
if (error instanceof Error) return error.message;
return String(error);
}
@@ -85,6 +100,9 @@ function startDispatcher(): ChildProcess | null {
if (parsed.ready === true) {
dispatcherReady = true;
dispatcherGpuAvailable = parsed.gpu === true;
console.log(
`[bridge] Python dispatcher ready (GPU: ${parsed.gpu === true})`,
);
continue;
}
@@ -97,7 +115,11 @@ function startDispatcher(): ChildProcess | null {
}
}
} catch {
// Not JSON - collect as error output for pending requests
// Not JSON - forward diagnostic messages to Node.js logger,
// collect the rest as error output for pending requests.
if (trimmed.startsWith("[")) {
console.log(`[python] ${trimmed}`);
}
for (const req of pendingRequests.values()) {
req.stderrLines.push(trimmed);
}
@@ -121,14 +143,17 @@ function startDispatcher(): ChildProcess | null {
if (pending) {
pendingRequests.delete(reqId);
if (response.exitCode !== 0) {
pending.reject(
new Error(
extractPythonError({
stdout: response.stdout,
stderr: pending.stderrLines.join("\n"),
}) || `Python script exited with code ${response.exitCode}`,
),
);
const errText =
extractPythonError({
stdout: response.stdout,
stderr: pending.stderrLines.join("\n"),
}) ||
(response.exitCode === 137
? "Process killed (out of memory) — try a lighter model or smaller image"
: response.exitCode === 139
? "Process crashed (segmentation fault)"
: `Python script exited with code ${response.exitCode}`);
pending.reject(new Error(errText));
} else {
pending.resolve({
stdout: response.stdout || "",
@@ -143,6 +168,7 @@ function startDispatcher(): ChildProcess | null {
});
child.on("error", (err: NodeJS.ErrnoException) => {
console.error(`[bridge] Dispatcher error: ${err.message} (code: ${err.code})`);
if (err.code === "ENOENT") {
// Venv python not found - mark as failed, will fall back to per-request
dispatcherFailed = true;
@@ -307,7 +333,7 @@ function runPythonPerRequest(
}
});
child.on("close", (code) => {
child.on("close", (code, signal) => {
clearTimeout(timer);
if (stderrBuffer.trim()) {
@@ -322,7 +348,16 @@ function runPythonPerRequest(
const stderr = stderrLines.join("\n");
if (code !== 0) {
// When the process was killed by a signal, use a clear message
// instead of surfacing unrelated stderr (e.g. CUDA warnings).
const signalMsg =
signal === "SIGKILL" || code === 137
? "Process killed (out of memory) — try a lighter model or smaller image"
: signal === "SIGSEGV" || code === 139
? "Process crashed (segmentation fault)"
: null;
const errorText =
signalMsg ||
extractPythonError({ stdout: stdout.trim(), stderr }) ||
`Python script exited with code ${code}`;
rejectPromise(new Error(errorText));
@@ -361,6 +396,9 @@ export function runPythonWithProgress(
// Retry in an isolated per-request process which starts clean and has
// more available memory than the warm dispatcher.
if (err.message === "Python dispatcher exited unexpectedly") {
console.warn(
`[bridge] Dispatcher crashed during ${scriptName}, retrying with per-request process`,
);
return runPythonPerRequest(scriptName, args, options);
}
throw err;