"""Petite interface web d'administration, greffée sur le serveur FastMCP (même port). Permet d'éditer à la main les paramètres « métier » sans rebuild ni édition JSON : - la liste des ingrédients à exclure (coco & co) ; - les préférences de scoring (liked / disliked). Les écritures passent par `hellofresh.filter` (mêmes fichiers que le serveur MCP), donc le serveur prend en compte les changements au prochain appel — pas de redémarrage. Routes enregistrées via `register(mcp)` depuis server.py : GET / -> page HTML GET /api/config -> {excludes, prefs} PUT /api/excludes-> sauve la liste d'exclusion PUT /api/prefs -> sauve liked/disliked """ from __future__ import annotations from starlette.requests import Request from starlette.responses import HTMLResponse, JSONResponse, Response from . import filter as hf_filter def _clean_list(value) -> list[str]: """Normalise une entrée en liste de chaînes non vides, dédupliquées (ordre conservé).""" if not isinstance(value, list): return [] out: list[str] = [] seen: set[str] = set() for v in value: s = str(v).strip() key = hf_filter.normalize(s) if s and key not in seen: seen.add(key) out.append(s) return out async def _config(request: Request) -> Response: return JSONResponse({ "excludes": hf_filter.load_excludes(), "prefs": hf_filter.load_prefs(), }) async def _put_excludes(request: Request) -> Response: try: body = await request.json() except Exception: return JSONResponse({"error": "JSON invalide"}, status_code=400) terms = _clean_list(body.get("exclude")) hf_filter.save_excludes(terms) return JSONResponse({"exclude": terms}) async def _put_prefs(request: Request) -> Response: try: body = await request.json() except Exception: return JSONResponse({"error": "JSON invalide"}, status_code=400) liked = _clean_list(body.get("liked")) disliked = _clean_list(body.get("disliked")) saved = hf_filter.save_prefs(liked, disliked) return JSONResponse(saved) async def _index(request: Request) -> Response: return HTMLResponse(_PAGE) def register(mcp) -> None: """Enregistre les routes web sur le serveur FastMCP.""" mcp.custom_route("/", methods=["GET"], include_in_schema=False)(_index) mcp.custom_route("/api/config", methods=["GET"], include_in_schema=False)(_config) mcp.custom_route("/api/excludes", methods=["PUT"], include_in_schema=False)(_put_excludes) mcp.custom_route("/api/prefs", methods=["PUT"], include_in_schema=False)(_put_prefs) _PAGE = """
Toute recette contenant l'un de ces termes (nom d'ingrédient ou allergène, sans accent ni casse) est refusée à la sélection.
Mots-clés cherchés dans le nom, le titre et les ingrédients. « Aimé » remonte la recette dans les propositions, « pas aimé » la fait descendre (sans l'exclure).