File size: 7,381 Bytes
6a50e97
 
 
 
 
 
 
 
 
 
4d77f4f
6a50e97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d77f4f
6a50e97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d77f4f
 
6a50e97
4d77f4f
6a50e97
4d77f4f
6a50e97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d77f4f
6a50e97
 
 
4d77f4f
 
6a50e97
 
 
 
4d77f4f
6a50e97
 
 
 
4d77f4f
360b135
4d77f4f
360b135
 
 
4d77f4f
360b135
 
 
 
 
 
4d77f4f
6a50e97
 
 
 
4d77f4f
360b135
6a50e97
 
 
4d77f4f
6a50e97
 
 
 
360b135
 
 
 
 
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
"""
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...")
    
    # Use HF Spaces default port (7860) or environment variable
    port = int(os.getenv("PORT", "7860"))
    print(f"πŸ“‘ Using port: {port}")
    
    # For Hugging Face Spaces, skip complex port logic and auto-build
    is_hf_space = os.getenv("SPACE_ID") is not None
    
    if not is_hf_space:
        # Local development: kill existing processes and auto-build
        kill_processes_on_port(port)
        
        # Auto-build frontend if needed
        if should_rebuild_frontend():
            print("πŸ”¨ Building frontend...")
            build_frontend()
        
        # Auto-open browser for local development
        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()
    
    try:
        # Start the backend server
        print(f"🌐 Starting server on http://{'0.0.0.0' if is_hf_space else 'localhost'}:{port}")
        print("🎯 Frontend and Backend integrated - ready to use!")
        
        # 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)