feat: add output filename suffixes, CPU fallback for GPU packages, and fix e2e tests

- Add tool-specific suffix to output filenames so downloads don't overwrite originals (batch & single-tool routes)
- Skip deleting shared models when uninstalling a bundle that shares models with another installed bundle
- Auto-detect NVIDIA GPU and swap GPU-only pip packages (onnxruntime-gpu, paddlepaddle-gpu) for CPU equivalents
- Refactor docker-compose with YAML anchors and explicit cpu/gpu profiles
- Add libheif-plugin-x265 to Dockerfile
- Fix install-all queue logic to handle concurrent individual installs and clear stale errors
- Unify playwright docker config to use same test dir with API_URL env var
- Fix flaky e2e selectors, rename Strip Metadata → Remove Metadata, handle collage custom dropzone, improve fallback test image generation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ashim
2026-04-20 10:56:47 +08:00
co-authored by Claude Opus 4.6
parent 92c0579a49
commit 6edb92c242
17 changed files with 234 additions and 66 deletions
+40
View File
@@ -46,6 +46,39 @@ def detect_arch() -> str:
return "amd64"
def has_nvidia_gpu() -> bool:
"""Check whether an NVIDIA GPU is accessible at runtime."""
try:
result = subprocess.run(
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
capture_output=True, text=True, timeout=5,
)
return result.returncode == 0 and len(result.stdout.strip()) > 0
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
def cpu_fallback_packages(packages: list[str]) -> list[str]:
"""Replace GPU-only packages with their CPU equivalents.
Called on amd64 when no NVIDIA GPU is detected so that onnxruntime /
paddlepaddle don't crash with a CUDA segfault.
"""
replacements = {
"onnxruntime-gpu": "onnxruntime",
"paddlepaddle-gpu": "paddlepaddle",
}
result = []
for pkg in packages:
name = pkg.split("==")[0].split(">=")[0].split("[")[0].strip()
if name in replacements:
version = pkg[len(name):] # e.g. "==1.20.1"
result.append(replacements[name] + version)
else:
result.append(pkg)
return result
def check_disk_space(path: str, min_bytes: int = 100 * 1024 * 1024) -> None:
"""Exit with a clear error if free disk space is below min_bytes."""
try:
@@ -94,6 +127,13 @@ def install_packages(bundle: dict, arch: str) -> None:
common_pkgs = packages_section.get("common", [])
arch_pkgs = packages_section.get(arch, [])
all_pkgs = common_pkgs + arch_pkgs
# On amd64 without GPU, swap GPU packages for CPU equivalents to avoid
# segfaults from onnxruntime-gpu / paddlepaddle-gpu trying to init CUDA.
if arch == "amd64" and not has_nvidia_gpu():
all_pkgs = cpu_fallback_packages(all_pkgs)
sys.stderr.write("No NVIDIA GPU detected — using CPU package variants\n")
sys.stderr.flush()
pip_flags = bundle.get("pipFlags", {})
post_install = bundle.get("postInstall", [])