Files
ObsiGate/docs/features/ai-provider-picker.md
T
bruno b69cb9b0f8
CI / lint (push) Successful in 1m20s
CI / security (push) Successful in 53s
CI / test (push) Successful in 2m27s
CI / build (push) Successful in 50s
CI / e2e (push) Successful in 10m48s
fix(ai): BUG-044 capacités des modèles lues chez le fournisseur (Mistral vision)
Le panneau de modèle par défaut et la bulle ⓘ n'affichaient aucun modèle Mistral
« Vision capable » alors que GET api.mistral.ai/v1/models en déclare 28 : la table
de capacités était entièrement statique et aucun de ses motifs ne correspondait aux
familles Mistral actuelles (seul `pixtral`, retiré de l'API, les matchait).

- backend/provider_capabilities.py (nouveau) : capacités déclarées par le
  fournisseur (Mistral `capabilities`, OpenRouter `architecture`), détectées par
  la forme du payload, snapshot en cache process-wide (TTL 30 min, surchargeable
  par AI_CAPABILITIES_TTL_SECONDS) rempli par GET /api/config/ai-models.
- backend/model_capabilities.py : une déclaration prime sur la table statique
  pour chaque drapeau mentionné ; la table ne comble que le reste (Mistral ne
  déclare jamais `embedding`). Table corrigée pour le repli hors ligne : familles
  vision Mistral (ministral, magistral, mistral-small, mistral-medium,
  mistral-vibe-cli, labs-leanstral), mistral-ocr = vision sans chat, et défaut du
  fournisseur Mistral sans `embeddings` (mistral-large / codestral n'étaient plus
  des « embedders »).
- backend/ai_routes.py : GET /api/ai/model-capabilities reste sans appel réseau
  (cache froid → table statique).
- Tests : tests/test_provider_capabilities.py (nouveau), TestMistralFamilies et
  TestDeclaredCapabilities (bout en bout via l'API).
- Docs : CHANGELOG [Unreleased], registre + journal ISSUES_TODOLIST,
  fiche docs/features/ai-provider-picker.md (§L).

Vérifié : 28/28 modèles vision déclarés par Mistral détectés (0 avant), 0 écart
dans les deux sens ; pytest 1007 passed / 6 skipped ; ruff 0 (backend) ; mypy 0 ;
tests frontend unit 9/9 + IA 66/66 + validate-imports 37 modules ; instance de test
reconstruite et vérifiée sur http://localhost:2020.
2026-09-15 09:26:31 -04:00

10 KiB

#82 — Assistant IA — Section « Fournisseur & modèle » compacte

Statut : ✅ Livré (en attente de validation utilisateur) Effort : 1 jour | Impact : 🟢 Références : Roadmap · Assistant IA #81 · Changelog

  • Description : rendre la barre de sélection du modèle de l'assistant IA plus discrète tout en conservant les informations utiles :
    1. Capacités via bulle — la liste complète des types d'endpoints (Chat, Embeddings, Rerank, Images, Video, Audio Speech, Audio Transcriptions, Vision) n'est plus affichée en permanence. Un bouton ⓘ révèle une bulle d'information au survol, au clic ou par appui long (mobile).
    2. Recherche de modèle — le <select> natif est remplacé par une liste déroulante compacte dotée d'un champ de recherche qui filtre les modèles en tapant (insensible à la casse et aux accents).
    3. Compatibilité — un <select class="ai-picker-model"> masqué reste présent (source de vérité) pour les commandes admin /model et l'état persisté obsigate_ai_picker.

A. Picker partagé — ✅ livré

  • frontend/js/ai.js : buildAIPickerUI() réécrit (provider compact + combobox modèle + bulle capacités). Le picker reste partagé par l'assistant (bookslm.js) et la barre d'édition IA.
  • renderCapabilityList() conservé et affiché dans la bulle (le panneau de configuration #cfg-ai-default-model-caps continue de l'utiliser directement).
  • Fermeture des popovers au clic extérieur via un unique listener délégué (pas de fuite).
  • i18n FR/EN (ai.model_search, ai.model_info).

B. Correctifs associés — ✅ livré

  • BUG-007 — détection / et @ Unicode + filtrage insensible aux accents + navigation clavier ↑/↓ fiabilisée (jeton de séquence, scrollIntoView).
  • BUG-008 — contexte ad-hoc @ pris en compte en mode Général côté backend (backend/bookslm_routes.py) et recherche de mention sur vault=all sans vault courant.

C. Tests — ✅ livré

  • tests/frontend/ai.test.mjs : filtrage accentué, mentions accentuées, navigation ↑/↓, recherche de modèle + bulle de capacités, plafonnement des longues listes (OpenRouter).
  • tests/test_bookslm.py : contexte ad-hoc en mode Général (/chat).

D. Durcissement anti-cache (BUG-009) — ✅ livré

  • Locales chargées avec cache: 'no-store' (frontend/js/i18n.js) : plus de clés brutes affichées à cause d'un fr.json obsolète en cache HTTP.
  • SW_VERSION incrémenté (frontend/sw.js) pour purger les caches du service worker.
  • Styles critiques du picker appliqués en ligne (popover absolu, liste en colonne, options display:block) pour rester corrects même si style.css est servi depuis un cache.
  • Rendu plafonné à 200 modèles (MODEL_RENDER_LIMIT) + indicateur ai.model_more.

E. Lisibilité & contexte @ (BUG-010, BUG-011) — ✅ livré

  • Popover modèle aligné à droite du déclencheur (right: 0), largeur 340 px bornée à 100vw - 24px : ne dépasse plus le bord de la sidebar ancrée à droite (BUG-011).
  • Noms de modèles sur plusieurs lignes (overflow-wrap: anywhere, police 0,78 rem) avec attribut title complet.
  • La sélection @ capture le vault renvoyé par /api/tree-search ; _contextVault() propage ce vault aux requêtes /context et /chat et les recherches suivantes restent dans ce vault (BUG-010, mode Général).

F. Barre latérale épurée & suivi visuel (BUG-012, #82) — ✅ livré

  • Section « Fournisseur & modèle » sans intitulés (ni titre de section, ni « Fournisseur : »).
  • Description du contexte Général retirée de l'affichage (bandeau de statut masqué) et exposée via title au survol de l'en-tête.
  • Placeholder « Posez une question sur ces documents… » retiré ; bouton « Envoyer » remplacé par l'emoji compact ✈️.
  • Indicateur d'activité (.bookslm-activity) affichant le workflow : envoi, réception, outil en cours, attente de confirmation, terminé, échec (ai.activity_*).
  • @ : le menu est toujours rendu et _mentionVault() retombe sur le vault de contexte puis le premier vault disponible (BUG-012).

G. Robustesse menus & liens (BUG-013, BUG-014) — ✅ livré

  • Navigation clavier ↑/↓ des menus / et @ gérée au niveau du panneau en phase de capture : fonctionne quel que soit l'élément focalisé (BUG-014).
  • Les liens fichiers/répertoires des réponses utilisent _activeVault() (vault de contexte → vault sélectionné → premier vault), ce qui supprime l'erreur « Aucun vault actif » en mode Général (BUG-013).
  • Purge des caches élargie : SW_VERSION v6 + migration qui supprime tous les caches obsigate-* (clé obsigate-sw-migration → v3).
  • Indice clavier sous la zone de saisie retiré pour un composeur plus épuré (#82).
  • Sélection clavier des menus / et @ rendue visible : état actif en --bg-hover + barre d'accent à gauche (--surface2 étant identique à --bg-primary en thème sombre, la sélection était invisible) — BUG-015.

H. Performance @, sélecteurs agrandis & BooksLM racine (#82) — ✅ livré

  • Nouvel endpoint GET /api/vault/{vault}/paths (backend/services/search.py: list_paths, backend/main.py) : index des chemins plat et plafonné (5000 par défaut).
  • Frontend : _ensurePaths() précharge cette liste une fois par vault (au openContext), puis _showMentionMenu filtre côté client → affichage instantané des fichiers/ répertoires, sans requête par frappe (repli serveur si le cache est vide).
  • Sélecteurs Fournisseur/Modèle agrandis pour s'aligner sur les autres contrôles (police 0,8 rem, hauteur 34 px, rayon 6 px).
  • Menu contextuel de la racine d'une vault : ajout de « 🧠 BooksLM » (frontend/js/ui.js).

J. Alignement à droite & ordre des sélecteurs (#82) — ✅ livré

  • Le groupe de sélection Fournisseur/Modèle est aligné sur le bord droit de la barre (frontend/style.css : .bookslm-toolbar { justify-content: flex-end; }).
  • Ordre visuel : capacité du modèle (ⓘ) → menu fournisseurs → menu des modèles (frontend/js/ai.js, ordre d'ajout dans _buildPickerUI()).
  • La bulle de capacités s'ouvre vers la droite (left: 0, inline + CSS) pour rester dans la barre latérale désormais que le bouton ⓘ est en tête de groupe.
  • Test JSDOM : l'ordre des trois contrôles visibles est vérifié (tests/frontend/ai.test.mjs).

K. Synchronisation de la liste des fournisseurs (BUG-043) — ✅ livré

  • frontend/js/ai.js : nouveau refreshAIPickers() — reconstruit chaque picker monté dans son emplacement .ai-picker-slot (constante PICKER_SLOT_CLASS). Le slot est conservé même quand aucun picker ne peut être construit (aucun fournisseur configuré), donc le premier fournisseur ajouté s'y monte aussi.
  • frontend/js/bookslm.js : l'emplacement du picker (span.bookslm-picker-host) porte la classe ai-picker-slot et reste dans la barre — le picker est monté à l'intérieur au lieu de remplacer le host (replaceChildren).
  • frontend/js/config.js : refreshAIPickers() appelé après l'enregistrement (saveAIKeys()) et la suppression (deleteAIKey()) d'une clé API.
  • Une sélection persistée dont le fournisseur n'est plus configuré est purgée de obsigate_ai_picker (fournisseur et modèle effacés → retour au défaut, plus de nom de modèle fantôme dans le déclencheur).
  • Tests JSDOM : ajout/retrait d'un fournisseur dans la barre de l'assistant, montage du premier fournisseur dans un slot vide, purge de la sélection orpheline, et garde-fou sur le câblage saveAIKeys/deleteAIKey (tests/frontend/ai.test.mjs).

I. Points d'attention

  • La table statique (backend/model_capabilities.py) n'est plus la source principale : elle sert de repli (fournisseur muet, pas de clé API, cache froid) et comble les drapeaux non déclarés. Un modèle inconnu retombe toujours sur le défaut du fournisseur.
  • Le cache de déclarations est en mémoire, par process : un redémarrage ou un cache froid ne casse rien (repli statique), et GET /api/config/ai-models le repeuple au premier affichage de la liste des modèles.
  • Le select masqué est conservé uniquement pour la rétro-compatibilité ; ne pas le supprimer sans migrer _cmdSwitchModel / _applyPickerSelection dans frontend/js/bookslm.js.

L. Capacités fournies par le provider (BUG-044) — ✅ livré

  • backend/provider_capabilities.py (nouveau) : lecture des capacités déclarées par l'API du fournisseur (capabilities Mistral, architecture OpenRouter), détectées par la forme du payload — un fournisseur qui se met à les publier est pris en charge sans modification de code.
  • Snapshot en cache process-wide (TTL 30 min par défaut, AI_CAPABILITIES_TTL_SECONDS), rempli par GET /api/config/ai-models (appel déjà effectué pour lister les modèles : aucune requête supplémentaire) ; clear_declared_capabilities() + cache_info() pour les tests et le diagnostic.
  • backend/model_capabilities.py : une déclaration prime sur la table statique pour chaque drapeau qu'elle mentionne ; la table ne comble que le reste (Mistral ne déclare jamais embedding, seulement l'absence de completion_chat).
  • Table statique corrigée pour le repli hors ligne : familles vision Mistral (ministral, magistral, mistral-small, mistral-medium, mistral-vibe-cli, labs-leanstral), mistral-ocr = vision sans chat, et défaut du fournisseur Mistral sans embeddings (un modèle inconnu n'est plus présenté comme un modèle d'embeddings).
  • GET /api/ai/model-capabilities reste sans appel réseau : il répond depuis le cache (froid → table statique), donc la bulle ⓘ ne bloque jamais.
  • Tests : tests/test_provider_capabilities.py (parsing, cache, TTL, fusion), tests/test_model_capabilities.py::TestMistralFamilies (repli hors ligne) et tests/test_ai_models.py::TestDeclaredCapabilities (bout en bout : payload Mistral simulé → capabilities de /api/config/ai-models puis /api/ai/model-capabilities).