"""Thin httpx wrapper for the FastAPI backend. The browser never talks to FastAPI directly — Flet's Python process is the only client of the API. So no CORS, no auth surface exposed publicly. """ from __future__ import annotations import httpx from web.config import API_BASE_URL, HTTP_TIMEOUT class ApiError(RuntimeError): pass class ApiClient: def __init__(self, base_url: str = API_BASE_URL, timeout: float = HTTP_TIMEOUT): self._client = httpx.Client(base_url=base_url, timeout=timeout) def close(self) -> None: self._client.close() # --- LLM --- def llm_status(self) -> dict: return self._get("/llm/config") def llm_models(self, base_url: str, api_key: str) -> list[dict]: data = self._post( "/llm/models", {"base_url": base_url, "api_key": api_key}, ) return data.get("models", []) def llm_test(self, base_url: str, api_key: str, model: str) -> dict: return self._post( "/llm/test", {"base_url": base_url, "api_key": api_key, "model": model}, ) # --- Pipeline / videos --- def pipeline_process(self, payload: dict) -> dict: return self._post("/pipeline/process", payload) def list_videos( self, q: str | None = None, section_id: int | None = None, method: str | None = None, ) -> list[dict]: params: dict = {} if q: params["q"] = q if section_id is not None: params["section_id"] = section_id if method: params["method"] = method return self._get("/videos/", params=params) def video_text(self, uuid: str, kind: str) -> dict: return self._get(f"/videos/{uuid}/text", params={"kind": kind}) def set_video_section(self, uuid: str, section_id: int | None) -> dict: return self._request("PATCH", f"/videos/{uuid}", {"section_id": section_id}) def delete_video(self, uuid: str) -> dict: return self._request("DELETE", f"/videos/{uuid}") # --- Sections --- def list_sections(self) -> list[dict]: return self._get("/sections/") def create_section(self, name: str, parent_id: int | None) -> dict: return self._post("/sections/", {"name": name, "parent_id": parent_id}) def delete_section(self, section_id: int) -> dict: return self._request("DELETE", f"/sections/{section_id}") # --- internals --- def _get(self, path: str, params: dict | None = None) -> dict | list: try: r = self._client.get(path, params=params) except httpx.HTTPError as e: raise ApiError(f"Сеть: {e}") return self._unwrap(r) def _post(self, path: str, json: dict | None) -> dict: try: r = self._client.post(path, json=json) except httpx.HTTPError as e: raise ApiError(f"Сеть: {e}") return self._unwrap(r) def _request(self, method: str, path: str, json: dict | None = None) -> dict: try: r = self._client.request(method, path, json=json) except httpx.HTTPError as e: raise ApiError(f"Сеть: {e}") return self._unwrap(r) @staticmethod def _unwrap(r: httpx.Response): try: data = r.json() except Exception: data = None if r.is_success: return data detail = "" if isinstance(data, dict): detail = str(data.get("detail") or data.get("message") or "") if not detail: detail = r.text[:300] if r.text else r.reason_phrase raise ApiError(f"HTTP {r.status_code}: {detail}")