"""Co moduł wnosi do aplikacji: funkcję, trasy, zasoby, akcje i pola formularza. Wszystko, co gdziekolwiek indziej musiałoby paść z nazwy, jest zebrane TUTAJ. Reszta aplikacji zna wyłącznie neutralny most (`app.rozszerzenia`) i pyta go o dane, nigdy o „generowanie" czy „model". """ from __future__ import annotations from typing import Any import httpx from fastapi import Form, Request from app.dodatki import klient # Pozycja w katalogu funkcji — surowa krotka, żeby nie importować `features` # (to zamknęłoby cykl features → most → moduł → features). FUNKCJA: tuple[tuple[str, str, str, str], ...] = ( ("ai", "Generowanie tekstu przez model", "Horoskopy pisane przez model językowy. UWAGA: każde użycie kosztuje.", "Rozszerzenia"), ) TRASY: dict[tuple[str, str], str] = {("POST", "/horoscope/stream"): "ai"} ZASOBY: dict[str, str] = { "x-ai.css": "ai", "models.js": "ai", "progress.js": "ai", "natal.js": "ai", "predictions.js": "ai", } # Akcje formularza wnoszone przez moduł: nazwa → wymagane uprawnienie. AKCJE: dict[str, str] = {"prompt": "ai", "horoscope": "ai"} # Wstrzykiwane przez `zamontuj` z main.py — moduł nie importuje aplikacji, # bo to byłby cykl, a przy okazji zmusiłoby aplikację do wiedzy o module. _logic: Any = None _szablony: Any = None def ustaw_zaleznosci(*, logic: Any, templates: Any) -> None: global _logic, _szablony _logic, _szablony = logic, templates def pola(prompt_budget: str = Form("medium"), llm_provider: str = Form(""), llm_model: str = Form("")) -> dict: """Pola formularza, których wspólny handler nie może wymienić w sygnaturze. FastAPI czyta pola z sygnatury, więc zadeklarowanie ich we wspólnym handlerze wpisałoby nazwy `llm_provider` i `prompt_budget` w kod, który trafia także do obrazu bez tego modułu. Zależność przenosi deklarację tutaj.""" return {"prompt_budget": prompt_budget, "llm_provider": llm_provider, "llm_model": llm_model} def _katalog() -> dict: """Podpowiedzi modeli dla pola wyboru. Awaria logiki nie może wywrócić strony — pole modelu jest tekstowe, więc bez katalogu nadal da się wpisać model ręcznie.""" try: return klient.modele(_logic) except httpx.HTTPError: return {"providers": {}, "defaults": {}} def katalog_dla(request: Request) -> dict: """Katalog modeli do kontekstu szablonu — pusty, gdy konto nie ma uprawnienia. Katalog trafia do strony jako blok JSON, więc bez tego ograniczenia nazwy dostawców, modeli i rozmiary okien kontekstu byłyby w źródle każdej strony, także dla kont, które o modelach nie mają prawa wiedzieć.""" perms = getattr(getattr(request.state, "principal", None), "permissions", frozenset()) return {"llm_catalog": _katalog() if "ai" in perms else {}} def obsluz(akcja: str, *, profil: str, iso_utc: str, lat: float, lon: float, dodatki: dict, od: str = "", do: str = "", **_reszta) -> dict | None: """Wykonanie akcji modułu. None znaczy „to nie moja akcja".""" if akcja not in AKCJE: return None metoda = klient.prompt if akcja == "prompt" else klient.horoscope argumenty = {"logic": _logic, "profile": profil, "when_utc_iso": iso_utc, "lat": lat, "lon": lon, "budget": dodatki.get("prompt_budget", "medium"), "provider": dodatki.get("llm_provider", ""), "model": dodatki.get("llm_model", "")} if profil == "period": argumenty["from_date"], argumenty["to_date"] = od, do return metoda(**argumenty)