Skip to content

Commit 3f8186d

Browse files
authored
Modernize frontend, modularize routers, and improve logging reliability (#292)
* style: remove nested logs and leading spaces from logger output messages * refactor: Remove leading spaces from log messages for cleaner output. * fix: improve reliability of chat message input and sending in page controller. * feat: enhance logging by adding new tags, silencing verbose modules, and improving test coverage for log messages * refactor: deprecate legacy frontend, add React SPA and modularize routers
1 parent ebeed2d commit 3f8186d

188 files changed

Lines changed: 25410 additions & 9149 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ ENABLE_THINKING_BUDGET=false
126126
DEFAULT_THINKING_BUDGET=8192
127127

128128
# 默认思考等级 (Thinking Level)
129-
# Options: high, low (仅特定模型支持)
130-
DEFAULT_THINKING_LEVEL=high
129+
# Gemini 3 Pro 默认等级 (Options: high, low)
130+
DEFAULT_THINKING_LEVEL_PRO=high
131+
# Gemini 3 Flash 默认等级 (Options: high, medium, low, minimal)
132+
DEFAULT_THINKING_LEVEL_FLASH=high
131133

132134
# 工具配置
133135
# 是否默认启用 Google Search

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,8 @@ pyright_output.txt
269269
pyright_utils_full.txt
270270
temp_*.txt
271271
temp_*.md
272-
utils_errors.txt
272+
utils_errors.txt
273+
# React frontend build artifacts and dependencies
274+
static/frontend/node_modules/
275+
static/frontend/coverage/
276+
static/frontend/dist/

api_utils/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
cancel_request,
2020
chat_completions,
2121
get_api_info,
22-
get_css,
23-
get_js,
2422
get_queue_status,
2523
health_check,
2624
list_models,
@@ -49,10 +47,8 @@
4947
__all__ = [
5048
# 应用初始化
5149
"create_app",
52-
# 路由处理器
50+
# 路由処理器
5351
"read_index",
54-
"get_css",
55-
"get_js",
5652
"get_api_info",
5753
"health_check",
5854
"list_models",

api_utils/app.py

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import multiprocessing
77
import queue # <-- FIX: Added missing import for queue.Empty
88
import sys
9+
import time
910
from asyncio import Lock, Queue
1011
from contextlib import asynccontextmanager
1112
from typing import Any, Awaitable, Callable
@@ -62,7 +63,7 @@ def _initialize_globals() -> None:
6263
state.model_switching_lock = Lock()
6364
state.params_cache_lock = Lock()
6465
auth_utils.initialize_keys()
65-
state.logger.info("API keys and global locks initialized.")
66+
state.logger.debug("API keys and global locks initialized.")
6667

6768

6869
def _initialize_proxy_settings() -> None:
@@ -79,11 +80,11 @@ def _initialize_proxy_settings() -> None:
7980
state.PLAYWRIGHT_PROXY_SETTINGS = {"server": proxy_server_env}
8081
if NO_PROXY_ENV:
8182
state.PLAYWRIGHT_PROXY_SETTINGS["bypass"] = NO_PROXY_ENV.replace(",", ";")
82-
state.logger.info(
83-
f"Playwright proxy settings configured: {state.PLAYWRIGHT_PROXY_SETTINGS}"
83+
state.logger.debug(
84+
f"[代理] 已配置: {state.PLAYWRIGHT_PROXY_SETTINGS.get('server', 'N/A')}"
8485
)
8586
else:
86-
state.logger.info("No proxy configured for Playwright.")
87+
state.logger.debug("[代理] 未配置")
8788

8889

8990
async def _start_stream_proxy() -> None:
@@ -96,24 +97,24 @@ async def _start_stream_proxy() -> None:
9697
or get_environment_variable("HTTPS_PROXY")
9798
or get_environment_variable("HTTP_PROXY")
9899
)
99-
state.logger.info(
100-
f"Starting STREAM proxy on port {port} with upstream proxy: {STREAM_PROXY_SERVER_ENV}"
101-
)
100+
state.logger.info(f"[系统] 启动流式代理服务 (端口: {port})")
102101
state.STREAM_QUEUE = multiprocessing.Queue()
103102
state.STREAM_PROCESS = multiprocessing.Process(
104103
target=stream.start,
105104
args=(state.STREAM_QUEUE, port, STREAM_PROXY_SERVER_ENV),
106105
)
107106
state.STREAM_PROCESS.start()
108-
state.logger.info("STREAM proxy process started. Waiting for 'READY' signal...")
107+
state.logger.debug(
108+
"STREAM proxy process started. Waiting for 'READY' signal..."
109+
)
109110

110111
# Wait for the proxy to be ready
111112
try:
112113
# Use asyncio.to_thread to wait for the blocking queue.get()
113114
# Set a timeout to avoid waiting forever
114115
ready_signal = await asyncio.to_thread(state.STREAM_QUEUE.get, timeout=15)
115116
if ready_signal == "READY":
116-
state.logger.info("Received 'READY' signal from STREAM proxy.")
117+
state.logger.info("[系统] 流式代理就绪")
117118
else:
118119
state.logger.warning(
119120
f"Received unexpected signal from proxy: {ready_signal}"
@@ -129,10 +130,9 @@ async def _initialize_browser_and_page() -> None:
129130
"""Initialize Playwright browser connection and page."""
130131
from playwright.async_api import async_playwright
131132

132-
state.logger.info("Starting Playwright...")
133+
state.logger.debug("[内核] 正在启动 Playwright...")
133134
state.playwright_manager = await async_playwright().start()
134135
state.is_playwright_ready = True
135-
state.logger.info("Playwright started.")
136136

137137
ws_endpoint = get_environment_variable("CAMOUFOX_WS_ENDPOINT")
138138
launch_mode = get_environment_variable("LAUNCH_MODE", "unknown")
@@ -141,20 +141,20 @@ async def _initialize_browser_and_page() -> None:
141141
raise ValueError("CAMOUFOX_WS_ENDPOINT environment variable is missing.")
142142

143143
if ws_endpoint:
144-
state.logger.info(f"Connecting to browser at: {ws_endpoint}")
144+
state.logger.debug(f"Connecting to browser at: {ws_endpoint}")
145145
state.browser_instance = await state.playwright_manager.firefox.connect(
146146
ws_endpoint, timeout=30000
147147
)
148148
state.is_browser_connected = True
149-
state.logger.info(f"Connected to browser: {state.browser_instance.version}")
149+
state.logger.info(f"[浏览器] 已连接 (版本: {state.browser_instance.version})")
150150

151151
state.page_instance, state.is_page_ready = await _initialize_page_logic(
152152
state.browser_instance
153153
)
154154
if state.is_page_ready:
155155
await _handle_initial_model_state_and_storage(state.page_instance)
156156
await enable_temporary_chat_mode(state.page_instance)
157-
state.logger.info("Page initialized successfully.")
157+
state.logger.info("[系统] 页面初始化成功")
158158
else:
159159
state.logger.error("Page initialization failed.")
160160

@@ -165,7 +165,7 @@ async def _initialize_browser_and_page() -> None:
165165
async def _shutdown_resources() -> None:
166166
"""Gracefully shut down all resources."""
167167
logger = state.logger
168-
logger.info("Shutting down resources...")
168+
logger.debug("[系统] 正在关闭资源...")
169169

170170
# Signal all streaming generators to exit immediately
171171
state.should_exit = True
@@ -185,29 +185,29 @@ async def _shutdown_resources() -> None:
185185
state.STREAM_QUEUE.join_thread()
186186
except Exception:
187187
pass
188-
logger.info("STREAM proxy terminated.")
188+
logger.debug("STREAM proxy terminated.")
189189

190190
if state.worker_task and not state.worker_task.done():
191-
logger.info("Cancelling worker task...")
191+
logger.debug("Cancelling worker task...")
192192
state.worker_task.cancel()
193193
try:
194194
await asyncio.wait_for(state.worker_task, timeout=2.0)
195-
logger.info("Worker task cancelled.")
195+
logger.debug("Worker task cancelled.")
196196
except asyncio.TimeoutError:
197197
logger.warning("Worker task did not respond to cancellation within 2s.")
198198
except asyncio.CancelledError:
199-
logger.info("Worker task cancelled.")
199+
logger.debug("Worker task cancelled.")
200200

201201
if state.page_instance:
202202
await _close_page_logic()
203203

204204
if state.browser_instance and state.browser_instance.is_connected():
205205
await state.browser_instance.close()
206-
logger.info("Browser connection closed.")
206+
logger.debug("Browser connection closed.")
207207

208208
if state.playwright_manager:
209209
await state.playwright_manager.stop()
210-
logger.info("Playwright stopped.")
210+
logger.debug("Playwright stopped.")
211211

212212

213213
@asynccontextmanager
@@ -225,7 +225,8 @@ async def lifespan(app: FastAPI):
225225
load_excluded_models(EXCLUDED_MODELS_FILENAME)
226226

227227
state.is_initializing = True
228-
logger.info("Starting AI Studio Proxy Server...")
228+
startup_start_time = time.time()
229+
logger.info("[系统] AI Studio 代理服务器启动中...")
229230

230231
try:
231232
await _start_stream_proxy()
@@ -234,11 +235,12 @@ async def lifespan(app: FastAPI):
234235
launch_mode = get_environment_variable("LAUNCH_MODE", "unknown")
235236
if state.is_page_ready or launch_mode == "direct_debug_no_browser":
236237
state.worker_task = asyncio.create_task(queue_worker())
237-
logger.info("Request processing worker started.")
238+
logger.debug("Request processing worker started.")
238239
else:
239240
raise RuntimeError("Failed to initialize browser/page, worker not started.")
240241

241-
logger.info("Server startup complete.")
242+
startup_duration = time.time() - startup_start_time
243+
logger.info(f"[系统] 服务器启动完成 (耗时: {startup_duration:.2f}秒)")
242244
state.is_initializing = False
243245
yield
244246
except asyncio.CancelledError:
@@ -248,11 +250,11 @@ async def lifespan(app: FastAPI):
248250
await _shutdown_resources()
249251
raise RuntimeError(f"Application startup failed: {e}") from e
250252
finally:
251-
logger.info("Shutting down server...")
253+
logger.info("[系统] 服务器关闭中...")
252254
await _shutdown_resources()
253255
restore_original_streams(initial_stdout, initial_stderr)
254256
restore_original_streams(*original_streams)
255-
logger.info("Server shutdown complete.")
257+
logger.info("[系统] 服务器已关闭")
256258

257259

258260
class APIKeyAuthMiddleware(BaseHTTPMiddleware):
@@ -330,24 +332,26 @@ def create_app() -> FastAPI:
330332

331333
from .routers import (
332334
add_api_key,
335+
auth_files_router,
333336
cancel_request,
334337
chat_completions,
335338
delete_api_key,
336339
get_api_info,
337340
get_api_keys,
338-
get_css,
339-
get_js,
340341
get_queue_status,
341342
health_check,
342343
list_models,
344+
model_capabilities_router,
345+
ports_router,
346+
proxy_router,
343347
read_index,
348+
serve_react_assets,
344349
test_api_key,
345350
websocket_log_endpoint,
346351
)
347352

348353
app.get("/", response_class=FileResponse)(read_index)
349-
app.get("/webui.css")(get_css)
350-
app.get("/webui.js")(get_js)
354+
app.get("/assets/{filename:path}")(serve_react_assets) # React built assets
351355
app.get("/api/info")(get_api_info)
352356
app.get("/health")(health_check)
353357
app.get("/v1/models")(list_models)
@@ -356,6 +360,20 @@ def create_app() -> FastAPI:
356360
app.get("/v1/queue")(get_queue_status)
357361
app.websocket("/ws/logs")(websocket_log_endpoint)
358362

363+
# Model capabilities endpoint (single source of truth)
364+
app.include_router(model_capabilities_router)
365+
366+
# Proxy, auth, and port management routers
367+
app.include_router(proxy_router)
368+
app.include_router(auth_files_router)
369+
app.include_router(ports_router)
370+
371+
# Server control and helper routers
372+
from api_utils.routers import helper_router, server_router
373+
374+
app.include_router(server_router)
375+
app.include_router(helper_router)
376+
359377
# API密钥管理端点
360378
app.get("/api/keys")(get_api_keys)
361379
app.post("/api/keys")(add_api_key)

api_utils/context_init.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ async def initialize_request_context(
1212
from api_utils.server_state import state
1313

1414
set_request_id(req_id)
15-
state.logger.info("开始处理请求...")
16-
state.logger.info(f" 请求参数 - Model: {request.model}, Stream: {request.stream}")
15+
state.logger.debug(
16+
f"[Request] 参数: Model={request.model}, Stream={request.stream}"
17+
)
1718

1819
context: RequestContext = cast(
1920
RequestContext,

api_utils/model_switching.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ async def analyze_model_requirements(
1818

1919
if requested_model and requested_model != proxy_model_name:
2020
requested_model_id = requested_model.split("/")[-1]
21-
logger.info(f"请求使用模型: {requested_model_id}")
2221

2322
if parsed_model_list:
2423
valid_model_ids = [
@@ -35,8 +34,8 @@ async def analyze_model_requirements(
3534
context["model_id_to_use"] = requested_model_id
3635
if current_ai_studio_model_id != requested_model_id:
3736
context["needs_model_switching"] = True
38-
logger.info(
39-
f"需要切换模型: 当前={current_ai_studio_model_id} -> 目标={requested_model_id}"
37+
logger.debug(
38+
f"[Model] 判定需切换: {current_ai_studio_model_id} -> {requested_model_id}"
4039
)
4140

4241
return context
@@ -60,17 +59,13 @@ async def handle_model_switching(
6059

6160
async with model_switching_lock:
6261
if state.current_ai_studio_model_id != model_id_to_use:
63-
logger.info(
64-
f"准备切换模型: {state.current_ai_studio_model_id} -> {model_id_to_use}"
65-
)
6662
from browser_utils import switch_ai_studio_model
6763

6864
switch_success = await switch_ai_studio_model(page, model_id_to_use, req_id)
6965
if switch_success:
7066
state.current_ai_studio_model_id = model_id_to_use
7167
context["model_actually_switched"] = True
7268
context["current_ai_studio_model_id"] = model_id_to_use
73-
logger.info(f"模型切换成功: {state.current_ai_studio_model_id}")
7469
else:
7570
# Current model ID should exist when switching fails
7671
current_model = state.current_ai_studio_model_id or "unknown"
@@ -104,7 +99,6 @@ async def _handle_model_switch_failure(
10499

105100
async def handle_parameter_cache(req_id: str, context: RequestContext) -> None:
106101
set_request_id(req_id)
107-
logger = context["logger"]
108102
params_cache_lock = context["params_cache_lock"]
109103
page_params_cache = context["page_params_cache"]
110104
current_ai_studio_model_id = context["current_ai_studio_model_id"]
@@ -117,7 +111,6 @@ async def handle_parameter_cache(req_id: str, context: RequestContext) -> None:
117111
if model_actually_switched or (
118112
current_ai_studio_model_id != cached_model_for_params
119113
):
120-
logger.info("模型已更改,参数缓存失效。")
121114
page_params_cache.clear()
122115
page_params_cache["last_known_model_id_for_params"] = (
123116
current_ai_studio_model_id

0 commit comments

Comments
 (0)