usability enhancements

This commit is contained in:
2026-02-13 03:12:42 -05:00
parent bf4a40f533
commit 0e21e035f5
28 changed files with 904 additions and 15 deletions

View File

@@ -10,6 +10,8 @@ IMAGE_QUALITY = int(os.getenv("IMAGE_QUALITY", "85"))
RETENTION_DAYS = int(os.getenv("RETENTION_DAYS", "30"))
UMAMI_SCRIPT_URL = os.getenv("UMAMI_SCRIPT_URL", "")
UMAMI_WEBSITE_ID = os.getenv("UMAMI_WEBSITE_ID", "")
GITHUB_REPO_URL = os.getenv("GITHUB_REPO_URL", "")
CONTACT_EMAIL = os.getenv("CONTACT_EMAIL", "")
ROYALTY_IMAGE_MCP_ENDPOINT = os.getenv("ROYALTY_IMAGE_MCP_ENDPOINT", "")
ROYALTY_IMAGE_API_KEY = os.getenv("ROYALTY_IMAGE_API_KEY", "")
ROYALTY_IMAGE_PROVIDER = os.getenv("ROYALTY_IMAGE_PROVIDER", "picsum")

View File

@@ -1,13 +1,15 @@
import logging
import os
import random
from apscheduler.schedulers.background import BackgroundScheduler
from fastapi import Depends, FastAPI, Query, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from sqlalchemy.orm import Session
from starlette.exceptions import HTTPException as StarletteHTTPException
from backend import config
from backend.database import get_db, init_db
@@ -33,6 +35,77 @@ logger = logging.getLogger(__name__)
app = FastAPI(title="ClawFort News API", version="0.1.0")
_ERROR_MESSAGES = {
404: [
"Oh no! This page wandered off to train a tiny model.",
"Oh no! We looked everywhere, even in the latent space.",
"Oh no! The link took a creative detour.",
"Oh no! This route is currently off doing research.",
"Oh no! The page you asked for is not in this timeline.",
],
500: [
"Oh no! The server hit a logic knot and needs a quick reset.",
"Oh no! Our robots dropped a semicolon somewhere important.",
"Oh no! A background process got stage fright.",
"Oh no! The AI took an unexpected coffee break.",
"Oh no! Something internal blinked at the wrong moment.",
],
}
def _render_error_page(status_code: int) -> str:
message = random.choice(_ERROR_MESSAGES.get(status_code, _ERROR_MESSAGES[500]))
return f"""<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\" />
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />
<title>{status_code} - ClawFort</title>
<link rel=\"icon\" type=\"image/svg+xml\" href=\"/static/images/favicon-ai.svg\" />
<style>
:root {{ color-scheme: dark; }}
body {{ margin:0; font-family: Inter, system-ui, sans-serif; background:#0f172a; color:#e2e8f0; }}
.wrap {{ min-height:100vh; display:grid; place-items:center; padding:24px; }}
.card {{ max-width:760px; width:100%; border:1px solid rgba(148,163,184,.25); border-radius:16px; background:#111827; padding:28px; }}
.code {{ font-size:72px; font-weight:900; line-height:1; color:#60a5fa; margin:0 0 12px; }}
.title {{ font-size:28px; font-weight:800; margin:0 0 8px; }}
.msg {{ color:#cbd5e1; font-size:17px; line-height:1.6; margin:0 0 16px; }}
a {{ color:#93c5fd; text-decoration:none; font-weight:700; }}
a:hover {{ color:#bfdbfe; }}
</style>
</head>
<body>
<main class=\"wrap\">
<section class=\"card\" role=\"status\" aria-live=\"polite\">
<p class=\"code\">{status_code}</p>
<h1 class=\"title\">Oh no!</h1>
<p class=\"msg\">{message}</p>
<a href=\"/\">Back to ClawFort</a>
</section>
</main>
</body>
</html>"""
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
if request.url.path.startswith("/api/"):
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
if exc.status_code == 404:
return HTMLResponse(_render_error_page(404), status_code=404)
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
logger.exception("Unhandled server error: %s", exc)
if request.url.path.startswith("/api/"):
return JSONResponse(status_code=500, content={"detail": "Internal Server Error"})
return HTMLResponse(_render_error_page(500), status_code=500)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -216,6 +289,8 @@ async def serve_config() -> dict:
return {
"umami_script_url": config.UMAMI_SCRIPT_URL,
"umami_website_id": config.UMAMI_WEBSITE_ID,
"github_repo_url": config.GITHUB_REPO_URL,
"contact_email": config.CONTACT_EMAIL,
"supported_languages": config.SUPPORTED_LANGUAGES,
"default_language": "en",
}

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="AI favicon">
<defs>
<linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#60a5fa"/>
<stop offset="1" stop-color="#22d3ee"/>
</linearGradient>
</defs>
<rect x="4" y="4" width="56" height="56" rx="14" fill="#0f172a"/>
<circle cx="32" cy="24" r="10" fill="none" stroke="url(#g)" stroke-width="4"/>
<path d="M18 44c4-7 8-10 14-10s10 3 14 10" fill="none" stroke="url(#g)" stroke-width="4" stroke-linecap="round"/>
<circle cx="26" cy="24" r="2.2" fill="#e2e8f0"/>
<circle cx="38" cy="24" r="2.2" fill="#e2e8f0"/>
</svg>

After

Width:  |  Height:  |  Size: 659 B