updated with startup script

This commit is contained in:
Steve White 2025-06-17 16:26:55 -05:00
parent 758aa02053
commit 2af705ca43
17 changed files with 637 additions and 21 deletions

27
.env.example Normal file
View File

@ -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

11
.gitignore vendored
View File

@ -9,5 +9,16 @@ dialog_output/
__pycache__ __pycache__
projects/ 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.js dependencies
node_modules/ node_modules/

41
AGENTS.md Normal file
View File

@ -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

153
ENVIRONMENT_SETUP.md Normal file
View File

@ -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

19
backend/.env.example Normal file
View File

@ -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

View File

@ -1,20 +1,41 @@
import os
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv
# Determine PROJECT_ROOT dynamically. # Load environment variables from .env file
# Use the current project directory instead of mounted volume paths load_dotenv()
PROJECT_ROOT = Path("/Users/stwhite/CODE/chatterbox-ui").resolve()
# Speaker data paths # Project root - can be overridden by environment variable
SPEAKER_DATA_BASE_DIR = PROJECT_ROOT / "speaker_data" PROJECT_ROOT = Path(os.getenv("PROJECT_ROOT", Path(__file__).parent.parent.parent)).resolve()
SPEAKER_SAMPLES_DIR = SPEAKER_DATA_BASE_DIR / "speaker_samples"
SPEAKERS_YAML_FILE = SPEAKER_DATA_BASE_DIR / "speakers.yaml" # 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 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) # Final dialog output path (used by Dialog router and served by main app)
# These are stored within the 'backend' directory to be easily servable. # These are stored within the 'backend' directory to be easily servable.
DIALOG_OUTPUT_PARENT_DIR = PROJECT_ROOT / "backend" 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 # Alias for clarity and backward compatibility
DIALOG_OUTPUT_DIR = DIALOG_GENERATED_DIR 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)

View File

@ -16,11 +16,11 @@ app = FastAPI(
# In production, you should restrict this to specific origins # In production, you should restrict this to specific origins
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], # Allow all origins during development allow_origins=config.CORS_ORIGINS,
allow_credentials=False, # Set to False when using allow_origins=["*"] allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
expose_headers=["*"], expose_headers=["*"]
) )
# Include routers # Include routers

View File

@ -5,3 +5,4 @@ PyYAML
torch torch
torchaudio torchaudio
chatterbox-tts chatterbox-tts
python-dotenv

23
backend/start_server.py Normal file
View File

@ -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
)

10
frontend/.env.example Normal file
View File

@ -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

View File

@ -1,6 +1,8 @@
// frontend/js/api.js // 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. * Fetches the list of available speakers.

View File

@ -1,11 +1,5 @@
import { getSpeakers, addSpeaker, deleteSpeaker, generateDialog } from './api.js'; import { getSpeakers, addSpeaker, deleteSpeaker, generateDialog } from './api.js';
import { API_BASE_URL, API_BASE_URL_FOR_FILES } from './config.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';
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
console.log('DOM fully loaded and parsed'); console.log('DOM fully loaded and parsed');

35
frontend/js/config.js Normal file
View File

@ -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;

View File

@ -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...")

86
setup.py Normal file
View File

@ -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()

View File

@ -19,3 +19,12 @@ a6387c23-4ca4-42b5-8aaf-5699dbabbdf0:
6cf4d171-667d-4bc8-adbb-6d9b7c620cb8: 6cf4d171-667d-4bc8-adbb-6d9b7c620cb8:
name: Minnie name: Minnie
sample_path: speaker_samples/6cf4d171-667d-4bc8-adbb-6d9b7c620cb8.wav 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

138
start_servers.py Executable file
View File

@ -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()