File size: 7,584 Bytes
6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 4d77f4f 6a50e97 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
"""
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)
|