edgellm / app.py
wu981526092's picture
add
6a50e97
raw
history blame
7.58 kB
"""
Edge LLM API - Main application entry point with integrated frontend
This entry point handles both backend API and frontend serving,
with automatic port detection and process management.
"""
import uvicorn
import socket
import subprocess
import sys
import os
import time
import signal
import webbrowser
from backend.main import app
def find_free_port(start_port=8000, max_attempts=50):
"""Find a free port starting from start_port"""
for port in range(start_port, start_port + max_attempts):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('localhost', port))
return port
except OSError:
continue
raise RuntimeError(f"Could not find a free port in range {start_port}-{start_port + max_attempts}")
def kill_processes_on_port(port):
"""Kill processes using the specified port"""
try:
if os.name == 'nt': # Windows
result = subprocess.run(['netstat', '-ano'], capture_output=True, text=True)
lines = result.stdout.split('\n')
for line in lines:
if f':{port}' in line and 'LISTENING' in line:
parts = line.split()
if len(parts) >= 5:
pid = parts[-1]
try:
subprocess.run(['taskkill', '/pid', pid, '/f'],
capture_output=True, check=True)
print(f"βœ… Killed process {pid} on port {port}")
except subprocess.CalledProcessError:
pass
else: # Unix/Linux/macOS
try:
result = subprocess.run(['lsof', '-ti', f':{port}'],
capture_output=True, text=True)
pids = result.stdout.strip().split('\n')
for pid in pids:
if pid:
subprocess.run(['kill', '-9', pid], capture_output=True)
print(f"βœ… Killed process {pid} on port {port}")
except subprocess.CalledProcessError:
pass
except Exception as e:
print(f"⚠️ Warning: Could not kill processes on port {port}: {e}")
def update_frontend_config(port):
"""Update frontend configuration to use the correct backend port"""
frontend_files = [
'frontend/src/pages/Models.tsx',
'frontend/src/pages/Playground.tsx'
]
for file_path in frontend_files:
if os.path.exists(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Update the baseUrl to use the current port (no longer needed with dynamic ports)
old_pattern = "window.location.hostname === 'localhost' ? `${window.location.protocol}//${window.location.host}` : ''"
new_pattern = old_pattern # No change needed since it's already dynamic
# No need to update frontend files since they use dynamic origins now
print(f"βœ… Frontend uses dynamic origins - no port updates needed")
except Exception as e:
print(f"⚠️ Warning: Could not update {file_path}: {e}")
def build_frontend():
"""Build the frontend if needed"""
if not os.path.exists('frontend/dist') or not os.listdir('frontend/dist'):
print("πŸ”¨ Building frontend...")
try:
os.chdir('frontend')
subprocess.run(['npm', 'install'], check=True, capture_output=True)
subprocess.run(['npm', 'run', 'build'], check=True, capture_output=True)
os.chdir('..')
print("βœ… Frontend built successfully")
except subprocess.CalledProcessError as e:
print(f"❌ Frontend build failed: {e}")
os.chdir('..')
return False
except FileNotFoundError:
print("❌ npm not found. Please install Node.js")
return False
return True
def should_rebuild_frontend():
"""Check if frontend needs to be rebuilt"""
# Check if build exists
if not (os.path.exists('frontend/dist/index.html') and os.path.exists('frontend/dist/assets')):
print("⚠️ Frontend build not found - will build it")
return True
# Check if source is newer than build
try:
dist_time = os.path.getmtime('frontend/dist/index.html')
# Check key source files
source_files = [
'frontend/src',
'frontend/package.json',
'frontend/vite.config.ts',
'frontend/tsconfig.json'
]
for src_path in source_files:
if os.path.exists(src_path):
if os.path.isdir(src_path):
# Check all files in directory
for root, dirs, files in os.walk(src_path):
for file in files:
file_path = os.path.join(root, file)
if os.path.getmtime(file_path) > dist_time:
print(f"πŸ”„ Source files changed - will rebuild frontend")
return True
else:
if os.path.getmtime(src_path) > dist_time:
print(f"πŸ”„ {src_path} changed - will rebuild frontend")
return True
print("βœ… Frontend build is up to date")
return False
except Exception as e:
print(f"⚠️ Error checking build status: {e} - will rebuild")
return True
def cleanup_handler(signum, frame):
"""Handle cleanup on exit"""
print("\nπŸ›‘ Shutting down Edge LLM...")
sys.exit(0)
if __name__ == "__main__":
# Set up signal handlers
signal.signal(signal.SIGINT, cleanup_handler)
signal.signal(signal.SIGTERM, cleanup_handler)
print("πŸš€ Starting Edge LLM with auto-build frontend...")
# Find available port
import os
original_port = int(os.getenv("PORT", "0")) # Use env var or auto-assign
if original_port == 0:
# Auto-assign a free port starting from 8000
original_port = find_free_port(8000)
print(f"πŸ” Auto-assigned port: {original_port}")
else:
kill_processes_on_port(original_port)
try:
port = find_free_port(original_port)
print(f"πŸ“‘ Using port: {port}")
if port != original_port:
print(f"⚠️ Port {original_port} was busy, switched to {port}")
update_frontend_config(port)
# Auto-build frontend if needed
if should_rebuild_frontend():
print("πŸ”¨ Building frontend...")
build_frontend()
# Start the backend server
print(f"🌐 Starting server on http://localhost:{port}")
print("🎯 Frontend and Backend integrated - ready to use!")
# Auto-open browser after a short delay
def open_browser():
time.sleep(2)
webbrowser.open(f'http://localhost:{port}')
import threading
browser_thread = threading.Thread(target=open_browser)
browser_thread.daemon = True
browser_thread.start()
# Start the server
uvicorn.run(app, host="0.0.0.0", port=port)
except Exception as e:
print(f"❌ Error starting server: {e}")
sys.exit(1)