From 2af705ca43bef2f72e311eff64dc565bdd35b773 Mon Sep 17 00:00:00 2001 From: Steve White Date: Tue, 17 Jun 2025 16:26:55 -0500 Subject: [PATCH] updated with startup script --- .env.example | 27 +++++++ .gitignore | 11 +++ AGENTS.md | 41 ++++++++++ ENVIRONMENT_SETUP.md | 153 +++++++++++++++++++++++++++++++++++ backend/.env.example | 19 +++++ backend/app/config.py | 39 ++++++--- backend/app/main.py | 8 +- backend/requirements.txt | 1 + backend/start_server.py | 23 ++++++ frontend/.env.example | 10 +++ frontend/js/api.js | 4 +- frontend/js/app.js | 8 +- frontend/js/config.js | 35 ++++++++ frontend/start_dev_server.py | 46 +++++++++++ setup.py | 86 ++++++++++++++++++++ speaker_data/speakers.yaml | 9 +++ start_servers.py | 138 +++++++++++++++++++++++++++++++ 17 files changed, 637 insertions(+), 21 deletions(-) create mode 100644 .env.example create mode 100644 AGENTS.md create mode 100644 ENVIRONMENT_SETUP.md create mode 100644 backend/.env.example create mode 100644 backend/start_server.py create mode 100644 frontend/.env.example create mode 100644 frontend/js/config.js create mode 100644 frontend/start_dev_server.py create mode 100644 setup.py create mode 100755 start_servers.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8d4454e --- /dev/null +++ b/.env.example @@ -0,0 +1,27 @@ +# Chatterbox TTS Application Configuration +# Copy this file to .env and adjust values for your environment + +# Project paths (adjust these for your system) +PROJECT_ROOT=/path/to/your/chatterbox-ui +SPEAKER_SAMPLES_DIR=${PROJECT_ROOT}/speaker_data/speaker_samples +TTS_TEMP_OUTPUT_DIR=${PROJECT_ROOT}/tts_temp_outputs +DIALOG_GENERATED_DIR=${PROJECT_ROOT}/backend/tts_generated_dialogs + +# Backend server configuration +BACKEND_HOST=0.0.0.0 +BACKEND_PORT=8000 +BACKEND_RELOAD=true + +# Frontend development server configuration +FRONTEND_HOST=127.0.0.1 +FRONTEND_PORT=8001 + +# API URLs (usually derived from backend configuration) +API_BASE_URL=http://localhost:8000 +API_BASE_URL_WITH_PREFIX=http://localhost:8000/api + +# CORS configuration (comma-separated list) +CORS_ORIGINS=http://localhost:8001,http://127.0.0.1:8001,http://localhost:3000,http://127.0.0.1:3000 + +# Device configuration for TTS model (auto, cpu, cuda, mps) +DEVICE=auto diff --git a/.gitignore b/.gitignore index 9d5781c..21748d3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,16 @@ dialog_output/ __pycache__ projects/ +# Environment files +.env +.env.local +.env.*.local +backend/.env +frontend/.env + +# Generated directories +tts_temp_outputs/ +backend/tts_generated_dialogs/ + # Node.js dependencies node_modules/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3af786c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# Agent Guidelines for Chatterbox-UI + +## Build/Test Commands +```bash +# Backend (FastAPI) +pip install -r backend/requirements.txt +uvicorn backend.app.main:app --reload --host 0.0.0.0 --port 8000 +python backend/run_api_test.py # Run all backend tests + +# Frontend +npm test # Run all frontend tests +npx jest frontend/tests/api.test.js # Run single test file +``` + +## Code Style Guidelines + +### Python +- Use type hints (from typing import Optional, List, etc.) +- Exception handling: Use try/except with specific exceptions +- Async/await for FastAPI endpoints and services +- Docstrings for functions and classes +- Use pathlib.Path for file operations +- Organize code into routers, models, and services + +### JavaScript +- ES6 modules with import/export +- JSDoc comments for functions +- Async/await for API calls +- Proper error handling with detailed messages +- Descriptive variable and function names +- Consistent error handling pattern in API calls + +### Error Handling +- Backend: Raise specific exceptions, use try/except/finally +- Frontend: Use try/catch with detailed error messages +- Always include error details in API responses + +### Naming Conventions +- Python: snake_case for variables/functions, PascalCase for classes +- JavaScript: camelCase for variables/functions +- Descriptive, intention-revealing names \ No newline at end of file diff --git a/ENVIRONMENT_SETUP.md b/ENVIRONMENT_SETUP.md new file mode 100644 index 0000000..6081211 --- /dev/null +++ b/ENVIRONMENT_SETUP.md @@ -0,0 +1,153 @@ +# Environment Configuration Guide + +This guide explains how to configure the Chatterbox TTS application for different environments using environment variables. + +## Quick Setup + +1. **Run the setup script:** + ```bash + python setup.py + ``` + +2. **Install backend dependencies:** + ```bash + cd backend + pip install -r requirements.txt + ``` + +3. **Start the servers:** + ```bash + # Terminal 1 - Backend + cd backend + python start_server.py + + # Terminal 2 - Frontend + cd frontend + python start_dev_server.py + ``` + +4. **Open the application:** + Open http://127.0.0.1:8001 in your browser + +## Manual Configuration + +### Environment Files + +The application uses environment variables for configuration. Three `.env` files control different aspects: + +- **Root `.env`**: Global configuration +- **`backend/.env`**: Backend-specific settings +- **`frontend/.env`**: Frontend-specific settings + +### Key Configuration Options + +#### Paths +- `PROJECT_ROOT`: Base directory for the project +- `SPEAKER_SAMPLES_DIR`: Directory containing speaker audio samples +- `TTS_TEMP_OUTPUT_DIR`: Temporary directory for TTS processing +- `DIALOG_GENERATED_DIR`: Directory for generated dialog audio files + +#### Server Configuration +- `HOST`: Backend server host (default: 0.0.0.0) +- `PORT`: Backend server port (default: 8000) +- `RELOAD`: Enable auto-reload for development (default: true) + +#### Frontend Configuration +- `VITE_API_BASE_URL`: Backend API base URL +- `VITE_DEV_SERVER_PORT`: Frontend development server port +- `VITE_DEV_SERVER_HOST`: Frontend development server host + +#### CORS Configuration +- `CORS_ORIGINS`: Comma-separated list of allowed origins + +#### Device Configuration +- `DEVICE`: Device for TTS model (auto, cpu, cuda, mps) + +## Example Configurations + +### Development Environment +```bash +# .env +PROJECT_ROOT=/Users/yourname/chatterbox-ui +BACKEND_PORT=8000 +FRONTEND_PORT=8001 +DEVICE=auto +CORS_ORIGINS=http://localhost:8001,http://127.0.0.1:8001 +``` + +### Production Environment +```bash +# .env +PROJECT_ROOT=/opt/chatterbox-ui +BACKEND_HOST=0.0.0.0 +BACKEND_PORT=8000 +FRONTEND_PORT=3000 +DEVICE=cuda +CORS_ORIGINS=https://yourdomain.com +``` + +### Docker Environment +```bash +# .env +PROJECT_ROOT=/app +BACKEND_HOST=0.0.0.0 +BACKEND_PORT=8000 +DEVICE=cpu +CORS_ORIGINS=http://localhost:3000 +``` + +## Troubleshooting + +### Common Issues + +1. **Permission Errors**: Ensure the `PROJECT_ROOT` directory is writable +2. **CORS Errors**: Check that your frontend URL is in `CORS_ORIGINS` +3. **Model Loading Errors**: Verify `DEVICE` setting matches your hardware +4. **Path Errors**: Ensure all path variables point to existing, accessible directories + +### Debugging + +Enable debug logging by setting: +```bash +export PYTHONPATH="${PYTHONPATH}:$(pwd)" +export DEBUG=1 +``` + +### Resetting Configuration + +To reset to defaults: +```bash +rm .env backend/.env frontend/.env +python setup.py +``` + +## File Structure + +``` +chatterbox-ui/ +├── .env # Global configuration +├── .env.example # Template for global config +├── setup.py # Automated setup script +├── backend/ +│ ├── .env # Backend configuration +│ ├── .env.example # Template for backend config +│ ├── start_server.py # Backend startup script +│ └── app/ +│ └── config.py # Configuration loader +├── frontend/ +│ ├── .env # Frontend configuration +│ ├── .env.example # Template for frontend config +│ ├── start_dev_server.py # Frontend dev server +│ └── js/ +│ └── config.js # Frontend configuration loader +└── speaker_data/ + └── speaker_samples/ # Speaker audio files +``` + +## Security Notes + +- Never commit `.env` files to version control +- Use strong, unique values for production +- Restrict CORS origins in production +- Use HTTPS in production environments +- Regularly update dependencies diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..14791eb --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,19 @@ +# Backend Configuration +# Copy this file to .env and adjust values as needed + +# Project paths +PROJECT_ROOT=/Users/stwhite/CODE/chatterbox-ui +SPEAKER_SAMPLES_DIR=${PROJECT_ROOT}/speaker_data/speaker_samples +TTS_TEMP_OUTPUT_DIR=${PROJECT_ROOT}/tts_temp_outputs +DIALOG_GENERATED_DIR=${PROJECT_ROOT}/backend/tts_generated_dialogs + +# Server configuration +HOST=0.0.0.0 +PORT=8000 +RELOAD=true + +# CORS configuration +CORS_ORIGINS=http://localhost:8001,http://127.0.0.1:8001,http://localhost:3000,http://127.0.0.1:3000 + +# Device configuration (auto, cpu, cuda, mps) +DEVICE=auto diff --git a/backend/app/config.py b/backend/app/config.py index a9c2f9a..2812436 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -1,20 +1,41 @@ +import os from pathlib import Path +from dotenv import load_dotenv -# Determine PROJECT_ROOT dynamically. -# Use the current project directory instead of mounted volume paths -PROJECT_ROOT = Path("/Users/stwhite/CODE/chatterbox-ui").resolve() +# Load environment variables from .env file +load_dotenv() -# Speaker data paths -SPEAKER_DATA_BASE_DIR = PROJECT_ROOT / "speaker_data" -SPEAKER_SAMPLES_DIR = SPEAKER_DATA_BASE_DIR / "speaker_samples" -SPEAKERS_YAML_FILE = SPEAKER_DATA_BASE_DIR / "speakers.yaml" +# Project root - can be overridden by environment variable +PROJECT_ROOT = Path(os.getenv("PROJECT_ROOT", Path(__file__).parent.parent.parent)).resolve() + +# Directory paths +SPEAKER_DATA_BASE_DIR = Path(os.getenv("SPEAKER_DATA_BASE_DIR", str(PROJECT_ROOT / "speaker_data"))) +SPEAKER_SAMPLES_DIR = Path(os.getenv("SPEAKER_SAMPLES_DIR", str(SPEAKER_DATA_BASE_DIR / "speaker_samples"))) +SPEAKERS_YAML_FILE = Path(os.getenv("SPEAKERS_YAML_FILE", str(SPEAKER_DATA_BASE_DIR / "speakers.yaml"))) # TTS temporary output path (used by DialogProcessorService) -TTS_TEMP_OUTPUT_DIR = PROJECT_ROOT / "tts_temp_outputs" +TTS_TEMP_OUTPUT_DIR = Path(os.getenv("TTS_TEMP_OUTPUT_DIR", str(PROJECT_ROOT / "tts_temp_outputs"))) # Final dialog output path (used by Dialog router and served by main app) # These are stored within the 'backend' directory to be easily servable. DIALOG_OUTPUT_PARENT_DIR = PROJECT_ROOT / "backend" -DIALOG_GENERATED_DIR = DIALOG_OUTPUT_PARENT_DIR / "tts_generated_dialogs" +DIALOG_GENERATED_DIR = Path(os.getenv("DIALOG_GENERATED_DIR", str(DIALOG_OUTPUT_PARENT_DIR / "tts_generated_dialogs"))) + # Alias for clarity and backward compatibility DIALOG_OUTPUT_DIR = DIALOG_GENERATED_DIR + +# Server configuration +HOST = os.getenv("HOST", "0.0.0.0") +PORT = int(os.getenv("PORT", "8000")) +RELOAD = os.getenv("RELOAD", "true").lower() == "true" + +# CORS configuration +CORS_ORIGINS = [origin.strip() for origin in os.getenv("CORS_ORIGINS", "http://localhost:8001,http://127.0.0.1:8001").split(",")] + +# Device configuration +DEVICE = os.getenv("DEVICE", "auto") + +# Ensure directories exist +SPEAKER_SAMPLES_DIR.mkdir(parents=True, exist_ok=True) +TTS_TEMP_OUTPUT_DIR.mkdir(parents=True, exist_ok=True) +DIALOG_GENERATED_DIR.mkdir(parents=True, exist_ok=True) diff --git a/backend/app/main.py b/backend/app/main.py index 788428a..7b57297 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -16,11 +16,11 @@ app = FastAPI( # In production, you should restrict this to specific origins app.add_middleware( CORSMiddleware, - allow_origins=["*"], # Allow all origins during development - allow_credentials=False, # Set to False when using allow_origins=["*"] - allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allow_origins=config.CORS_ORIGINS, + allow_credentials=False, + allow_methods=["*"], allow_headers=["*"], - expose_headers=["*"], + expose_headers=["*"] ) # Include routers diff --git a/backend/requirements.txt b/backend/requirements.txt index 082887f..41f93af 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -5,3 +5,4 @@ PyYAML torch torchaudio chatterbox-tts +python-dotenv diff --git a/backend/start_server.py b/backend/start_server.py new file mode 100644 index 0000000..3b7a82c --- /dev/null +++ b/backend/start_server.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +""" +Backend server startup script that uses environment variables from config. +""" + +import uvicorn +from app import config + +if __name__ == "__main__": + print(f"Starting Chatterbox TTS Backend Server...") + print(f"Host: {config.HOST}") + print(f"Port: {config.PORT}") + print(f"Reload: {config.RELOAD}") + print(f"CORS Origins: {config.CORS_ORIGINS}") + print(f"Project Root: {config.PROJECT_ROOT}") + print(f"Device: {config.DEVICE}") + + uvicorn.run( + "app.main:app", + host=config.HOST, + port=config.PORT, + reload=config.RELOAD + ) diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..4bc5e67 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,10 @@ +# Frontend Configuration +# Copy this file to .env and adjust values as needed + +# Backend API configuration +VITE_API_BASE_URL=http://localhost:8000 +VITE_API_BASE_URL_WITH_PREFIX=http://localhost:8000/api + +# Development server configuration +VITE_DEV_SERVER_PORT=8001 +VITE_DEV_SERVER_HOST=127.0.0.1 diff --git a/frontend/js/api.js b/frontend/js/api.js index efeeaba..85549a0 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -1,6 +1,8 @@ // frontend/js/api.js -const API_BASE_URL = 'http://localhost:8000/api'; // Assuming backend runs on port 8000 +import { API_BASE_URL_WITH_PREFIX } from './config.js'; + +const API_BASE_URL = API_BASE_URL_WITH_PREFIX; /** * Fetches the list of available speakers. diff --git a/frontend/js/app.js b/frontend/js/app.js index 7210552..9eda494 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -1,11 +1,5 @@ import { getSpeakers, addSpeaker, deleteSpeaker, generateDialog } from './api.js'; - -const API_BASE_URL = 'http://localhost:8000'; // Assuming backend runs here - -// This should match the base URL from which FastAPI serves static files -// If your main app is at http://localhost:8000, and static files are served from /generated_audio relative to that, -// then this should be http://localhost:8000. The backend will return paths like /generated_audio/... -const API_BASE_URL_FOR_FILES = 'http://localhost:8000'; +import { API_BASE_URL, API_BASE_URL_FOR_FILES } from './config.js'; document.addEventListener('DOMContentLoaded', async () => { console.log('DOM fully loaded and parsed'); diff --git a/frontend/js/config.js b/frontend/js/config.js new file mode 100644 index 0000000..e8dbc5e --- /dev/null +++ b/frontend/js/config.js @@ -0,0 +1,35 @@ +// Frontend Configuration +// This file handles environment variable configuration for the frontend + +// Get environment variables (these would be injected by a build tool like Vite) +// For now, we'll use defaults that can be overridden +const getEnvVar = (name, defaultValue) => { + // In a real Vite setup, this would be import.meta.env[name] + // For now, we'll check if there's a global config object or use defaults + if (typeof window !== 'undefined' && window.APP_CONFIG && window.APP_CONFIG[name]) { + return window.APP_CONFIG[name]; + } + return defaultValue; +}; + +// API Configuration +export const API_BASE_URL = getEnvVar('VITE_API_BASE_URL', 'http://localhost:8000'); +export const API_BASE_URL_WITH_PREFIX = getEnvVar('VITE_API_BASE_URL_WITH_PREFIX', 'http://localhost:8000/api'); + +// For file serving (same as API_BASE_URL since files are served from the same server) +export const API_BASE_URL_FOR_FILES = API_BASE_URL; + +// Development server configuration +export const DEV_SERVER_PORT = getEnvVar('VITE_DEV_SERVER_PORT', '8001'); +export const DEV_SERVER_HOST = getEnvVar('VITE_DEV_SERVER_HOST', '127.0.0.1'); + +// Export all config as a single object for convenience +export const CONFIG = { + API_BASE_URL, + API_BASE_URL_WITH_PREFIX, + API_BASE_URL_FOR_FILES, + DEV_SERVER_PORT, + DEV_SERVER_HOST +}; + +export default CONFIG; diff --git a/frontend/start_dev_server.py b/frontend/start_dev_server.py new file mode 100644 index 0000000..1638097 --- /dev/null +++ b/frontend/start_dev_server.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Simple development server for the frontend that reads configuration from .env +""" + +import os +import http.server +import socketserver +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 +PORT = int(os.getenv('VITE_DEV_SERVER_PORT', '8001')) +HOST = os.getenv('VITE_DEV_SERVER_HOST', '127.0.0.1') + +# Change to frontend directory +frontend_dir = Path(__file__).parent +os.chdir(frontend_dir) + +class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + def end_headers(self): + # Add CORS headers for development + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', '*') + super().end_headers() + +if __name__ == "__main__": + print(f"Starting Frontend Development Server...") + print(f"Host: {HOST}") + print(f"Port: {PORT}") + print(f"Serving from: {frontend_dir}") + print(f"Open: http://{HOST}:{PORT}") + + with socketserver.TCPServer((HOST, PORT), MyHTTPRequestHandler) as httpd: + print(f"Server running at http://{HOST}:{PORT}/") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nShutting down server...") diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9f7c88e --- /dev/null +++ b/setup.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +""" +Setup script for Chatterbox TTS Application +This script helps configure the application for different environments. +""" + +import os +import shutil +from pathlib import Path + +def setup_environment(): + """Setup environment configuration files""" + project_root = Path(__file__).parent + + print("🔧 Setting up Chatterbox TTS Application...") + + # Create .env file if it doesn't exist + env_file = project_root / ".env" + env_example = project_root / ".env.example" + + if not env_file.exists() and env_example.exists(): + print("📝 Creating .env file from .env.example...") + shutil.copy(env_example, env_file) + + # Update PROJECT_ROOT in .env to current directory + with open(env_file, 'r') as f: + content = f.read() + + content = content.replace('/path/to/your/chatterbox-ui', str(project_root)) + + with open(env_file, 'w') as f: + f.write(content) + + print(f"✅ Created .env file with PROJECT_ROOT set to: {project_root}") + else: + print("ℹ️ .env file already exists") + + # Setup backend .env + backend_env = project_root / "backend" / ".env" + backend_env_example = project_root / "backend" / ".env.example" + + if not backend_env.exists() and backend_env_example.exists(): + print("📝 Creating backend/.env file...") + shutil.copy(backend_env_example, backend_env) + + # Update PROJECT_ROOT in backend .env + with open(backend_env, 'r') as f: + content = f.read() + + content = content.replace('/Users/stwhite/CODE/chatterbox-ui', str(project_root)) + + with open(backend_env, 'w') as f: + f.write(content) + + print(f"✅ Created backend/.env file") + + # Setup frontend .env + frontend_env = project_root / "frontend" / ".env" + frontend_env_example = project_root / "frontend" / ".env.example" + + if not frontend_env.exists() and frontend_env_example.exists(): + print("📝 Creating frontend/.env file...") + shutil.copy(frontend_env_example, frontend_env) + print(f"✅ Created frontend/.env file") + + # Create necessary directories + directories = [ + project_root / "speaker_data" / "speaker_samples", + project_root / "tts_temp_outputs", + project_root / "backend" / "tts_generated_dialogs" + ] + + for directory in directories: + directory.mkdir(parents=True, exist_ok=True) + print(f"📁 Created directory: {directory}") + + print("\n🎉 Setup complete!") + print("\n📋 Next steps:") + print("1. Review and adjust the .env files as needed") + print("2. Install backend dependencies: cd backend && pip install -r requirements.txt") + print("3. Start backend server: cd backend && python start_server.py") + print("4. Start frontend server: cd frontend && python start_dev_server.py") + print("5. Open http://127.0.0.1:8001 in your browser") + +if __name__ == "__main__": + setup_environment() diff --git a/speaker_data/speakers.yaml b/speaker_data/speakers.yaml index 3331196..2c608f1 100644 --- a/speaker_data/speakers.yaml +++ b/speaker_data/speakers.yaml @@ -19,3 +19,12 @@ a6387c23-4ca4-42b5-8aaf-5699dbabbdf0: 6cf4d171-667d-4bc8-adbb-6d9b7c620cb8: name: Minnie sample_path: speaker_samples/6cf4d171-667d-4bc8-adbb-6d9b7c620cb8.wav +f1377dc6-aec5-42fc-bea7-98c0be49c48e: + name: Glinda + sample_path: speaker_samples/f1377dc6-aec5-42fc-bea7-98c0be49c48e.wav +dd3552d9-f4e8-49ed-9892-f9e67afcf23c: + name: emily + sample_path: speaker_samples/dd3552d9-f4e8-49ed-9892-f9e67afcf23c.wav +2cdd6d3d-c533-44bf-a5f6-cc83bd089d32: + name: Grace + sample_path: speaker_samples/2cdd6d3d-c533-44bf-a5f6-cc83bd089d32.wav diff --git a/start_servers.py b/start_servers.py new file mode 100755 index 0000000..cf908c7 --- /dev/null +++ b/start_servers.py @@ -0,0 +1,138 @@ +#!/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') + +# 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") + + # 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()