From 12c4d4de6f9ef7e4d7460183ddf620107dce3724 Mon Sep 17 00:00:00 2001 From: ashim-hq Date: Sun, 19 Apr 2026 12:13:26 +0800 Subject: [PATCH] fix: update tool installation checks and refactor stdout JSON parsing in AI modules --- apps/api/src/lib/feature-status.ts | 2 +- apps/api/src/routes/tools/restore-photo.ts | 23 +++++++++++----------- packages/ai/src/background-removal.ts | 4 ++-- packages/ai/src/bridge.ts | 11 ++++++++--- packages/ai/src/colorization.ts | 4 ++-- packages/ai/src/face-detection.ts | 6 +++--- packages/ai/src/face-enhancement.ts | 4 ++-- packages/ai/src/face-landmarks.ts | 4 ++-- packages/ai/src/inpainting.ts | 4 ++-- packages/ai/src/noise-removal.ts | 4 ++-- packages/ai/src/ocr.ts | 4 ++-- packages/ai/src/red-eye-removal.ts | 4 ++-- packages/ai/src/restoration.ts | 4 ++-- packages/ai/src/upscaling.ts | 4 ++-- 14 files changed, 43 insertions(+), 39 deletions(-) diff --git a/apps/api/src/lib/feature-status.ts b/apps/api/src/lib/feature-status.ts index 5103b789..c3837263 100644 --- a/apps/api/src/lib/feature-status.ts +++ b/apps/api/src/lib/feature-status.ts @@ -100,7 +100,7 @@ export function isFeatureInstalled(bundleId: string): boolean { export function isToolInstalled(toolId: string): boolean { const bundleId = TOOL_BUNDLE_MAP[toolId]; - if (!bundleId) return false; + if (!bundleId) return true; return isFeatureInstalled(bundleId); } diff --git a/apps/api/src/routes/tools/restore-photo.ts b/apps/api/src/routes/tools/restore-photo.ts index b917dd70..7e1267b4 100644 --- a/apps/api/src/routes/tools/restore-photo.ts +++ b/apps/api/src/routes/tools/restore-photo.ts @@ -32,6 +32,17 @@ const settingsSchema = z.object({ */ export function registerRestorePhoto(app: FastifyInstance) { app.post("/api/v1/tools/restore-photo", async (request: FastifyRequest, reply: FastifyReply) => { + if (!isToolInstalled("restore-photo")) { + const bundle = getBundleForTool("restore-photo"); + return reply.status(501).send({ + error: "Feature not installed", + code: "FEATURE_NOT_INSTALLED", + feature: "photo-restoration", + featureName: bundle?.name ?? "Photo Restoration", + estimatedSize: bundle?.estimatedSize ?? "unknown", + }); + } + let fileBuffer: Buffer | null = null; let filename = "image"; let settingsRaw: string | null = null; @@ -69,18 +80,6 @@ export function registerRestorePhoto(app: FastifyInstance) { return reply.status(400).send({ error: `Invalid image: ${validation.reason}` }); } - // Guard: check if the photo restoration feature bundle is installed - if (!isToolInstalled("restore-photo")) { - const bundle = getBundleForTool("restore-photo"); - return reply.status(501).send({ - error: "Feature not installed", - code: "FEATURE_NOT_INSTALLED", - feature: "photo-restoration", - featureName: bundle?.name ?? "Photo Restoration", - estimatedSize: bundle?.estimatedSize ?? "unknown", - }); - } - try { const settings = settingsSchema.parse(settingsRaw ? JSON.parse(settingsRaw) : {}); diff --git a/packages/ai/src/background-removal.ts b/packages/ai/src/background-removal.ts index 162a6347..817f95a7 100644 --- a/packages/ai/src/background-removal.ts +++ b/packages/ai/src/background-removal.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import { readFile, unlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface RemoveBackgroundOptions { model?: string; @@ -29,7 +29,7 @@ export async function removeBackground( { onProgress, timeout }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Background removal failed"); } diff --git a/packages/ai/src/bridge.ts b/packages/ai/src/bridge.ts index b5dad362..9b6f3116 100644 --- a/packages/ai/src/bridge.ts +++ b/packages/ai/src/bridge.ts @@ -100,9 +100,7 @@ function startDispatcher(): ChildProcess | null { if (parsed.ready === true) { dispatcherReady = true; dispatcherGpuAvailable = parsed.gpu === true; - console.log( - `[bridge] Python dispatcher ready (GPU: ${parsed.gpu === true})`, - ); + console.log(`[bridge] Python dispatcher ready (GPU: ${parsed.gpu === true})`); continue; } @@ -408,3 +406,10 @@ export function runPythonWithProgress( // Fall back to per-request spawning return runPythonPerRequest(scriptName, args, options); } + +// biome-ignore lint/suspicious/noExplicitAny: matches JSON.parse return type +export function parseStdoutJson(stdout: string): any { + const match = stdout.match(/\{[\s\S]*\}$/); + if (!match) throw new Error("No JSON response from Python script"); + return JSON.parse(match[0]); +} diff --git a/packages/ai/src/colorization.ts b/packages/ai/src/colorization.ts index 4849f746..25805d71 100644 --- a/packages/ai/src/colorization.ts +++ b/packages/ai/src/colorization.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface ColorizeOptions { intensity?: number; @@ -30,7 +30,7 @@ export async function colorize( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Colorization failed"); } diff --git a/packages/ai/src/face-detection.ts b/packages/ai/src/face-detection.ts index 50026087..c8641d8d 100644 --- a/packages/ai/src/face-detection.ts +++ b/packages/ai/src/face-detection.ts @@ -1,7 +1,7 @@ import { readFile, unlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface BlurFacesOptions { blurRadius?: number; @@ -46,7 +46,7 @@ export async function blurFaces( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Face detection failed"); } @@ -74,7 +74,7 @@ export async function detectFaces( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Face detection failed"); } diff --git a/packages/ai/src/face-enhancement.ts b/packages/ai/src/face-enhancement.ts index 2e743894..e9f5ddc2 100644 --- a/packages/ai/src/face-enhancement.ts +++ b/packages/ai/src/face-enhancement.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface EnhanceFacesOptions { model?: "auto" | "gfpgan" | "codeformer"; @@ -32,7 +32,7 @@ export async function enhanceFaces( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Face enhancement failed"); } diff --git a/packages/ai/src/face-landmarks.ts b/packages/ai/src/face-landmarks.ts index 944ecbd3..02913c9c 100644 --- a/packages/ai/src/face-landmarks.ts +++ b/packages/ai/src/face-landmarks.ts @@ -1,7 +1,7 @@ import { unlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface FaceLandmarkPoint { x: number; @@ -40,7 +40,7 @@ export async function detectFaceLandmarks( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Face landmark detection failed"); } diff --git a/packages/ai/src/inpainting.ts b/packages/ai/src/inpainting.ts index dd54a046..fc4ba17c 100644 --- a/packages/ai/src/inpainting.ts +++ b/packages/ai/src/inpainting.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export async function inpaint( inputBuffer: Buffer, @@ -19,7 +19,7 @@ export async function inpaint( onProgress, }); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Inpainting failed"); } diff --git a/packages/ai/src/noise-removal.ts b/packages/ai/src/noise-removal.ts index e199af1e..d28d69ab 100644 --- a/packages/ai/src/noise-removal.ts +++ b/packages/ai/src/noise-removal.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface NoiseRemovalOptions { tier?: string; @@ -35,7 +35,7 @@ export async function noiseRemoval( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Noise removal failed"); } diff --git a/packages/ai/src/ocr.ts b/packages/ai/src/ocr.ts index e3e067e6..511154e6 100644 --- a/packages/ai/src/ocr.ts +++ b/packages/ai/src/ocr.ts @@ -1,7 +1,7 @@ import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import sharp from "sharp"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export type OcrQuality = "fast" | "balanced" | "best"; @@ -36,7 +36,7 @@ export async function extractText( timeout: 600_000, // 10 min timeout for VLM on CPU }); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "OCR failed"); } diff --git a/packages/ai/src/red-eye-removal.ts b/packages/ai/src/red-eye-removal.ts index f8332f90..f1773248 100644 --- a/packages/ai/src/red-eye-removal.ts +++ b/packages/ai/src/red-eye-removal.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface RedEyeRemovalOptions { sensitivity?: number; @@ -34,7 +34,7 @@ export async function removeRedEye( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Red eye removal failed"); } diff --git a/packages/ai/src/restoration.ts b/packages/ai/src/restoration.ts index 74568215..9cad74eb 100644 --- a/packages/ai/src/restoration.ts +++ b/packages/ai/src/restoration.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface RestorePhotoOptions { mode?: string; @@ -39,7 +39,7 @@ export async function restorePhoto( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Photo restoration failed"); } diff --git a/packages/ai/src/upscaling.ts b/packages/ai/src/upscaling.ts index e5d3ff1e..bde8766f 100644 --- a/packages/ai/src/upscaling.ts +++ b/packages/ai/src/upscaling.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { type ProgressCallback, runPythonWithProgress } from "./bridge.js"; +import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js"; export interface UpscaleOptions { scale?: number; @@ -35,7 +35,7 @@ export async function upscale( { onProgress }, ); - const result = JSON.parse(stdout); + const result = parseStdoutJson(stdout); if (!result.success) { throw new Error(result.error || "Upscaling failed"); }