feat: add Phase 4 AI tools with Python bridge and 6 new tools

Add Python bridge (packages/ai/src/bridge.ts) that calls Python scripts
via child_process with venv-first fallback to system python3. Implements
6 AI-powered tools:

- Remove Background: rembg-based with U2-Net/IS-Net models
- Image Upscaling: Real-ESRGAN with Lanczos fallback
- OCR/Text Extraction: Tesseract + PaddleOCR engines
- Face/PII Blur: MediaPipe face detection with configurable blur
- Object Eraser: LaMa inpainting with mask-based input
- Smart Crop: Sharp attention-based entropy cropping (no Python needed)

Each tool includes: Python script, TypeScript wrapper, API route,
and React settings component. All Python scripts handle ImportError
gracefully with clear installation messages.
This commit is contained in:
Siddharth Kumar Sah
2026-03-22 04:31:49 +08:00
parent a8cc611eb2
commit 5524939b6f
30 changed files with 1880 additions and 2 deletions
+86
View File
@@ -0,0 +1,86 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const execFileAsync = promisify(execFile);
const __dirname = dirname(fileURLToPath(import.meta.url));
const PYTHON_DIR = resolve(__dirname, "../python");
/** Try venv first, then system python. */
function getPythonPath(): string {
const venvPath = process.env.PYTHON_VENV_PATH || "/opt/venv";
return `${venvPath}/bin/python3`;
}
/**
* Extract a user-friendly error from a Python process error.
* Python scripts print JSON to stderr/stdout on failure — try to parse it.
*/
function extractPythonError(error: unknown): string {
if (error && typeof error === "object") {
const execError = error as {
stderr?: string;
stdout?: string;
message?: string;
};
// Try to parse JSON error from stderr or stdout
for (const output of [execError.stderr, execError.stdout]) {
if (output) {
try {
const parsed = JSON.parse(output.trim());
if (parsed.error) return parsed.error;
} catch {
// Not JSON, check for human-readable content
const trimmed = output.trim();
if (trimmed && !trimmed.startsWith("Traceback")) {
return trimmed;
}
}
}
}
if (execError.message) return execError.message;
}
return String(error);
}
/**
* Run a Python script from packages/ai/python/ with the given arguments.
* Falls back to system python3 if the venv is not available.
*/
export async function runPythonScript(
scriptName: string,
args: string[],
timeoutMs = 300000, // 5 min default
): Promise<{ stdout: string; stderr: string }> {
const scriptPath = resolve(PYTHON_DIR, scriptName);
const pythonPath = getPythonPath();
const execOpts = {
timeout: timeoutMs,
maxBuffer: 50 * 1024 * 1024, // 50MB
};
try {
const { stdout, stderr } = await execFileAsync(
pythonPath,
[scriptPath, ...args],
execOpts,
);
return { stdout: stdout.trim(), stderr: stderr.trim() };
} catch {
// Try system python as fallback
try {
const { stdout, stderr } = await execFileAsync(
"python3",
[scriptPath, ...args],
execOpts,
);
return { stdout: stdout.trim(), stderr: stderr.trim() };
} catch (fallbackError: unknown) {
const message = extractPythonError(fallbackError);
throw new Error(message);
}
}
}