#!/Users/stwhite/CODE/chatterbox-ui/.venv/bin/python """ Startup script that launches both the backend and frontend servers concurrently. """ import os import sys import time import signal import subprocess import threading from pathlib import Path # Try to load environment variables, but don't fail if dotenv is not available try: from dotenv import load_dotenv load_dotenv() except ImportError: print("python-dotenv not installed, using system environment variables only") # Configuration BACKEND_PORT = int(os.getenv('BACKEND_PORT', '8000')) BACKEND_HOST = os.getenv('BACKEND_HOST', '0.0.0.0') FRONTEND_PORT = int(os.getenv('FRONTEND_PORT', '8001')) FRONTEND_HOST = os.getenv('FRONTEND_HOST', '127.0.0.1') def find_free_port(start_port, host='127.0.0.1'): """Find a free port starting from start_port""" import socket for port in range(start_port, start_port + 10): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(1) result = sock.connect_ex((host, port)) if result != 0: # Port is free return port raise RuntimeError(f"Could not find a free port starting from {start_port}") def check_port_available(port, host='127.0.0.1'): """Check if a port is available""" import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(1) result = sock.connect_ex((host, port)) return result != 0 # True if port is free # Get project root directory PROJECT_ROOT = Path(__file__).parent.absolute() def run_backend(): """Run the backend FastAPI server""" os.chdir(PROJECT_ROOT / "backend") cmd = [ sys.executable, "-m", "uvicorn", "app.main:app", "--reload", f"--host={BACKEND_HOST}", f"--port={BACKEND_PORT}" ] print(f"\n{'='*50}") print(f"Starting Backend Server at http://{BACKEND_HOST}:{BACKEND_PORT}") print(f"API docs available at http://{BACKEND_HOST}:{BACKEND_PORT}/docs") print(f"{'='*50}\n") return subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) def run_frontend(): """Run the frontend development server""" frontend_dir = PROJECT_ROOT / "frontend" os.chdir(frontend_dir) cmd = [sys.executable, "start_dev_server.py"] env = os.environ.copy() env["VITE_DEV_SERVER_HOST"] = FRONTEND_HOST env["VITE_DEV_SERVER_PORT"] = str(FRONTEND_PORT) print(f"\n{'='*50}") print(f"Starting Frontend Server at http://{FRONTEND_HOST}:{FRONTEND_PORT}") print(f"{'='*50}\n") return subprocess.Popen( cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) def print_process_output(process, prefix): """Print process output with a prefix""" for line in iter(process.stdout.readline, ''): if not line: break print(f"{prefix} | {line}", end='') def main(): """Main function to start both servers""" print("\n🚀 Starting Chatterbox UI Development Environment") # Check and adjust ports if needed global BACKEND_PORT, FRONTEND_PORT if not check_port_available(BACKEND_PORT, '127.0.0.1'): original_backend_port = BACKEND_PORT BACKEND_PORT = find_free_port(BACKEND_PORT + 1) print(f"⚠️ Backend port {original_backend_port} is in use, using port {BACKEND_PORT} instead") if not check_port_available(FRONTEND_PORT, FRONTEND_HOST): original_frontend_port = FRONTEND_PORT FRONTEND_PORT = find_free_port(FRONTEND_PORT + 1) print(f"⚠️ Frontend port {original_frontend_port} is in use, using port {FRONTEND_PORT} instead") # Start the backend server backend_process = run_backend() # Give the backend a moment to start time.sleep(2) # Start the frontend server frontend_process = run_frontend() # Create threads to monitor and print output backend_monitor = threading.Thread( target=print_process_output, args=(backend_process, "BACKEND"), daemon=True ) frontend_monitor = threading.Thread( target=print_process_output, args=(frontend_process, "FRONTEND"), daemon=True ) backend_monitor.start() frontend_monitor.start() # Setup signal handling for graceful shutdown def signal_handler(sig, frame): print("\n\n🛑 Shutting down servers...") backend_process.terminate() frontend_process.terminate() # Threads are daemon, so they'll exit when the main thread exits print("✅ Servers stopped successfully") sys.exit(0) signal.signal(signal.SIGINT, signal_handler) # Print access information print("\n📋 Access Information:") print(f" • Frontend: http://{FRONTEND_HOST}:{FRONTEND_PORT}") print(f" • Backend API: http://{BACKEND_HOST}:{BACKEND_PORT}/api") print(f" • API Documentation: http://{BACKEND_HOST}:{BACKEND_PORT}/docs") print("\n⚠️ Press Ctrl+C to stop both servers\n") # Keep the main process running try: while True: time.sleep(1) except KeyboardInterrupt: signal_handler(None, None) if __name__ == "__main__": main()