loader.js:
- Fetch /api/version first; append ?v=<version> to every script URL
- Load scripts with async=false (fetch in parallel, execute in DOM order)
- 4 sequential batches instead of 17 sequential awaits:
utils → settings → [9 feature modules] → init → [4 post-init] → nav
- 17 round-trips → 6 round-trips; 9 and 4 files now download in parallel
- Section HTML keeps ?v=<timestamp> (no-store, always fresh)
server.py:
- Add static_cache_headers middleware:
/static/js/*?v=... → Cache-Control: max-age=31536000, immutable
/static/sections/* → Cache-Control: no-store
/static/* (no v=) → Cache-Control: max-age=3600
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
3.8 KiB
Python
86 lines
3.8 KiB
Python
"""TTS Voice Creator - Clone and Design — FastAPI entry point (thin wrapper)."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from logging.handlers import RotatingFileHandler
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import Response
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from core.constants import STATIC_DIR, _BufferHandler
|
|
from routes import admin, settings, library, stt, sources, docker, tts, conversation
|
|
|
|
logger = logging.getLogger("uvicorn.error")
|
|
|
|
# ── App ───────────────────────────────────────────────────────────────────────
|
|
|
|
app = FastAPI(title="TTS Voice Creator - Clone and Design")
|
|
|
|
# ── In-memory log buffer handler ──────────────────────────────────────────────
|
|
|
|
_buf_handler = _BufferHandler()
|
|
_buf_handler.setLevel(logging.DEBUG)
|
|
logging.getLogger().addHandler(_buf_handler)
|
|
|
|
# ── File log handler (rotates at 5 MB, keeps 3 files) ────────────────────────
|
|
|
|
_LOG_DIR = Path(os.environ.get("LOG_DIR", "/logs"))
|
|
try:
|
|
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
_file_handler = RotatingFileHandler(
|
|
_LOG_DIR / "app.log",
|
|
maxBytes=5 * 1024 * 1024,
|
|
backupCount=3,
|
|
encoding="utf-8",
|
|
)
|
|
_file_handler.setLevel(logging.INFO)
|
|
_file_handler.setFormatter(logging.Formatter(
|
|
"%(asctime)s %(levelname)-8s %(name)s: %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
))
|
|
logging.getLogger().addHandler(_file_handler)
|
|
except Exception as _log_err:
|
|
logger.warning("Could not set up file logging at %s: %s", _LOG_DIR, _log_err)
|
|
|
|
# ── Static-asset caching middleware ──────────────────────────────────────────
|
|
# JS and CSS are served with ?v=<version> by loader.js — safe to cache for 1 year.
|
|
# Section HTML uses ?v=<timestamp> (always fresh) so no long-term cache there.
|
|
|
|
@app.middleware("http")
|
|
async def static_cache_headers(request: Request, call_next):
|
|
response: Response = await call_next(request)
|
|
path = request.url.path
|
|
has_version = bool(request.query_params.get("v"))
|
|
if has_version and (path.startswith("/static/js/") or path.endswith(".css")):
|
|
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
|
|
elif path.startswith("/static/sections/"):
|
|
response.headers["Cache-Control"] = "no-store"
|
|
elif path.startswith("/static/") and not has_version:
|
|
response.headers["Cache-Control"] = "public, max-age=3600"
|
|
return response
|
|
|
|
|
|
# ── Routers ───────────────────────────────────────────────────────────────────
|
|
|
|
app.include_router(admin.router)
|
|
app.include_router(settings.router)
|
|
app.include_router(library.router)
|
|
app.include_router(stt.router)
|
|
app.include_router(sources.router)
|
|
app.include_router(docker.router)
|
|
app.include_router(tts.router)
|
|
app.include_router(conversation.router)
|
|
|
|
# ── Static files ──────────────────────────────────────────────────────────────
|
|
|
|
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
|
|
|
# ── Dev runner ────────────────────────────────────────────────────────────────
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=7890, log_level="info")
|