mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-06-02 14:19:55 +08:00
本次更新引入了对 Google Vertex AI Express API 的支持,允许用户配置和使用 Vertex AI 模型。 主要变更包括: 后端: - 新增 `VERTEX_API_KEYS` 和 `VERTEX_EXPRESS_BASE_URL` 至系统配置 ([`.env.example`](.env.example:13), [`app/config/config.py:62`](app/config/config.py:62), [`app/database/models.py`](app/database/models.py), [`app/database/services.py`](app/database/services.py))。 - 实现 `VertexExpressChatService` ([`app/service/chat/vertex_express_chat_service.py`](app/service/chat/vertex_express_chat_service.py)) 用于处理与 Vertex AI Express API 的交互。 - 添加 `vertex_express_routes` ([`app/router/vertex_express_routes.py`](app/router/vertex_express_routes.py)) 来暴露 Vertex AI 相关的 API 端点,并集成到主应用 ([`app/core/application.py:36`](app/core/application.py:36), [`app/router/routes.py:15`](app/router/routes.py:15))。 - 更新密钥管理器 ([`app/service/key/key_manager.py`](app/service/key/key_manager.py)) 以支持 Vertex API 密钥的获取、检查和轮换。 前端 (配置编辑器): - 在配置页面 ([`app/templates/config_editor.html:463`](app/templates/config_editor.html:463)) 添加了 Vertex API 密钥列表和 Vertex Express API 基础 URL 的表单字段。 - 实现了批量添加和删除 Vertex API 密钥的功能,包括相应的模态框和操作逻辑 ([`app/static/js/config_editor.js:550`](app/static/js/config_editor.js:550), [`app/static/js/config_editor.js:1097`](app/static/js/config_editor.js:1097), [`app/templates/config_editor.html:1657`](app/templates/config_editor.html:1657))。 - 确保新的配置项在初始化 ([`app/static/js/config_editor.js:598`](app/static/js/config_editor.js:598)) 和表单填充 ([`app/static/js/config_editor.js:671`](app/static/js/config_editor.js:671)) 时得到正确处理。 - 更新了数组项添加逻辑以识别 `VERTEX_API_KEYS` 为敏感字段 ([`app/static/js/config_editor.js:1235`](app/static/js/config_editor.js:1235))。 此功能扩展了应用支持的 AI 服务范围,为用户提供了更多模型选择。
188 lines
7.2 KiB
Python
188 lines
7.2 KiB
Python
"""
|
|
路由配置模块,负责设置和配置应用程序的路由
|
|
"""
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.core.security import verify_auth_token
|
|
from app.log.logger import get_routes_logger
|
|
from app.router import error_log_routes, gemini_routes, openai_routes, config_routes, scheduler_routes, stats_routes, version_routes, openai_compatiable_routes, vertex_express_routes
|
|
from app.service.key.key_manager import get_key_manager_instance
|
|
from app.service.stats.stats_service import StatsService
|
|
|
|
logger = get_routes_logger()
|
|
|
|
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
|
|
def setup_routers(app: FastAPI) -> None:
|
|
"""
|
|
设置应用程序的路由
|
|
|
|
Args:
|
|
app: FastAPI应用程序实例
|
|
"""
|
|
app.include_router(openai_routes.router)
|
|
app.include_router(gemini_routes.router)
|
|
app.include_router(gemini_routes.router_v1beta)
|
|
app.include_router(config_routes.router)
|
|
app.include_router(error_log_routes.router)
|
|
app.include_router(scheduler_routes.router)
|
|
app.include_router(stats_routes.router)
|
|
app.include_router(version_routes.router)
|
|
app.include_router(openai_compatiable_routes.router)
|
|
app.include_router(vertex_express_routes.router)
|
|
|
|
setup_page_routes(app)
|
|
|
|
setup_health_routes(app)
|
|
setup_api_stats_routes(app)
|
|
|
|
|
|
def setup_page_routes(app: FastAPI) -> None:
|
|
"""
|
|
设置页面相关的路由
|
|
|
|
Args:
|
|
app: FastAPI应用程序实例
|
|
"""
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def auth_page(request: Request):
|
|
"""认证页面"""
|
|
return templates.TemplateResponse("auth.html", {"request": request})
|
|
|
|
@app.post("/auth")
|
|
async def authenticate(request: Request):
|
|
"""处理认证请求"""
|
|
try:
|
|
form = await request.form()
|
|
auth_token = form.get("auth_token")
|
|
if not auth_token:
|
|
logger.warning("Authentication attempt with empty token")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
if verify_auth_token(auth_token):
|
|
logger.info("Successful authentication")
|
|
response = RedirectResponse(url="/config", status_code=302)
|
|
response.set_cookie(
|
|
key="auth_token", value=auth_token, httponly=True, max_age=3600
|
|
)
|
|
return response
|
|
logger.warning("Failed authentication attempt with invalid token")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
except Exception as e:
|
|
logger.error(f"Authentication error: {str(e)}")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
@app.get("/keys", response_class=HTMLResponse)
|
|
async def keys_page(request: Request):
|
|
"""密钥管理页面"""
|
|
try:
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to keys page")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
key_manager = await get_key_manager_instance()
|
|
keys_status = await key_manager.get_keys_by_status()
|
|
total_keys = len(keys_status["valid_keys"]) + len(keys_status["invalid_keys"])
|
|
valid_key_count = len(keys_status["valid_keys"])
|
|
invalid_key_count = len(keys_status["invalid_keys"])
|
|
|
|
stats_service = StatsService()
|
|
api_stats = await stats_service.get_api_usage_stats()
|
|
logger.info(f"API stats retrieved: {api_stats}")
|
|
|
|
logger.info(f"Keys status retrieved successfully. Total keys: {total_keys}")
|
|
return templates.TemplateResponse(
|
|
"keys_status.html",
|
|
{
|
|
"request": request,
|
|
"valid_keys": keys_status["valid_keys"],
|
|
"invalid_keys": keys_status["invalid_keys"],
|
|
"total_keys": total_keys,
|
|
"valid_key_count": valid_key_count,
|
|
"invalid_key_count": invalid_key_count,
|
|
"api_stats": api_stats,
|
|
},
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error retrieving keys status or API stats: {str(e)}")
|
|
raise
|
|
|
|
@app.get("/config", response_class=HTMLResponse)
|
|
async def config_page(request: Request):
|
|
"""配置编辑页面"""
|
|
try:
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to config page")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
logger.info("Config page accessed successfully")
|
|
return templates.TemplateResponse("config_editor.html", {"request": request})
|
|
except Exception as e:
|
|
logger.error(f"Error accessing config page: {str(e)}")
|
|
raise
|
|
|
|
@app.get("/logs", response_class=HTMLResponse)
|
|
async def logs_page(request: Request):
|
|
"""错误日志页面"""
|
|
try:
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to logs page")
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
logger.info("Logs page accessed successfully")
|
|
return templates.TemplateResponse("error_logs.html", {"request": request})
|
|
except Exception as e:
|
|
logger.error(f"Error accessing logs page: {str(e)}")
|
|
raise
|
|
|
|
|
|
def setup_health_routes(app: FastAPI) -> None:
|
|
"""
|
|
设置健康检查相关的路由
|
|
|
|
Args:
|
|
app: FastAPI应用程序实例
|
|
"""
|
|
|
|
@app.get("/health")
|
|
async def health_check(request: Request):
|
|
"""健康检查端点"""
|
|
logger.info("Health check endpoint called")
|
|
return {"status": "healthy"}
|
|
|
|
|
|
def setup_api_stats_routes(app: FastAPI) -> None:
|
|
"""
|
|
设置 API 统计相关的路由
|
|
|
|
Args:
|
|
app: FastAPI应用程序实例
|
|
"""
|
|
@app.get("/api/stats/details")
|
|
async def api_stats_details(request: Request, period: str):
|
|
"""获取指定时间段内的 API 调用详情"""
|
|
try:
|
|
auth_token = request.cookies.get("auth_token")
|
|
if not auth_token or not verify_auth_token(auth_token):
|
|
logger.warning("Unauthorized access attempt to API stats details")
|
|
return {"error": "Unauthorized"}, 401
|
|
|
|
logger.info(f"Fetching API call details for period: {period}")
|
|
stats_service = StatsService()
|
|
details = await stats_service.get_api_call_details(period)
|
|
return details
|
|
except ValueError as e:
|
|
logger.warning(f"Invalid period requested for API stats details: {period} - {str(e)}")
|
|
return {"error": str(e)}, 400
|
|
except Exception as e:
|
|
logger.error(f"Error fetching API stats details for period {period}: {str(e)}")
|
|
return {"error": "Internal server error"}, 500
|