UX improvements: mobile bottom sheet, cuisine taxonomy, search enhancements
- Add BottomSheet component for Google Maps-style restaurant detail on mobile (3-snap drag: 40%/55%/92%, velocity-based close, backdrop overlay) - Mobile map mode now full-screen with bottom sheet overlay for details - Collapsible filter panel on mobile with active filter badge count - Standardized cuisine taxonomy (46 categories: 한식|국밥, 일식|스시 etc.) with LLM remap endpoint and admin UI button - Enhanced search: keyword search now includes foods_mentioned + video title - Search results include channels array for frontend filtering - Channel filter moved to frontend filteredRestaurants (not API-level) - LLM extraction prompt updated for pipe-delimited region + cuisine taxonomy - Vector rebuild endpoint with rich JSON chunks per restaurant - Geolocation-based auto region selection on page load - Desktop filters split into two clean rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
98
backend/api/routes/daemon.py
Normal file
98
backend/api/routes/daemon.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""Daemon config & manual trigger API routes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api.deps import get_admin_user
|
||||
from core.db import conn
|
||||
from core import cache
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class DaemonConfigUpdate(BaseModel):
|
||||
scan_enabled: bool | None = None
|
||||
scan_interval_min: int | None = None
|
||||
process_enabled: bool | None = None
|
||||
process_interval_min: int | None = None
|
||||
process_limit: int | None = None
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
def get_config():
|
||||
"""Get daemon config (read-only for all authenticated users)."""
|
||||
with conn() as c:
|
||||
cur = c.cursor()
|
||||
cur.execute(
|
||||
"SELECT scan_enabled, scan_interval_min, process_enabled, process_interval_min, "
|
||||
"process_limit, last_scan_at, last_process_at, updated_at "
|
||||
"FROM daemon_config WHERE id = 1"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return {}
|
||||
return {
|
||||
"scan_enabled": bool(row[0]),
|
||||
"scan_interval_min": row[1],
|
||||
"process_enabled": bool(row[2]),
|
||||
"process_interval_min": row[3],
|
||||
"process_limit": row[4],
|
||||
"last_scan_at": str(row[5]) if row[5] else None,
|
||||
"last_process_at": str(row[6]) if row[6] else None,
|
||||
"updated_at": str(row[7]) if row[7] else None,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(body: DaemonConfigUpdate, _admin: dict = Depends(get_admin_user)):
|
||||
"""Update daemon schedule config (admin only)."""
|
||||
sets = []
|
||||
params: dict = {}
|
||||
if body.scan_enabled is not None:
|
||||
sets.append("scan_enabled = :se")
|
||||
params["se"] = 1 if body.scan_enabled else 0
|
||||
if body.scan_interval_min is not None:
|
||||
sets.append("scan_interval_min = :si")
|
||||
params["si"] = body.scan_interval_min
|
||||
if body.process_enabled is not None:
|
||||
sets.append("process_enabled = :pe")
|
||||
params["pe"] = 1 if body.process_enabled else 0
|
||||
if body.process_interval_min is not None:
|
||||
sets.append("process_interval_min = :pi")
|
||||
params["pi"] = body.process_interval_min
|
||||
if body.process_limit is not None:
|
||||
sets.append("process_limit = :pl")
|
||||
params["pl"] = body.process_limit
|
||||
if not sets:
|
||||
return {"ok": True}
|
||||
sets.append("updated_at = SYSTIMESTAMP")
|
||||
sql = f"UPDATE daemon_config SET {', '.join(sets)} WHERE id = 1"
|
||||
with conn() as c:
|
||||
c.cursor().execute(sql, params)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/run/scan")
|
||||
def run_scan(_admin: dict = Depends(get_admin_user)):
|
||||
"""Manually trigger channel scan (admin only)."""
|
||||
from core.youtube import scan_all_channels
|
||||
new_count = scan_all_channels()
|
||||
with conn() as c:
|
||||
c.cursor().execute("UPDATE daemon_config SET last_scan_at = SYSTIMESTAMP WHERE id = 1")
|
||||
if new_count > 0:
|
||||
cache.flush()
|
||||
return {"ok": True, "new_videos": new_count}
|
||||
|
||||
|
||||
@router.post("/run/process")
|
||||
def run_process(limit: int = 10, _admin: dict = Depends(get_admin_user)):
|
||||
"""Manually trigger video processing (admin only)."""
|
||||
from core.pipeline import process_pending
|
||||
rest_count = process_pending(limit=limit)
|
||||
with conn() as c:
|
||||
c.cursor().execute("UPDATE daemon_config SET last_process_at = SYSTIMESTAMP WHERE id = 1")
|
||||
if rest_count > 0:
|
||||
cache.flush()
|
||||
return {"ok": True, "restaurants_extracted": rest_count}
|
||||
Reference in New Issue
Block a user