"""Async docs-api client for the WebUI.""" import os from typing import Any, Dict, Optional from httpx import AsyncClient, Timeout class DocsAPIClient: """Small async HTTP client for the docs-api backend.""" def __init__(self, base_url: Optional[str] = None, api_key: Optional[str] = None): self.base_url = (base_url or os.environ.get("DOCS_API_URL", "http://docs-api:8787")).rstrip("/") self.api_key = api_key if api_key is not None else os.environ.get("WEBUI_API_KEY") self.headers = {"X-API-Key": self.api_key} if self.api_key else {} self._client: Optional[AsyncClient] = None async def _get_client(self) -> AsyncClient: if self._client is None or self._client.is_closed: self._client = AsyncClient( base_url=self.base_url, headers=self.headers, timeout=Timeout(120.0), ) return self._client async def request(self, method: str, path: str, **kwargs: Any) -> Dict[str, Any]: client = await self._get_client() resp = await client.request(method, path, **kwargs) if resp.status_code >= 400: raise RuntimeError(f"{method} {path} failed: {resp.status_code} {resp.text}") if resp.headers.get("content-type", "").startswith("application/json"): data = resp.json() return data if isinstance(data, dict) else {"data": data} return {"data": resp.text} async def get(self, path: str, **kwargs: Any) -> Dict[str, Any]: return await self.request("GET", path, **kwargs) async def post(self, path: str, **kwargs: Any) -> Dict[str, Any]: return await self.request("POST", path, **kwargs) async def delete(self, path: str, **kwargs: Any) -> Dict[str, Any]: return await self.request("DELETE", path, **kwargs) async def health(self) -> Dict[str, Any]: try: return await self.get("/health") except Exception as e: return {"status": "error", "message": str(e)} async def upload_file(self, library_id: str, filename: str, content: bytes) -> Dict[str, Any]: files = {"file": (filename, content)} return await self.post(f"/api/v1/upload/{library_id}", files=files) async def close(self) -> None: if self._client is not None and not self._client.is_closed: await self._client.aclose() _client_instance: Optional[DocsAPIClient] = None async def get_client() -> DocsAPIClient: global _client_instance if _client_instance is None: _client_instance = DocsAPIClient() return _client_instance async def close_client() -> None: if _client_instance is not None: await _client_instance.close()