diff --git a/backend/apps/dev_combined.py b/backend/apps/dev_combined.py index d66a57c..0a32bea 100644 --- a/backend/apps/dev_combined.py +++ b/backend/apps/dev_combined.py @@ -17,6 +17,7 @@ """ import logging +import os import modal from shared.config import get_environment, get_secrets @@ -37,10 +38,25 @@ f"Current environment: {env}. Use separate apps for staging/prod." ) -logger.info("Starting Combined Dev App - all services in one app for local iteration") +# Get dev name prefix (required in dev mode to avoid naming conflicts) +# Only validate locally - Modal containers re-import but don't need DEV_NAME +# since the app name is already determined by the local import +dev_name = os.environ.get("DEV_NAME") +if not dev_name: + if modal.is_local(): + raise ValueError( + "DEV_NAME environment variable is required for dev mode. " + "Run with: uv run dev " + ) + else: + # Inside Modal container - use placeholder (app name already set) + dev_name = "container" + +app_name = f"{dev_name}-{env}-server" +logger.info(f"Starting Combined Dev App '{app_name}' - all services in one app for local iteration") app = modal.App( - name=f"{env}-server", + name=app_name, image=get_dev_image(), secrets=[get_secrets()] ) diff --git a/backend/cli.py b/backend/cli.py index 7227512..e6c80d5 100644 --- a/backend/cli.py +++ b/backend/cli.py @@ -1,5 +1,6 @@ """CLI for serving Modal apps locally.""" +import os import signal import subprocess import sys @@ -31,16 +32,33 @@ def _prefix_output(process, name, color): def serve_all(): """ Serve the combined dev app (all services in one). - + For local development, we use dev_combined.py which includes Server, Search, and Processing in a single Modal app. This allows hot-reload on all services without cross-app lookup issues. + + Usage: uv run dev + + The name parameter is required and will prefix the Modal app name + to avoid conflicts when multiple developers run dev instances. """ - print("Starting combined dev app (all services in one)...\n") - print(f" \033[32m●{RESET} dev-combined (server + search + processing)\n") + if len(sys.argv) < 2: + print("Error: Name parameter is required for dev mode.") + print("Usage: uv run dev ") + print("\nExample: uv run dev john") + print("This creates a Modal app named 'john-dev-server'") + sys.exit(1) + + dev_name = sys.argv[1] + + # Set environment variable for the Modal app to read + os.environ["DEV_NAME"] = dev_name + + print(f"Starting combined dev app for '{dev_name}'...\n") + print(f" \033[32m●{RESET} {dev_name}-dev-server (server + search + processing)\n") print("Note: For staging/prod, deploy individual apps separately.\n") print("-" * 60 + "\n") - + # Run with color-coded output prefixing color = "\033[32m" # Green for combined dev app process = subprocess.Popen( @@ -49,18 +67,19 @@ def serve_all(): stderr=subprocess.STDOUT, text=True, bufsize=1, + env={**os.environ, "DEV_NAME": dev_name}, ) - + # Handle graceful shutdown def signal_handler(sig, frame): process.terminate() sys.exit(0) - + signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - + # Stream output with color prefix - _prefix_output(process, "dev", color) + _prefix_output(process, dev_name, color) process.wait() diff --git a/backend/services/http_server.py b/backend/services/http_server.py index 7c9d72a..32854dd 100644 --- a/backend/services/http_server.py +++ b/backend/services/http_server.py @@ -92,7 +92,7 @@ def create_fastapi_app(self, processing_service_cls=None): self.fastapi_app = FastAPI(title="Clipabit Server") - # Add CORS middleware for testing + # Add CORS middleware self.fastapi_app.add_middleware( CORSMiddleware, allow_origins=["*"], diff --git a/frontend/streamlit/cli.py b/frontend/streamlit/cli.py index 6feaddf..86f2e02 100644 --- a/frontend/streamlit/cli.py +++ b/frontend/streamlit/cli.py @@ -1,7 +1,31 @@ +import os import subprocess +import sys from pathlib import Path def serve() -> None: + """ + Serve the Streamlit frontend. + + Usage: uv run dev + + The name parameter is required in dev mode and must match the backend + dev name to connect to the correct Modal app URLs. + """ + env = os.environ.get("ENVIRONMENT", "dev") + + if env == "dev": + if len(sys.argv) < 2: + print("Error: Name parameter is required for dev mode.") + print("Usage: uv run dev ") + print("\nExample: uv run dev john") + print("This connects to the Modal app named 'john-dev-server'") + sys.exit(1) + + dev_name = sys.argv[1] + os.environ["DEV_NAME"] = dev_name + print(f"Starting frontend for dev instance '{dev_name}'...") + app_path = Path(__file__).resolve().parent / "app.py" subprocess.run(["streamlit", "run", str(app_path)], check=True) diff --git a/frontend/streamlit/config.py b/frontend/streamlit/config.py index 0bf7d9d..c59c74d 100644 --- a/frontend/streamlit/config.py +++ b/frontend/streamlit/config.py @@ -23,7 +23,15 @@ class Config: if ENVIRONMENT not in ["dev", "prod", "staging"]: raise ValueError(f"Invalid ENVIRONMENT value: {ENVIRONMENT}. Must be one of: dev, prod, staging") - print(f"Running in {ENVIRONMENT} environment") + # Dev name prefix (required in dev mode to avoid naming conflicts) + DEV_NAME = os.environ.get("DEV_NAME", "") + if ENVIRONMENT == "dev" and not DEV_NAME: + raise ValueError( + "DEV_NAME environment variable is required for dev mode. " + "Run with: uv run dev " + ) + + print(f"Running in {ENVIRONMENT} environment" + (f" (dev instance: {DEV_NAME})" if DEV_NAME else "")) # Modal app name (matches backend app name) APP_NAME = f"clipabit-{ENVIRONMENT}" @@ -32,12 +40,14 @@ class Config: url_portion = "dev" if ENVIRONMENT == "dev" else "" url_portion2 = "-dev" if ENVIRONMENT == "dev" else "" + # App name prefix for dev mode (e.g., "john-dev" instead of "dev") + app_prefix = f"{DEV_NAME}-{ENVIRONMENT}" if ENVIRONMENT == "dev" else ENVIRONMENT # Server API URL (handles upload, status, videos, delete, cache) - SERVER_BASE_URL = f"https://clipabit01--{ENVIRONMENT}-server-{url_portion}server-asgi-app{url_portion2}.modal.run" + SERVER_BASE_URL = f"https://clipabit01--{app_prefix}-server-{url_portion}server-asgi-app{url_portion2}.modal.run" # Search API URL (in dev its server-searchservice-asgi-app, else its search-searchservice-asgi-app) - SEARCH_BASE_URL = f"https://clipabit01--{ENVIRONMENT}-{"server" if ENVIRONMENT == "dev" else "search"}-searchservice-asgi-app{url_portion2}.modal.run" + SEARCH_BASE_URL = f"https://clipabit01--{app_prefix}-{"server" if ENVIRONMENT == "dev" else "search"}-searchservice-asgi-app{url_portion2}.modal.run" # API Endpoints SERVER_UPLOAD_URL = f"{SERVER_BASE_URL}/upload"