Files
ObsiGate/docs/features/ai-assistant-history.md
T
bruno 4231f2e929
CI / lint (push) Failing after 1m20s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 59s
feat(sidebar+assistant): filtre Recents/Sauvegardes (#99), pastille Deep Research (#100) et icone du bouton + (BUG-049)
2026-09-16 20:16:41 -04:00

177 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# #94–#97 — Assistant IA : historique permanent, accès sidebar, bouton rond et panneau « + »
> **Statut :** ✅ Livré (en attente de validation utilisateur)
> **Effort :** ~7 jours | **Impact :** 🟡
> **Références :** [Roadmap](../ROADMAP.md) · [Changelog](../../CHANGELOG.md)
- **Description :** quatre améliorations de l'Assistant IA livrées ensemble :
1. **#94** — bouton de soumission **circulaire** avec icône Lucide `arrow-up`
(remplace l'avion ✈️).
2. **#95** — **historique permanent** des conversations **persisté côté backend**
(les échanges survivent aux rechargements de page et aux navigateurs).
3. **#96** — accès rapide à l'**historique depuis la sidebar de navigation** (onglet dédié).
4. **#97** — panneau **« + » extensible** remplaçant « Attach an image » (modules :
fichiers, image, contextes, skills, Deep Research, web, Canva).
## A. Backend — persistance des conversations (#95) — ✅ livré
- [x] **`backend/ai_history.py`** (nouveau) : stockage JSON par utilisateur
(`data/ai_history/{username}.json`), écriture atomique (tmp + `shutil.move`),
plafond `MAX_SESSIONS = 200` (purge des plus anciennes), tri par `updatedAt` desc.
- [x] `list_sessions`, `get_session`, `upsert_session`, `delete_session` ;
la liste renvoie des **résumés sans messages** (`_summary` : titre, mode, vault,
contexte, `message_count`, `preview`) pour rester légère.
- [x] **`backend/bookslm_routes.py`** : modèle Pydantic `BookslmSession` et 4 endpoints :
- `GET /api/ai/bookslm/history` — liste des conversations (résumés).
- `GET /api/ai/bookslm/history/{session_id}` — conversation complète (404 si absente).
- `PUT /api/ai/bookslm/history/{session_id}` — création/mise à jour (l'`id` du corps
est forcé à l'`id` du path → jamais d'écriture sous une autre clé).
- `DELETE /api/ai/bookslm/history/{session_id}` — suppression (booléen `ok`).
- [x] Isolation par utilisateur (`require_auth`) ; saisies défensives (username/id vides) →
`[]` ou `None`.
## B. Frontend — sync serveur + cache local (#95) — ✅ livré
- [x] `frontend/js/bookslm.js` : clé local globale `bookslm-sessions-all-{username}` ;
**migration** des anciennes clés périmées `bookslm-sessions-<ctx>` et
`bookslm-history-<ctx>` à la première ouverture.
- [x] `_loadHistory(preferredSessionId)` **async** au chargement du panneau :
lecture serveur (`GET /history`), hydratation du localStorage, repli hors-ligne
silencieux si le serveur ne répond pas.
- [x] Synchronisation **debounced 600 ms** (`_syncHistoryToServer` → `_flushHistoryToServer`)
via `_dirtySessionIds` / `_deletedIds` : `PUT`/`DELETE` seulement pour les sessions
modifiées ; pas d'appel réseau à la simple ouverture.
- [x] `_positionSession` : nouvelle session insérée en tête, session rechargée remise à jour.
- [x] `openContext(opts, preferredSessionId)` **async** ; `openWithSession(sessionOrId)`
public pour ouvrir une conversation connue (utilisé par la sidebar #96).
- [x] `_notifyHistoryChanged()` : événement `bookslm:history-updated` pour rafraîchir la
sidebar #96 sans rechargement.
## C. Frontend — sidebar & historique (#96) — ✅ livré
- [x] `frontend/index.html` : cinquième onglet `#sidebar-tab-ai` (icône Lucide
`messages-square`) et panneau `#sidebar-panel-ai` (`#ai-history-list`,
`#ai-history-empty`).
- [x] `frontend/js/config.js` : `loadAISessionList()` / `renderAIHistoryList()` (réutilise
les classes `.recent-*` de l'UI), placeholder « Aucune conversation », listener
`bookslm:history-updated` monté par `initSidebarTabs()` et actif quand l'onglet `ai`
est affiché.
- [x] Le clic sur une conversation l'ouvre dans le panneau Assistant IA
(via `openWithSession`) et bascule la sidebar.
## D. Frontend — panneau « + » extensible (#97) — ✅ livré
- [x] Bouton « Attach an image » remplacé par un bouton **« + »** circulaire
(`frontend/js/bookslm.js`, `.bookslm-btn-plus`).
- [x] Panneau overlay `.bookslm-ext-menu` (dropdown au-dessus de la zone de saisie,
fermeture au clic extérieur / Échap) listant les modules.
- [x] Architecture **modulaire** : registre `_extensions` (objet d'enregistrement
simple) — chaque entrée : `id`, icône, libellé i18n, action ;
`renderExtMenu()` construit la liste ; un nouveau module s'ajoute en une entrée.
- [x] Modules livrés : **Fichiers** (sélecteur général `.bookslm-files-input`),
**Image** (joindre une image), **Contextes/fichiers**, **Skills**,
**Deep Research** (mode agent + prompt pré-rempli), **Recherche sur Internet**,
**Canva**.
- [x] `web`/`canva` affichés **désactivés** avec badge « Bientôt » (`.bookslm-ext-soon`)
— le catalogue d'outils #92 les alimentera.
- [x] `_startDeepResearch()` : active le mode agent, injecte le prompt Deep Research et
déclenche l'envoi.
## E. UI — bouton rond & icône (#94) — ✅ livré
- [x] `.bookslm-btn-send` circulaire (40 px, `border-radius: 50%`), icône Lucide
`arrow-up` à la place de l'emoji ✈️, aligné en bas à droite de la zone de saisie.
- [x] i18n FR/EN : `bookslm.add_options`, `bookslm.ext_files`, `bookslm.ext_image_hint`,
`bookslm.ext_contexts`, `bookslm.ext_skills`, `bookslm.ext_deep_research`,
`bookslm.ext_web`, `bookslm.ext_canva`, `bookslm.coming_soon`, `bookslm.soon`,
`bookslm.ext_more_coming`, `bookslm.deep_research_prompt`,
`bookslm.deep_research_started`, `bookslm.mode_*`.
## F. Tests — ✅ livré
- [x] `tests/test_bookslm.py` : `TestBooksLMSessionHistoryEndpoints` (7) + `TestAIGatewayHistoryStore` (4) —
liste résumée sans messages, full 404, upsert qui force l'id et isole par utilisateur,
delete, cap à 200, purge.
- [x] `tests/frontend/ai.test.mjs` : persistance/reouverture/suppression de sessions,
migration de l'historique legacy, menu d'historique.
- [x] Vérifs : pytest **1088 passed / 6 skipped**, ruff 0, mypy 0 (71 fichiers),
frontend `ai.test.mjs` **84/84**, `unit.test.mjs` 9/9, `validate-imports` 38 modules.
## H. Complément — filtre de la sidebar et menu « + » (BUG-048, #98) — ✅ livré
En retour utilisateur sur la version 2.4.0 :
*(1) **Filtre fonctionnel sur l'onglet « Historique IA » (sidebar) — #98** — la barre de
filtrage globale de la sidebar agissait uniquement sur les onglets Fichiers/Tags. Elle
s'applique désormais aussi à l'historique IA :*
- Utile `filterAIHistory(query)` (`frontend/js/config.js`) : filtre le cache de sessions
`_aiSessionsCache` (peuplé par `loadAISessionList()`) sur **titre, aperçu, répertoire,
contexte ou libellé de mode** traduit, insensible à la casse et aux accents
(`_aiNorm` : `NFD` + suppression des diacritiques + `toLowerCase`). Une requête sans
résultat affiche `bookslm.history_no_match` (nouvelle clé i18n FR/EN) **dans la liste**,
en plus de l'état vide d'origine ; un champ vide restaure tout.
- Routage dans `initSidebarFilter` (`frontend/js/sidebar.js`) : la saisie (debounce 220 ms),
le bouton casse `Aa` et le bouton « × » dirigent vers `filterAIHistory` quand l'onglet IA
est actif (`state.activeSidebarTab`), au lieu de `filterTagCloud`. Le placeholder devient
`sidebar.filter_ai` (« Filtrer l'historique IA… » / « Filter AI history… ») — résolu dans
`switchSidebarTab` (config.js), qui re-charge aussi la liste à chaque entrée dans l'onglet
en ré-appliquant la requête courante.
*(2) **Menu « + » du panneau Assistant — BUG-048** — « ajouter des contextes » ouvrait un
menu « @ » jamais affiché (et idem pour « ajouter des skills » → « / ») :* le clic sur une
entrée du panneau `.bookslm-ext-menu` remontait au gestionnaire `click` du panneau
(`_hideMenus()` + `_menuSeq++`), qui annulait le rendu asynchrone du menu ouvert juste après.
`e.stopPropagation()` est ajouté sur chaque entrée de `_renderExtMenu()` (bookslm.js) :
les menus « @ » (contextes) et « / » (skills) restent affichés. Le bouton d'ouverture porte
bien l'icône Lucide `plus` (vérifié par test JSDOM).
**Points d'attention**
- Le filtrage reste **client-side** : la charge est triviale vu le cap de 200 sessions
(`MAX_SESSIONS`). Un futur filtrage serveur n'est justifié que si la rétention croît.
- `filterAIHistory` est exportée en plus de `loadAISessionList` : `validate-imports` couvre le
nouveau contrat d'import de `sidebar.js`.
### Tests du complément
- `tests/frontend/ai-sidebar.test.mjs` (nouveau, 6 tests) : rendu complet, filtre par titre
(casse), accents (« cafe » → « café au lait »), aperçu/répertoire, restauration complète,
message « aucune correspondance ».
- `tests/frontend/ai.test.mjs` (+3) : clic « Contextes » → menu « @ » visible listé, clic
« Skills » → menu « / » visible listé, icône `plus` présente sur le bouton « + ».
- Vérifs : frontend IA **87/87**, `ai-sidebar` **6/6**, `unit` 9/9, 9 suites JSDOM vertes,
`validate-imports` 38 modules ; backend inchangé (pytest / ruff / mypy valides).
## G. Points d'attention
- Le localStorage reste un **cache** : le serveur est la source de vérité (`data/ai_history/`).
En cas de données locales corrompues, un `localStorage.clear()` réinitialise proprement.
- `MAX_SESSIONS = 200` : le comportement de purge est testé (`TestAIGatewayHistoryStore`)
; la rétention configurable (item #95 du backlog) pourra s'appuyer sur ce plafond.
- Les modules web/Canva du panneau « + » sont volontairement désactivés tant que
l'écosystème d'outils phase 2 (#92) n'est pas livré.
## I. Complément — icône « + » et pastille Deep Research (BUG-049, #100) — ✅ livré
*(1) **BUG-049 — l'icône du bouton « + » n'était pas visible.*** Le SVG Lucide était
bien rendu, mais la règle générique `.bookslm-input-area button { padding: 8px 16px;
background: var(--accent); color:#fff }` l'emportait en **spécificité** sur
`.bookslm-btn-plus` (une classe seule). Le bouton conservait `width:32px` avec
`padding: 8px 16px` → **largeur de contenu = 0 px**, donc SVG à `width: 0px`
(invisible). Correctif CSS : sélecteur porté à
`.bookslm-input-area button.bookslm-btn-plus` (et `:hover`), qui reprend la main
(`padding:0`, fond transparent, couleur `--text-secondary`). Vérifié en navigateur
(Playwright) : `svgWidth` passe de `0px` à `18px`.
*(2) **#100 — Deep Research devient une pastille (comme les skills).*** Auparavant,
cliquer sur « Deep Research » injectait la directive de recherche dans la zone de
saisie. Désormais :
- `_startDeepResearch()` active le **mode Agent** (si nécessaire), positionne le drapeau
`_activeDeepResearch` et rend une **pastille** `.bookslm-chip-deep-research`
(`_renderAttachments()`), sans rien écrire dans le composeur.
- La pastille se retire via son « × » (comme les chips skills/fichiers) et remet le
drapeau à `false`.
- La directive (`bookslm.deep_research_prompt`) est injectée **au moment de l'envoi**
dans le `message` du payload (`_sendMessage()`), sans polluer le message affiché à
l'utilisateur.
- Message d'information mis à jour (`bookslm.deep_research_started`) : « Deep Research
activé — ajoutez votre question puis envoyez. »
### Tests du complément
- `tests/frontend/ai.test.mjs` (+1) : le clic sur « Deep Research » ajoute une pastille,
laisse le composeur vide, positionne le drapeau, et le retrait de la pastille remet
le drapeau à `false`.
- Vérification navigateur du bouton « + » (Playwright, instance de test).