"""Routes liées à Ollama : statut de connexion, modèles, téléchargement.""" from __future__ import annotations import json import httpx from fastapi import APIRouter, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel from ..config import settings from ..ollama_client import ollama router = APIRouter(prefix="/api", tags=["ollama"]) @router.get("/status") async def status() -> dict: """État de la connexion Ollama (point vert/rouge de la barre supérieure).""" try: version = await ollama.ping() return { "connected": True, "host": ollama.host, "version": version.get("version"), "default_model": settings.default_model, } except (httpx.HTTPError, OSError) as exc: return { "connected": False, "host": ollama.host, "error": str(exc), "default_model": settings.default_model, } @router.get("/models") async def list_models() -> dict: """Modèles installés localement, formatés pour le sélecteur de l'UI.""" try: raw = await ollama.list_models() except (httpx.HTTPError, OSError): return {"models": []} models = [] for m in raw: details = m.get("details", {}) size_go = round(m.get("size", 0) / 1_000_000_000, 1) models.append( { "name": m.get("name"), "size_go": size_go, "parameter_size": details.get("parameter_size"), "quantization": details.get("quantization_level"), "family": details.get("family"), } ) return {"models": models, "default": settings.default_model} class PullRequest(BaseModel): name: str class DeleteModelRequest(BaseModel): name: str @router.delete("/models") async def delete_model(req: DeleteModelRequest) -> dict: """Supprime explicitement un modèle de l'instance Ollama.""" if not req.name.strip(): raise HTTPException(400, "nom de modèle vide") try: await ollama.delete_model(req.name.strip()) except httpx.HTTPStatusError as exc: detail = exc.response.text[:500] or str(exc) raise HTTPException(502, f"Ollama : {detail}") from exc except (httpx.HTTPError, OSError) as exc: raise HTTPException(502, f"Ollama injoignable : {exc}") from exc return {"deleted": req.name.strip()} @router.post("/models/pull") async def pull_model(req: PullRequest) -> StreamingResponse: """Télécharge un modèle en streamant la progression (SSE).""" async def event_stream(): try: async for chunk in ollama.pull_model(req.name): yield f"data: {json.dumps(chunk)}\n\n" except (httpx.HTTPError, OSError) as exc: yield f"data: {json.dumps({'error': str(exc)})}\n\n" yield "data: [DONE]\n\n" return StreamingResponse(event_stream(), media_type="text/event-stream")