12 KiB
#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 · Changelog
- Description : quatre améliorations de l'Assistant IA livrées ensemble :
- #94 — bouton de soumission circulaire avec icône Lucide
arrow-up(remplace l'avion ✈️). - #95 — historique permanent des conversations persisté côté backend (les échanges survivent aux rechargements de page et aux navigateurs).
- #96 — accès rapide à l'historique depuis la sidebar de navigation (onglet dédié).
- #97 — panneau « + » extensible remplaçant « Attach an image » (modules : fichiers, image, contextes, skills, Deep Research, web, Canva).
- #94 — bouton de soumission circulaire avec icône Lucide
A. Backend — persistance des conversations (#95) — ✅ livré
backend/ai_history.py(nouveau) : stockage JSON par utilisateur (data/ai_history/{username}.json), écriture atomique (tmp +shutil.move), plafondMAX_SESSIONS = 200(purge des plus anciennes), tri parupdatedAtdesc.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.backend/bookslm_routes.py: modèle PydanticBookslmSessionet 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'iddu corps est forcé à l'iddu path → jamais d'écriture sous une autre clé). -DELETE /api/ai/bookslm/history/{session_id}— suppression (booléenok).- Isolation par utilisateur (
require_auth) ; saisies défensives (username/id vides) →[]ouNone.
B. Frontend — sync serveur + cache local (#95) — ✅ livré
frontend/js/bookslm.js: clé local globalebookslm-sessions-all-{username}; migration des anciennes clés périméesbookslm-sessions-<ctx>etbookslm-history-<ctx>à la première ouverture._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.- Synchronisation debounced 600 ms (
_syncHistoryToServer→_flushHistoryToServer) via_dirtySessionIds/_deletedIds:PUT/DELETEseulement pour les sessions modifiées ; pas d'appel réseau à la simple ouverture. _positionSession: nouvelle session insérée en tête, session rechargée remise à jour.openContext(opts, preferredSessionId)async ;openWithSession(sessionOrId)public pour ouvrir une conversation connue (utilisé par la sidebar #96)._notifyHistoryChanged(): événementbookslm:history-updatedpour rafraîchir la sidebar #96 sans rechargement.
C. Frontend — sidebar & historique (#96) — ✅ livré
frontend/index.html: cinquième onglet#sidebar-tab-ai(icône Lucidemessages-square) et panneau#sidebar-panel-ai(#ai-history-list,#ai-history-empty).frontend/js/config.js:loadAISessionList()/renderAIHistoryList()(réutilise les classes.recent-*de l'UI), placeholder « Aucune conversation », listenerbookslm:history-updatedmonté parinitSidebarTabs()et actif quand l'ongletaiest affiché.- Le clic sur une conversation l'ouvre dans le panneau Assistant IA
(via
openWithSession) et bascule la sidebar.
D. Frontend — panneau « + » extensible (#97) — ✅ livré
- Bouton « Attach an image » remplacé par un bouton « + » circulaire
(
frontend/js/bookslm.js,.bookslm-btn-plus). - Panneau overlay
.bookslm-ext-menu(dropdown au-dessus de la zone de saisie, fermeture au clic extérieur / Échap) listant les modules. - 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. - 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. web/canvaaffichés désactivés avec badge « Bientôt » (.bookslm-ext-soon) — le catalogue d'outils #92 les alimentera._startDeepResearch(): active le mode agent, injecte le prompt Deep Research et déclenche l'envoi.
E. UI — bouton rond & icône (#94) — ✅ livré
.bookslm-btn-sendcirculaire (40 px,border-radius: 50%), icône Lucidearrow-upà la place de l'emoji ✈️, aligné en bas à droite de la zone de saisie.- 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é
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.tests/frontend/ai.test.mjs: persistance/reouverture/suppression de sessions, migration de l'historique legacy, menu d'historique.- Vérifs : pytest 1088 passed / 6 skipped, ruff 0, mypy 0 (71 fichiers),
frontend
ai.test.mjs84/84,unit.test.mjs9/9,validate-imports38 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é parloadAISessionList()) 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 affichebookslm.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 casseAaet le bouton « × » dirigent versfilterAIHistoryquand l'onglet IA est actif (state.activeSidebarTab), au lieu defilterTagCloud. Le placeholder devientsidebar.filter_ai(« Filtrer l'historique IA… » / « Filter AI history… ») — résolu dansswitchSidebarTab(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. filterAIHistoryest exportée en plus deloadAISessionList:validate-importscouvre le nouveau contrat d'import desidebar.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ôneplusprésente sur le bouton « + ».- Vérifs : frontend IA 87/87,
ai-sidebar6/6,unit9/9, 9 suites JSDOM vertes,validate-imports38 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, unlocalStorage.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_activeDeepResearchet 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 lemessagedu 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).