Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions backend/apps/dev_combined.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""

import logging
import os
import modal

from shared.config import get_environment, get_secrets
Expand All @@ -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 <name>"
)
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()]
)
Expand Down
35 changes: 27 additions & 8 deletions backend/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""CLI for serving Modal apps locally."""

import os
import signal
import subprocess
import sys
Expand Down Expand Up @@ -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 <name>

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 <name>")
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(
Expand All @@ -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()


Expand Down
2 changes: 1 addition & 1 deletion backend/services/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=["*"],
Expand Down
24 changes: 24 additions & 0 deletions frontend/streamlit/cli.py
Original file line number Diff line number Diff line change
@@ -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 <name>

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 <name>")
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)
16 changes: 13 additions & 3 deletions frontend/streamlit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>"
)

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}"
Expand All @@ -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"
Expand Down
Loading