mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: resolve basicsr/torchvision shim bug, lint warnings, and code formatting
The torchvision compatibility shim for basicsr 1.4.2 was missing the parent-package binding and only proxied a single attribute, causing upscale and enhance-faces to fail at import time. The fix adds a __getattr__ proxy for all attributes, binds the shim to the parent package, and installs it in the dispatcher at startup for defense-in-depth. Also removes unused anyInstalling variable, redundant `as any` cast, and applies Biome formatting fixes across the codebase.
This commit is contained in:
@@ -239,7 +239,7 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise<void>
|
|||||||
for (const [otherId, otherBundle] of Object.entries(manifest.bundles)) {
|
for (const [otherId, otherBundle] of Object.entries(manifest.bundles)) {
|
||||||
if (otherId === bundleId) continue;
|
if (otherId === bundleId) continue;
|
||||||
if (!isFeatureInstalled(otherId)) continue;
|
if (!isFeatureInstalled(otherId)) continue;
|
||||||
for (const m of (otherBundle as any).models ?? []) {
|
for (const m of otherBundle.models ?? []) {
|
||||||
if (m.path) sharedPaths.add(m.path);
|
if (m.path) sharedPaths.add(m.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import llmstxt from "vitepress-plugin-llms";
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: "ashim",
|
title: "ashim",
|
||||||
description: "Documentation for ashim - A Self Hosted Image Manipulator. 45+ tools, local AI, pipelines, REST API.",
|
description:
|
||||||
|
"Documentation for ashim - A Self Hosted Image Manipulator. 45+ tools, local AI, pipelines, REST API.",
|
||||||
base: "/ashim/",
|
base: "/ashim/",
|
||||||
srcDir: ".",
|
srcDir: ".",
|
||||||
outDir: "./.vitepress/dist",
|
outDir: "./.vitepress/dist",
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ export function AiFeaturesSection() {
|
|||||||
prevInstallingKeys.current = currentKeys;
|
prevInstallingKeys.current = currentKeys;
|
||||||
}, [installing, loadDiskUsage]);
|
}, [installing, loadDiskUsage]);
|
||||||
|
|
||||||
const anyInstalling = Object.keys(installing).length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -105,9 +103,7 @@ export function AiFeaturesSection() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={installAll}
|
onClick={installAll}
|
||||||
disabled={
|
disabled={installAllActive || bundles.every((b) => b.status === "installed")}
|
||||||
installAllActive || bundles.every((b) => b.status === "installed")
|
|
||||||
}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
|
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
|
|||||||
@@ -232,9 +232,7 @@ export const useFeaturesStore = create<FeaturesState>((set, get) => {
|
|||||||
// Immediately mark every not-yet-installed bundle as queued so the UI
|
// Immediately mark every not-yet-installed bundle as queued so the UI
|
||||||
// updates right away. Exclude bundles that are already installing.
|
// updates right away. Exclude bundles that are already installing.
|
||||||
const activeIds = new Set(Object.keys(get().installing));
|
const activeIds = new Set(Object.keys(get().installing));
|
||||||
const pending = get().bundles.filter(
|
const pending = get().bundles.filter((b) => b.status !== "installed" && !activeIds.has(b.id));
|
||||||
(b) => b.status !== "installed" && !activeIds.has(b.id),
|
|
||||||
);
|
|
||||||
// Clear stale errors for these bundles
|
// Clear stale errors for these bundles
|
||||||
const errors = { ...get().errors };
|
const errors = { ...get().errors };
|
||||||
for (const b of pending) delete errors[b.id];
|
for (const b of pending) delete errors[b.id];
|
||||||
|
|||||||
@@ -49,6 +49,36 @@ def emit_progress(percent, stage):
|
|||||||
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
|
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ── basicsr / torchvision compatibility shim ──────────────────────────
|
||||||
|
# basicsr 1.4.2 (pulled in by realesrgan) does:
|
||||||
|
# from torchvision.transforms.functional_tensor import rgb_to_grayscale
|
||||||
|
# but torchvision >= 0.17 removed the functional_tensor submodule,
|
||||||
|
# merging everything into torchvision.transforms.functional.
|
||||||
|
# We install a shim module ONCE here so every script in this process
|
||||||
|
# benefits, rather than relying on each script to patch individually.
|
||||||
|
try:
|
||||||
|
import torchvision.transforms.functional_tensor # noqa: F401
|
||||||
|
except (ImportError, ModuleNotFoundError):
|
||||||
|
try:
|
||||||
|
import types
|
||||||
|
import torchvision.transforms.functional as _F
|
||||||
|
import torchvision.transforms
|
||||||
|
|
||||||
|
_shim = types.ModuleType("torchvision.transforms.functional_tensor")
|
||||||
|
_shim.__getattr__ = lambda name: getattr(_F, name)
|
||||||
|
_shim.rgb_to_grayscale = _F.rgb_to_grayscale
|
||||||
|
sys.modules["torchvision.transforms.functional_tensor"] = _shim
|
||||||
|
torchvision.transforms.functional_tensor = _shim
|
||||||
|
print("[dispatcher] Installed torchvision.transforms.functional_tensor shim",
|
||||||
|
file=sys.stderr, flush=True)
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
# torchvision not installed yet — shim not needed until
|
||||||
|
# the upscale-enhance bundle is installed.
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
# Catch-all so dispatcher startup is never blocked.
|
||||||
|
pass
|
||||||
|
|
||||||
# ── Pre-import heavy libraries ──────────────────────────────────────
|
# ── Pre-import heavy libraries ──────────────────────────────────────
|
||||||
# These imports are the main source of cold-start latency.
|
# These imports are the main source of cold-start latency.
|
||||||
# By importing once at startup, subsequent requests skip the import cost.
|
# By importing once at startup, subsequent requests skip the import cost.
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Patch for basicsr compatibility with torchvision >= 0.18.
|
# Patch for basicsr compatibility with torchvision >= 0.17.
|
||||||
# torchvision removed transforms.functional_tensor, merging it into
|
# torchvision removed transforms.functional_tensor, merging everything
|
||||||
# transforms.functional. basicsr still imports the old path, so we
|
# into transforms.functional. basicsr 1.4.2 still imports the old path
|
||||||
# create a shim module to redirect the import.
|
# (e.g. rgb_to_grayscale), so we create a proxy module that forwards
|
||||||
|
# ALL attribute lookups to the new location.
|
||||||
try:
|
try:
|
||||||
import torchvision.transforms.functional_tensor # noqa: F401
|
import torchvision.transforms.functional_tensor # noqa: F401
|
||||||
except (ImportError, ModuleNotFoundError):
|
except (ImportError, ModuleNotFoundError):
|
||||||
@@ -14,10 +15,20 @@ except (ImportError, ModuleNotFoundError):
|
|||||||
import types
|
import types
|
||||||
import torchvision.transforms.functional as _F
|
import torchvision.transforms.functional as _F
|
||||||
|
|
||||||
|
import torchvision.transforms
|
||||||
|
|
||||||
_shim = types.ModuleType("torchvision.transforms.functional_tensor")
|
_shim = types.ModuleType("torchvision.transforms.functional_tensor")
|
||||||
|
_shim.__getattr__ = lambda name: getattr(_F, name)
|
||||||
|
# Pre-populate the attribute basicsr actually imports so that
|
||||||
|
# `from torchvision.transforms.functional_tensor import rgb_to_grayscale`
|
||||||
|
# works (from-import checks __dict__ before __getattr__).
|
||||||
_shim.rgb_to_grayscale = _F.rgb_to_grayscale
|
_shim.rgb_to_grayscale = _F.rgb_to_grayscale
|
||||||
sys.modules["torchvision.transforms.functional_tensor"] = _shim
|
sys.modules["torchvision.transforms.functional_tensor"] = _shim
|
||||||
except ImportError as e:
|
# The parent package must also reference the submodule for
|
||||||
|
# `from torchvision.transforms.functional_tensor import ...` to
|
||||||
|
# resolve correctly in all Python versions.
|
||||||
|
torchvision.transforms.functional_tensor = _shim
|
||||||
|
except (ImportError, AttributeError) as e:
|
||||||
print(f"[enhance-faces] torchvision shim failed: {e}", file=sys.stderr, flush=True)
|
print(f"[enhance-faces] torchvision shim failed: {e}", file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Patch for basicsr compatibility with torchvision >= 0.18.
|
# Patch for basicsr compatibility with torchvision >= 0.17.
|
||||||
# torchvision removed transforms.functional_tensor, merging it into
|
# torchvision removed transforms.functional_tensor, merging everything
|
||||||
# transforms.functional. basicsr still imports the old path, so we
|
# into transforms.functional. basicsr 1.4.2 still imports the old path
|
||||||
# create a shim module to redirect the import.
|
# (e.g. rgb_to_grayscale), so we create a proxy module that forwards
|
||||||
|
# ALL attribute lookups to the new location.
|
||||||
try:
|
try:
|
||||||
import torchvision.transforms.functional_tensor # noqa: F401
|
import torchvision.transforms.functional_tensor # noqa: F401
|
||||||
except (ImportError, ModuleNotFoundError):
|
except (ImportError, ModuleNotFoundError):
|
||||||
@@ -14,10 +15,20 @@ except (ImportError, ModuleNotFoundError):
|
|||||||
import types
|
import types
|
||||||
import torchvision.transforms.functional as _F
|
import torchvision.transforms.functional as _F
|
||||||
|
|
||||||
|
import torchvision.transforms
|
||||||
|
|
||||||
_shim = types.ModuleType("torchvision.transforms.functional_tensor")
|
_shim = types.ModuleType("torchvision.transforms.functional_tensor")
|
||||||
|
_shim.__getattr__ = lambda name: getattr(_F, name)
|
||||||
|
# Pre-populate the attribute basicsr actually imports so that
|
||||||
|
# `from torchvision.transforms.functional_tensor import rgb_to_grayscale`
|
||||||
|
# works (from-import checks __dict__ before __getattr__).
|
||||||
_shim.rgb_to_grayscale = _F.rgb_to_grayscale
|
_shim.rgb_to_grayscale = _F.rgb_to_grayscale
|
||||||
sys.modules["torchvision.transforms.functional_tensor"] = _shim
|
sys.modules["torchvision.transforms.functional_tensor"] = _shim
|
||||||
except ImportError as e:
|
# The parent package must also reference the submodule for
|
||||||
|
# `from torchvision.transforms.functional_tensor import ...` to
|
||||||
|
# resolve correctly in all Python versions.
|
||||||
|
torchvision.transforms.functional_tensor = _shim
|
||||||
|
except (ImportError, AttributeError) as e:
|
||||||
print(f"[upscale] torchvision shim failed: {e}", file=sys.stderr, flush=True)
|
print(f"[upscale] torchvision shim failed: {e}", file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,13 +46,26 @@ async function test(name, path, settings, checks = {}) {
|
|||||||
if (checks.expectKey && checks.expectValue) {
|
if (checks.expectKey && checks.expectValue) {
|
||||||
const actual = body[checks.expectKey];
|
const actual = body[checks.expectKey];
|
||||||
if (actual !== checks.expectValue) {
|
if (actual !== checks.expectValue) {
|
||||||
log(name, "FAIL", `Expected ${checks.expectKey}=${checks.expectValue} but got ${actual} (FALLBACK DETECTED)`);
|
log(
|
||||||
|
name,
|
||||||
|
"FAIL",
|
||||||
|
`Expected ${checks.expectKey}=${checks.expectValue} but got ${actual} (FALLBACK DETECTED)`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Build detail string
|
// Build detail string
|
||||||
const parts = [];
|
const parts = [];
|
||||||
for (const k of ["method", "model", "engine", "format", "width", "height", "facesDetected", "steps"]) {
|
for (const k of [
|
||||||
|
"method",
|
||||||
|
"model",
|
||||||
|
"engine",
|
||||||
|
"format",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"facesDetected",
|
||||||
|
"steps",
|
||||||
|
]) {
|
||||||
if (body[k] !== undefined) {
|
if (body[k] !== undefined) {
|
||||||
const v = Array.isArray(body[k]) ? JSON.stringify(body[k]) : body[k];
|
const v = Array.isArray(body[k]) ? JSON.stringify(body[k]) : body[k];
|
||||||
parts.push(`${k}=${v}`);
|
parts.push(`${k}=${v}`);
|
||||||
@@ -93,27 +106,82 @@ async function main() {
|
|||||||
console.log("--- GPU/AI TOOLS (must use GPU, no CPU fallback) ---\n");
|
console.log("--- GPU/AI TOOLS (must use GPU, no CPU fallback) ---\n");
|
||||||
|
|
||||||
// Background removal — all models
|
// Background removal — all models
|
||||||
await test("Remove BG (birefnet-general-lite)", "remove-background", { model: "birefnet-general-lite" }, { expectKey: "model", expectValue: "birefnet-general-lite" });
|
await test(
|
||||||
await test("Remove BG (birefnet-portrait)", "remove-background", { model: "birefnet-portrait" }, { expectKey: "model", expectValue: "birefnet-portrait" });
|
"Remove BG (birefnet-general-lite)",
|
||||||
await test("Remove BG (birefnet-general)", "remove-background", { model: "birefnet-general" }, { expectKey: "model", expectValue: "birefnet-general" });
|
"remove-background",
|
||||||
await test("Remove BG (u2net)", "remove-background", { model: "u2net" }, { expectKey: "model", expectValue: "u2net" });
|
{ model: "birefnet-general-lite" },
|
||||||
await test("Remove BG (bria-rmbg)", "remove-background", { model: "bria-rmbg" }, { expectKey: "model", expectValue: "bria-rmbg" });
|
{ expectKey: "model", expectValue: "birefnet-general-lite" },
|
||||||
await test("Remove BG (isnet-general-use)", "remove-background", { model: "isnet-general-use" }, { expectKey: "model", expectValue: "isnet-general-use" });
|
);
|
||||||
await test("Remove BG (birefnet-matting/Ultra)", "remove-background", { model: "birefnet-matting" }, { expectKey: "model", expectValue: "birefnet-matting" });
|
await test(
|
||||||
|
"Remove BG (birefnet-portrait)",
|
||||||
|
"remove-background",
|
||||||
|
{ model: "birefnet-portrait" },
|
||||||
|
{ expectKey: "model", expectValue: "birefnet-portrait" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"Remove BG (birefnet-general)",
|
||||||
|
"remove-background",
|
||||||
|
{ model: "birefnet-general" },
|
||||||
|
{ expectKey: "model", expectValue: "birefnet-general" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"Remove BG (u2net)",
|
||||||
|
"remove-background",
|
||||||
|
{ model: "u2net" },
|
||||||
|
{ expectKey: "model", expectValue: "u2net" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"Remove BG (bria-rmbg)",
|
||||||
|
"remove-background",
|
||||||
|
{ model: "bria-rmbg" },
|
||||||
|
{ expectKey: "model", expectValue: "bria-rmbg" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"Remove BG (isnet-general-use)",
|
||||||
|
"remove-background",
|
||||||
|
{ model: "isnet-general-use" },
|
||||||
|
{ expectKey: "model", expectValue: "isnet-general-use" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"Remove BG (birefnet-matting/Ultra)",
|
||||||
|
"remove-background",
|
||||||
|
{ model: "birefnet-matting" },
|
||||||
|
{ expectKey: "model", expectValue: "birefnet-matting" },
|
||||||
|
);
|
||||||
|
|
||||||
// Upscale
|
// Upscale
|
||||||
await test("Upscale (realesrgan 2x)", "upscale", { scale: 2, model: "realesrgan" }, { expectKey: "method", expectValue: "realesrgan" });
|
await test(
|
||||||
await test("Upscale (realesrgan 4x)", "upscale", { scale: 4, model: "realesrgan" }, { expectKey: "method", expectValue: "realesrgan" });
|
"Upscale (realesrgan 2x)",
|
||||||
|
"upscale",
|
||||||
|
{ scale: 2, model: "realesrgan" },
|
||||||
|
{ expectKey: "method", expectValue: "realesrgan" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"Upscale (realesrgan 4x)",
|
||||||
|
"upscale",
|
||||||
|
{ scale: 4, model: "realesrgan" },
|
||||||
|
{ expectKey: "method", expectValue: "realesrgan" },
|
||||||
|
);
|
||||||
await test("Upscale (lanczos 2x)", "upscale", { scale: 2, model: "lanczos" });
|
await test("Upscale (lanczos 2x)", "upscale", { scale: 2, model: "lanczos" });
|
||||||
await test("Upscale (auto)", "upscale", { scale: 2, model: "auto" });
|
await test("Upscale (auto)", "upscale", { scale: 2, model: "auto" });
|
||||||
|
|
||||||
// Face enhancement
|
// Face enhancement
|
||||||
await test("Face Enhance (gfpgan)", "enhance-faces", { model: "gfpgan" }, { expectKey: "model", expectValue: "gfpgan" });
|
await test(
|
||||||
|
"Face Enhance (gfpgan)",
|
||||||
|
"enhance-faces",
|
||||||
|
{ model: "gfpgan" },
|
||||||
|
{ expectKey: "model", expectValue: "gfpgan" },
|
||||||
|
);
|
||||||
await test("Face Enhance (codeformer)", "enhance-faces", { model: "codeformer" });
|
await test("Face Enhance (codeformer)", "enhance-faces", { model: "codeformer" });
|
||||||
await test("Face Enhance (auto)", "enhance-faces", { model: "auto" });
|
await test("Face Enhance (auto)", "enhance-faces", { model: "auto" });
|
||||||
|
|
||||||
// Colorize
|
// Colorize
|
||||||
await test("Colorize (ddcolor)", "colorize", { model: "ddcolor" }, { expectKey: "method", expectValue: "ddcolor" });
|
await test(
|
||||||
|
"Colorize (ddcolor)",
|
||||||
|
"colorize",
|
||||||
|
{ model: "ddcolor" },
|
||||||
|
{ expectKey: "method", expectValue: "ddcolor" },
|
||||||
|
);
|
||||||
await test("Colorize (auto)", "colorize", { model: "auto" });
|
await test("Colorize (auto)", "colorize", { model: "auto" });
|
||||||
|
|
||||||
// Noise removal — all tiers
|
// Noise removal — all tiers
|
||||||
@@ -126,8 +194,18 @@ async function main() {
|
|||||||
await test("Photo Restoration", "restore-photo", {});
|
await test("Photo Restoration", "restore-photo", {});
|
||||||
|
|
||||||
// OCR
|
// OCR
|
||||||
await test("OCR (tesseract)", "ocr", { engine: "tesseract" }, { expectKey: "engine", expectValue: "tesseract" });
|
await test(
|
||||||
await test("OCR (paddleocr)", "ocr", { engine: "paddleocr" }, { expectKey: "engine", expectValue: "paddleocr-v5" });
|
"OCR (tesseract)",
|
||||||
|
"ocr",
|
||||||
|
{ engine: "tesseract" },
|
||||||
|
{ expectKey: "engine", expectValue: "tesseract" },
|
||||||
|
);
|
||||||
|
await test(
|
||||||
|
"OCR (paddleocr)",
|
||||||
|
"ocr",
|
||||||
|
{ engine: "paddleocr" },
|
||||||
|
{ expectKey: "engine", expectValue: "paddleocr-v5" },
|
||||||
|
);
|
||||||
|
|
||||||
// Face operations (MediaPipe)
|
// Face operations (MediaPipe)
|
||||||
await test("Face Blur", "blur-faces", { intensity: 50 });
|
await test("Face Blur", "blur-faces", { intensity: 50 });
|
||||||
@@ -159,7 +237,11 @@ async function main() {
|
|||||||
await test("Image Enhancement (vivid)", "image-enhancement", { preset: "vivid" });
|
await test("Image Enhancement (vivid)", "image-enhancement", { preset: "vivid" });
|
||||||
await test("Sharpening", "sharpening", { sigma: 1.5, amount: 1.0 });
|
await test("Sharpening", "sharpening", { sigma: 1.5, amount: 1.0 });
|
||||||
await test("Border", "border", { size: 20, color: "#ff0000" });
|
await test("Border", "border", { size: 20, color: "#ff0000" });
|
||||||
await test("Replace Color", "replace-color", { targetColor: "#ffffff", replacementColor: "#000000", tolerance: 30 });
|
await test("Replace Color", "replace-color", {
|
||||||
|
targetColor: "#ffffff",
|
||||||
|
replacementColor: "#000000",
|
||||||
|
tolerance: 30,
|
||||||
|
});
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════
|
||||||
// SECTION 3: UTILITY TOOLS
|
// SECTION 3: UTILITY TOOLS
|
||||||
@@ -178,10 +260,18 @@ async function main() {
|
|||||||
formData.append("file", new File([imageBlob], "test.webp", { type: "image/webp" }));
|
formData.append("file", new File([imageBlob], "test.webp", { type: "image/webp" }));
|
||||||
formData.append("settings", JSON.stringify({}));
|
formData.append("settings", JSON.stringify({}));
|
||||||
const res = await fetch(`${BASE}/api/v1/tools/favicon`, {
|
const res = await fetch(`${BASE}/api/v1/tools/favicon`, {
|
||||||
method: "POST", headers: { Authorization: `Bearer ${token}` }, body: formData,
|
method: "POST",
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
body: formData,
|
||||||
});
|
});
|
||||||
log("Favicon", res.ok ? "PASS" : "FAIL", `HTTP ${res.status}, ${res.headers.get('content-type')}`);
|
log(
|
||||||
} catch (e) { log("Favicon", "FAIL", e.message.slice(0, 100)); }
|
"Favicon",
|
||||||
|
res.ok ? "PASS" : "FAIL",
|
||||||
|
`HTTP ${res.status}, ${res.headers.get("content-type")}`,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
log("Favicon", "FAIL", e.message.slice(0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════
|
||||||
// SECTION 4: MULTI-IMAGE / SPECIAL TOOLS (may need special input)
|
// SECTION 4: MULTI-IMAGE / SPECIAL TOOLS (may need special input)
|
||||||
@@ -189,7 +279,12 @@ async function main() {
|
|||||||
console.log("\n--- SPECIAL TOOLS (may need specific inputs) ---\n");
|
console.log("\n--- SPECIAL TOOLS (may need specific inputs) ---\n");
|
||||||
|
|
||||||
await test("QR Generate", "qr-generate", { text: "https://ashim.app", size: 512, format: "png" });
|
await test("QR Generate", "qr-generate", { text: "https://ashim.app", size: 512, format: "png" });
|
||||||
await test("Text Overlay", "text-overlay", { text: "TEST", fontSize: 48, color: "#ff0000", position: "center" });
|
await test("Text Overlay", "text-overlay", {
|
||||||
|
text: "TEST",
|
||||||
|
fontSize: 48,
|
||||||
|
color: "#ff0000",
|
||||||
|
position: "center",
|
||||||
|
});
|
||||||
await test("Vectorize", "vectorize", {});
|
await test("Vectorize", "vectorize", {});
|
||||||
await test("SVG to Raster", "svg-to-raster", {}); // Will fail - needs SVG input
|
await test("SVG to Raster", "svg-to-raster", {}); // Will fail - needs SVG input
|
||||||
|
|
||||||
@@ -200,8 +295,8 @@ async function main() {
|
|||||||
console.log(" SUMMARY");
|
console.log(" SUMMARY");
|
||||||
console.log("=============================================================\n");
|
console.log("=============================================================\n");
|
||||||
|
|
||||||
const passed = results.filter(r => r.status === "PASS");
|
const passed = results.filter((r) => r.status === "PASS");
|
||||||
const failed = results.filter(r => r.status === "FAIL");
|
const failed = results.filter((r) => r.status === "FAIL");
|
||||||
|
|
||||||
console.log(`PASSED: ${passed.length}`);
|
console.log(`PASSED: ${passed.length}`);
|
||||||
console.log(`FAILED: ${failed.length}`);
|
console.log(`FAILED: ${failed.length}`);
|
||||||
@@ -219,21 +314,34 @@ async function main() {
|
|||||||
// Check GPU usage in docker logs
|
// Check GPU usage in docker logs
|
||||||
console.log("\n--- GPU USAGE CHECK ---\n");
|
console.log("\n--- GPU USAGE CHECK ---\n");
|
||||||
const { execSync } = await import("child_process");
|
const { execSync } = await import("child_process");
|
||||||
const logs = execSync('docker logs ashim 2>&1', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
|
const logs = execSync("docker logs ashim 2>&1", { encoding: "utf-8", maxBuffer: 1024 * 1024 });
|
||||||
const gpuLines = logs.split('\n').filter(l =>
|
const gpuLines = logs
|
||||||
l.includes('[gpu]') || l.includes('[bridge]') || l.includes('[dispatcher]') ||
|
.split("\n")
|
||||||
l.includes('GPU') || l.includes('CUDA') || l.includes('CUDAExecution')
|
.filter(
|
||||||
);
|
(l) =>
|
||||||
|
l.includes("[gpu]") ||
|
||||||
|
l.includes("[bridge]") ||
|
||||||
|
l.includes("[dispatcher]") ||
|
||||||
|
l.includes("GPU") ||
|
||||||
|
l.includes("CUDA") ||
|
||||||
|
l.includes("CUDAExecution"),
|
||||||
|
);
|
||||||
for (const line of gpuLines.slice(0, 15)) {
|
for (const line of gpuLines.slice(0, 15)) {
|
||||||
console.log(" " + line.trim().slice(0, 120));
|
console.log(" " + line.trim().slice(0, 120));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any fallback warnings
|
// Check for any fallback warnings
|
||||||
console.log("\n--- FALLBACK/MISMATCH WARNINGS ---\n");
|
console.log("\n--- FALLBACK/MISMATCH WARNINGS ---\n");
|
||||||
const warnLines = logs.split('\n').filter(l =>
|
const warnLines = logs
|
||||||
l.includes('mismatch') || l.includes('fallback') || l.includes('Falling back') ||
|
.split("\n")
|
||||||
l.includes('degraded') || l.includes('lanczos') && l.includes('warn')
|
.filter(
|
||||||
);
|
(l) =>
|
||||||
|
l.includes("mismatch") ||
|
||||||
|
l.includes("fallback") ||
|
||||||
|
l.includes("Falling back") ||
|
||||||
|
l.includes("degraded") ||
|
||||||
|
(l.includes("lanczos") && l.includes("warn")),
|
||||||
|
);
|
||||||
if (warnLines.length === 0) {
|
if (warnLines.length === 0) {
|
||||||
console.log(" None detected - no silent fallbacks occurred.");
|
console.log(" None detected - no silent fallbacks occurred.");
|
||||||
} else {
|
} else {
|
||||||
@@ -245,4 +353,7 @@ async function main() {
|
|||||||
process.exit(failed.length > 0 ? 1 : 0);
|
process.exit(failed.length > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(err => { console.error("Fatal:", err); process.exit(1); });
|
main().catch((err) => {
|
||||||
|
console.error("Fatal:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -87,7 +87,12 @@ export function getTestImagePath(): string {
|
|||||||
ihdr[9] = 6; // RGBA
|
ihdr[9] = 6; // RGBA
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
_testImagePath,
|
_testImagePath,
|
||||||
Buffer.concat([sig, chunk("IHDR", ihdr), chunk("IDAT", deflated), chunk("IEND", Buffer.alloc(0))]),
|
Buffer.concat([
|
||||||
|
sig,
|
||||||
|
chunk("IHDR", ihdr),
|
||||||
|
chunk("IDAT", deflated),
|
||||||
|
chunk("IEND", Buffer.alloc(0)),
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user