AcuarelaPortraits / frontend_builder.py
Alexramsal's picture
feat: add setup_frontend script to copy pre-built frontend files to daggr installation
8f39e99
"""
Frontend Builder - Automatically builds or copies the Daggr frontend.
This module checks if the frontend dist directory exists and builds/copies it as needed.
Useful for Hugging Face Spaces where the frontend might be in site-packages.
"""
import os
import subprocess
import sys
import shutil
import importlib.util
from pathlib import Path
def find_daggr_package_location():
"""Find where the daggr package is actually installed using importlib."""
try:
# Try to import daggr and find its location
spec = importlib.util.find_spec("daggr")
if spec and spec.origin:
daggr_init = Path(spec.origin)
daggr_root = daggr_init.parent
print(f"[DEBUG] Found daggr package at: {daggr_root}")
frontend_path = daggr_root / "frontend"
if frontend_path.exists():
print(f"[DEBUG] Found frontend in daggr package: {frontend_path}")
return frontend_path
except Exception as e:
print(f"[DEBUG] Error finding daggr with importlib: {e}")
return None
def find_daggr_site_packages():
"""Find daggr in Python site-packages using importlib."""
locations = []
# Try importlib first (most reliable)
pkg_frontend = find_daggr_package_location()
if pkg_frontend:
locations.append(pkg_frontend)
# Fallback: check common site-packages paths
try:
import site
for site_dir in site.getsitepackages():
daggr_path = Path(site_dir) / "daggr" / "frontend"
if daggr_path.exists():
print(f"[DEBUG] Found daggr frontend in site-packages: {daggr_path}")
locations.append(daggr_path)
except Exception as e:
print(f"[DEBUG] Error checking site-packages: {e}")
return locations
def get_frontend_dir():
"""Get the frontend directory path, checking local and site-packages locations."""
# Check local/mounted locations first
local_paths = [
Path("/app/daggr/frontend"), # Hugging Face default
Path.cwd() / "daggr" / "frontend", # Current working directory
Path(__file__).parent / "daggr" / "frontend", # Relative to this file
Path(__file__).parent.parent / "daggr" / "frontend", # Parent directory
Path(__file__).parent.parent.parent / "daggr" / "frontend", # Grandparent
]
print(f"[DEBUG] Looking for frontend directory...")
print(f"[DEBUG] Current working directory: {Path.cwd()}")
print(f"[DEBUG] Script location: {Path(__file__)}")
# Check local paths
for path in local_paths:
print(f"[DEBUG] Checking local: {path}")
if path.exists():
print(f"[DEBUG] Found at: {path}")
return path
# Check site-packages using importlib
print(f"[DEBUG] Checking site-packages with importlib...")
for path in find_daggr_site_packages():
if path.exists():
print(f"[DEBUG] Found in site-packages: {path}")
return path
print(f"[DEBUG] No frontend directory found")
return None
def copy_dist_from_source():
"""
Try to copy pre-built dist from source code to site-packages.
This is useful when the frontend is already built but in a different location.
"""
# Look for pre-built dist in common source locations
source_paths = [
Path("/workspace/AcuarelaPortraits/daggr/frontend/dist"),
Path("/workspace/temp_daggr_build/daggr/frontend/dist"),
Path("/home/user/AcuarelaPortraits/daggr/frontend/dist"),
Path.cwd() / "AcuarelaPortraits" / "daggr" / "frontend" / "dist",
Path.cwd() / "daggr" / "frontend" / "dist",
Path(__file__).parent / "daggr" / "frontend" / "dist",
]
print("[DEBUG] Looking for pre-built dist in source locations...")
for source_dist in source_paths:
print(f"[DEBUG] Checking source: {source_dist}")
if source_dist.exists() and (source_dist / "index.html").exists():
print(f"[DEBUG] Found pre-built dist at: {source_dist}")
# Try to find and copy to site-packages
frontend_dir = find_daggr_package_location()
if frontend_dir and frontend_dir.exists():
target_dist = frontend_dir / "dist"
print(f"[*] Copying pre-built frontend from {source_dist}")
print(f" to {target_dist}")
try:
# Remove existing dist if present
if target_dist.exists():
shutil.rmtree(target_dist)
# Copy the dist directory
shutil.copytree(source_dist, target_dist)
print(f"[OK] Frontend copied successfully")
return True
except Exception as e:
print(f"[WARNING] Failed to copy dist: {e}")
continue
print("[DEBUG] No pre-built dist found in source locations")
return False
def is_frontend_built():
"""Check if the frontend has been built (dist directory exists)."""
frontend_dir = get_frontend_dir()
if not frontend_dir:
print("[WARNING] Frontend directory not located")
return False
dist_dir = frontend_dir / "dist"
has_dist = dist_dir.exists()
has_index = (dist_dir / "index.html").exists() if has_dist else False
print(f"[DEBUG] Frontend dir: {frontend_dir}")
print(f"[DEBUG] Dist dir exists: {has_dist}")
print(f"[DEBUG] index.html exists: {has_index}")
return has_dist and has_index
def build_frontend():
"""Build the frontend using npm."""
frontend_dir = get_frontend_dir()
if not frontend_dir:
print("[WARNING] Frontend directory not found. Skipping frontend build.")
return False
print(f"[*] Building frontend at {frontend_dir}...")
try:
# Install dependencies if needed
node_modules = frontend_dir / "node_modules"
if not node_modules.exists():
print("[*] Installing npm dependencies...")
result = subprocess.run(
["npm", "install", "--legacy-peer-deps"],
cwd=str(frontend_dir),
capture_output=True,
text=True,
timeout=600, # 10 minute timeout
)
if result.returncode != 0:
print(f"[ERROR] npm install failed:")
print(result.stderr)
return False
print("[OK] Dependencies installed")
else:
print("[OK] node_modules already exists")
# Build the frontend
print("[*] Running npm run build...")
result = subprocess.run(
["npm", "run", "build"],
cwd=str(frontend_dir),
capture_output=True,
text=True,
timeout=600, # 10 minute timeout
)
if result.returncode != 0:
print(f"[ERROR] npm build failed:")
print(result.stderr)
return False
print("[OK] Frontend built successfully")
if result.stdout:
print(result.stdout[-500:] if len(result.stdout) > 500 else result.stdout)
# Verify dist was created
dist_dir = frontend_dir / "dist"
if not dist_dir.exists():
print("[ERROR] dist directory was not created after build")
return False
index_html = dist_dir / "index.html"
if not index_html.exists():
print("[ERROR] index.html not found in dist directory")
return False
print(f"[OK] Frontend ready at {dist_dir}")
return True
except FileNotFoundError as e:
print(f"[ERROR] npm not found: {e}. Make sure Node.js is installed.")
return False
except subprocess.TimeoutExpired:
print("[ERROR] Frontend build timed out (10 minutes exceeded)")
return False
except Exception as e:
print(f"[ERROR] Unexpected error building frontend: {e}")
import traceback
traceback.print_exc()
return False
def ensure_frontend_ready():
"""
Ensure the frontend is ready. Tries multiple strategies:
1. Check if already built
2. Copy from pre-built source
3. Build from source
Uses fallback UI if all fail.
"""
print("[*] Checking frontend status...")
if is_frontend_built():
print("[OK] Frontend is already built")
return True
print("[*] Frontend not found, trying to copy pre-built version...")
if copy_dist_from_source():
print("[OK] Pre-built frontend copied successfully")
return True
print("[*] Attempting to build frontend from source...")
if build_frontend():
print("[OK] Frontend build successful!")
return True
print("[WARNING] Frontend not available, using fallback UI")
return False
if __name__ == "__main__":
# Test the builder
if ensure_frontend_ready():
print("[OK] Frontend is ready!")
sys.exit(0)
else:
print("[WARNING] Frontend build/copy encountered issues")
sys.exit(1)