Spaces:
Paused
Paused
| from __future__ import annotations | |
| from contextlib import asynccontextmanager | |
| from threading import Event | |
| import anyio | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from api import accounts, ai, image_tasks, openai_keys, register, system | |
| from api.support import resolve_web_asset, start_limited_account_watcher | |
| from services.backup_service import backup_service | |
| from services.config import config | |
| from utils.log import logger | |
| def _apply_blocking_threadpool_size() -> None: | |
| # 抬高 anyio run_in_threadpool 线程池上限(默认 40),解除生图/文本接口的同时并发硬顶。 | |
| # 必须在事件循环内调用(limiter 绑定当前 async backend),故放在 lifespan 启动里。 | |
| size = config.blocking_threadpool_size | |
| try: | |
| limiter = anyio.to_thread.current_default_thread_limiter() | |
| if limiter.total_tokens != size: | |
| limiter.total_tokens = size | |
| logger.info({"event": "blocking_threadpool_configured", "total_tokens": limiter.total_tokens}) | |
| except Exception as exc: | |
| logger.warning({"event": "blocking_threadpool_config_failed", "error": str(exc)}) | |
| def create_app() -> FastAPI: | |
| app_version = config.app_version | |
| async def lifespan(_: FastAPI): | |
| _apply_blocking_threadpool_size() | |
| stop_event = Event() | |
| thread = start_limited_account_watcher(stop_event) | |
| backup_service.start() | |
| config.cleanup_old_images() | |
| try: | |
| yield | |
| finally: | |
| stop_event.set() | |
| thread.join(timeout=1) | |
| backup_service.stop() | |
| app = FastAPI(title="chatgpt2api", version=app_version, lifespan=lifespan) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=False, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| app.include_router(ai.create_router()) | |
| app.include_router(accounts.create_router()) | |
| app.include_router(openai_keys.create_router()) | |
| app.include_router(image_tasks.create_router()) | |
| app.include_router(register.create_router()) | |
| app.include_router(system.create_router(app_version)) | |
| if config.images_dir.exists(): | |
| app.mount("/images", StaticFiles(directory=str(config.images_dir)), name="images") | |
| async def serve_web(full_path: str): | |
| asset = resolve_web_asset(full_path) | |
| if asset is not None: | |
| return FileResponse(asset) | |
| if full_path.strip("/").startswith("_next/"): | |
| raise HTTPException(status_code=404, detail="Not Found") | |
| fallback = resolve_web_asset("") | |
| if fallback is None: | |
| raise HTTPException(status_code=404, detail="Not Found") | |
| return FileResponse(fallback) | |
| return app | |