diff --git a/scripts/frontend_server.py b/scripts/frontend_server.py
old mode 100755
new mode 100644
index 77dac02..95b4c04
--- a/scripts/frontend_server.py
+++ b/scripts/frontend_server.py
@@ -1,276 +1,12 @@
#!/usr/bin/env python3
-"""
-Simple HTTP server for saas-search frontend.
-"""
+"""Backward-compatible frontend server entrypoint."""
-import http.server
-import socketserver
-import os
-import sys
-import logging
-import time
-import urllib.request
-import urllib.error
-from collections import defaultdict, deque
-from pathlib import Path
-from dotenv import load_dotenv
-
-# Load .env file
-project_root = Path(__file__).parent.parent
-load_dotenv(project_root / '.env')
-
-# Get API_BASE_URL from environment(默认不注入,避免被旧 .env 覆盖同源策略)
-# 仅当显式设置 FRONTEND_INJECT_API_BASE_URL=1 时才注入 window.API_BASE_URL。
-API_BASE_URL = os.getenv('API_BASE_URL') or None
-INJECT_API_BASE_URL = os.getenv('FRONTEND_INJECT_API_BASE_URL', '0') == '1'
-# Backend proxy target for same-origin API forwarding
-BACKEND_PROXY_URL = os.getenv('BACKEND_PROXY_URL', 'http://127.0.0.1:6002').rstrip('/')
-
-# Change to frontend directory
-frontend_dir = os.path.join(os.path.dirname(__file__), '../frontend')
-os.chdir(frontend_dir)
-
-# FRONTEND_PORT is the canonical config; keep PORT as a secondary fallback.
-PORT = int(os.getenv('FRONTEND_PORT', os.getenv('PORT', 6003)))
-
-# Configure logging to suppress scanner noise
-logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
-
-class RateLimitingMixin:
- """Mixin for rate limiting requests by IP address."""
- request_counts = defaultdict(deque)
- rate_limit = 100 # requests per minute
- window = 60 # seconds
-
- @classmethod
- def is_rate_limited(cls, ip):
- now = time.time()
-
- # Clean old requests
- while cls.request_counts[ip] and cls.request_counts[ip][0] < now - cls.window:
- cls.request_counts[ip].popleft()
-
- # Check rate limit
- if len(cls.request_counts[ip]) > cls.rate_limit:
- return True
-
- cls.request_counts[ip].append(now)
- return False
-
-class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMixin):
- """Custom request handler with CORS support and robust error handling."""
-
- def _is_proxy_path(self, path: str) -> bool:
- """Return True for API paths that should be forwarded to backend service."""
- return path.startswith('/search/') or path.startswith('/admin/') or path.startswith('/indexer/')
-
- def _proxy_to_backend(self):
- """Proxy current request to backend service on the GPU server."""
- target_url = f"{BACKEND_PROXY_URL}{self.path}"
- method = self.command.upper()
-
- try:
- content_length = int(self.headers.get('Content-Length', '0'))
- except ValueError:
- content_length = 0
- body = self.rfile.read(content_length) if content_length > 0 else None
+from __future__ import annotations
- forward_headers = {}
- for key, value in self.headers.items():
- lk = key.lower()
- if lk in ('host', 'content-length', 'connection'):
- continue
- forward_headers[key] = value
-
- req = urllib.request.Request(
- target_url,
- data=body,
- headers=forward_headers,
- method=method,
- )
-
- try:
- with urllib.request.urlopen(req, timeout=30) as resp:
- resp_body = resp.read()
- self.send_response(resp.getcode())
- for header, value in resp.getheaders():
- lh = header.lower()
- if lh in ('transfer-encoding', 'connection', 'content-length'):
- continue
- self.send_header(header, value)
- self.end_headers()
- self.wfile.write(resp_body)
- except urllib.error.HTTPError as e:
- err_body = e.read() if hasattr(e, 'read') else b''
- self.send_response(e.code)
- if e.headers:
- for header, value in e.headers.items():
- lh = header.lower()
- if lh in ('transfer-encoding', 'connection', 'content-length'):
- continue
- self.send_header(header, value)
- self.end_headers()
- if err_body:
- self.wfile.write(err_body)
- except Exception as e:
- logging.error(f"Backend proxy error for {method} {self.path}: {e}")
- self.send_response(502)
- self.send_header('Content-Type', 'application/json; charset=utf-8')
- self.end_headers()
- self.wfile.write(b'{"error":"Bad Gateway: backend proxy failed"}')
-
- def do_GET(self):
- """Handle GET requests with API config injection."""
- path = self.path.split('?')[0]
-
- # Proxy API paths to backend first
- if self._is_proxy_path(path):
- self._proxy_to_backend()
- return
-
- # Route / to index.html
- if path == '/' or path == '':
- self.path = '/index.html' + (self.path.split('?', 1)[1] if '?' in self.path else '')
-
- # Inject API config for HTML files
- if self.path.endswith('.html'):
- self._serve_html_with_config()
- else:
- super().do_GET()
-
- def _serve_html_with_config(self):
- """Serve HTML with optional API_BASE_URL injected."""
- try:
- file_path = self.path.lstrip('/')
- if not os.path.exists(file_path):
- self.send_error(404)
- return
-
- with open(file_path, 'r', encoding='utf-8') as f:
- html = f.read()
-
- # 默认不注入 API_BASE_URL,避免历史 .env(如 http://xx:6002)覆盖同源调用。
- # 仅当 FRONTEND_INJECT_API_BASE_URL=1 且 API_BASE_URL 有值时才注入。
- if INJECT_API_BASE_URL and API_BASE_URL:
- config_script = f'\n '
- html = html.replace('