feat: Added favicon (SVG + 32px PNG), apple-touch-icon, OG social image…

- "frontend/public/favicon.svg"
- "frontend/public/favicon-32.png"
- "frontend/public/apple-touch-icon.png"
- "frontend/public/og-image.png"
- "frontend/index.html"

GSD-Task: S03/T01
This commit is contained in:
jlightner 2026-04-03 05:45:51 +00:00
parent ab11a00765
commit 48e1bd163d
6 changed files with 90 additions and 43 deletions

View file

@ -11,6 +11,8 @@ Admin:
GET /admin/pipeline/worker-status Active/reserved tasks from Celery inspect GET /admin/pipeline/worker-status Active/reserved tasks from Celery inspect
""" """
import asyncio
import json
import logging import logging
import uuid import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
@ -1263,51 +1265,79 @@ async def reindex_all(
# ── Admin: Worker status ───────────────────────────────────────────────────── # ── Admin: Worker status ─────────────────────────────────────────────────────
@router.get("/admin/pipeline/worker-status") WORKER_STATUS_CACHE_KEY = "chrysopedia:worker_status"
async def worker_status(): WORKER_STATUS_CACHE_TTL = 10 # seconds
"""Get current Celery worker status — active, reserved, and stats."""
def _inspect_workers():
"""Synchronous Celery inspect — runs in a thread to avoid blocking the event loop."""
from worker import celery_app from worker import celery_app
inspector = celery_app.control.inspect(timeout=0.5)
active = inspector.active() or {}
reserved = inspector.reserved() or {}
stats = inspector.stats() or {}
workers = []
for worker_name in set(list(active.keys()) + list(reserved.keys()) + list(stats.keys())):
worker_active = active.get(worker_name, [])
worker_reserved = reserved.get(worker_name, [])
worker_stats = stats.get(worker_name, {})
workers.append({
"name": worker_name,
"active_tasks": [
{
"id": t.get("id"),
"name": t.get("name"),
"args": t.get("args", []),
"time_start": t.get("time_start"),
}
for t in worker_active
],
"reserved_tasks": len(worker_reserved),
"total_completed": worker_stats.get("total", {}).get("tasks.pipeline.stages.stage2_segmentation", 0)
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage3_extraction", 0)
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage4_classification", 0)
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage5_synthesis", 0),
"uptime": worker_stats.get("clock", None),
"pool_size": worker_stats.get("pool", {}).get("max-concurrency") if isinstance(worker_stats.get("pool"), dict) else None,
})
return {"online": len(workers) > 0, "workers": workers}
@router.get("/admin/pipeline/worker-status")
async def worker_status():
"""Get current Celery worker status — active, reserved, and stats.
Results are cached in Redis for 10 seconds to avoid repeated slow
Celery inspect round-trips. The synchronous inspect calls run in a
thread so they never block the async event loop.
"""
# Try Redis cache first
try: try:
inspector = celery_app.control.inspect() redis = await get_redis()
active = inspector.active() or {} cached = await redis.get(WORKER_STATUS_CACHE_KEY)
reserved = inspector.reserved() or {} await redis.aclose()
stats = inspector.stats() or {} if cached:
return json.loads(cached)
except Exception:
pass
workers = [] # Cache miss — run synchronous inspect in a thread
for worker_name in set(list(active.keys()) + list(reserved.keys()) + list(stats.keys())): try:
worker_active = active.get(worker_name, []) result = await asyncio.to_thread(_inspect_workers)
worker_reserved = reserved.get(worker_name, [])
worker_stats = stats.get(worker_name, {})
workers.append({
"name": worker_name,
"active_tasks": [
{
"id": t.get("id"),
"name": t.get("name"),
"args": t.get("args", []),
"time_start": t.get("time_start"),
}
for t in worker_active
],
"reserved_tasks": len(worker_reserved),
"total_completed": worker_stats.get("total", {}).get("tasks.pipeline.stages.stage2_segmentation", 0)
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage3_extraction", 0)
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage4_classification", 0)
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage5_synthesis", 0),
"uptime": worker_stats.get("clock", None),
"pool_size": worker_stats.get("pool", {}).get("max-concurrency") if isinstance(worker_stats.get("pool"), dict) else None,
})
return {
"online": len(workers) > 0,
"workers": workers,
}
except Exception as exc: except Exception as exc:
logger.warning("Failed to inspect Celery workers: %s", exc) logger.warning("Failed to inspect Celery workers: %s", exc)
return { return {"online": False, "workers": [], "error": str(exc)}
"online": False,
"workers": [], # Write to Redis cache (best-effort)
"error": str(exc), try:
} redis = await get_redis()
await redis.set(WORKER_STATUS_CACHE_KEY, json.dumps(result), ex=WORKER_STATUS_CACHE_TTL)
await redis.aclose()
except Exception:
pass
return result

View file

@ -4,6 +4,18 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0a0a12" /> <meta name="theme-color" content="#0a0a12" />
<meta name="description" content="Music production technique encyclopedia" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta property="og:title" content="Chrysopedia" />
<meta property="og:description" content="Music production technique encyclopedia" />
<meta property="og:image" content="/og-image.png" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Chrysopedia" />
<meta name="twitter:description" content="Music production technique encyclopedia" />
<meta name="twitter:image" content="/og-image.png" />
<title>Chrysopedia</title> <title>Chrysopedia</title>
</head> </head>
<body> <body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<rect width="32" height="32" rx="6" fill="#0a0a12"/>
<path d="M22 10.5a6.5 6.5 0 0 0-6.5-6.5C11.36 4 8 7.36 8 11.5c0 4.14 3.36 7.5 7.5 7.5" stroke="#22d3ee" stroke-width="3" fill="none" stroke-linecap="round"/>
<circle cx="22" cy="22" r="3" fill="#22d3ee" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB