feat(assistant): #91 refonte Notion de la zone de discussion — question ancrée en haut, fil chronologique, étapes repliables, barre Copier/Ajouter
This commit is contained in:
@@ -1,57 +1,81 @@
|
||||
# #91 — Assistant IA — Fenêtre de résultats : post en haut, fournisseur/modèle & bouton copier
|
||||
# #91 — Assistant IA — Zone de discussion façon Notion : post ancré en haut, fournisseur/modèle discret & barre d'actions
|
||||
|
||||
> **Statut :** ✅ Livré
|
||||
> **Effort :** 0,5-1 jour | **Impact :** 🟡
|
||||
> **Références :** [Roadmap](../ROADMAP.md) · [BooksLM #76](./bookslm.md) · [Assistant UX #80](./ai-assistant-ux.md) · [Changelog](../../CHANGELOG.md)
|
||||
|
||||
- **Description :** Trois améliorations d'expérience dans la fenêtre de résultats de
|
||||
l'assistant IA (`frontend/js/bookslm.js`, `frontend/style.css`) :
|
||||
1. **Post toujours en haut** : dès qu'un message est soumis, ce tour (le post
|
||||
utilisateur **et** la réponse de l'assistant en cours d'écriture) est épinglé en
|
||||
haut de la fenêtre de messages, afin que le résultat écrit par l'assistant reste
|
||||
toujours visible — même après re-render successifs et auto-scroll.
|
||||
2. **Fournisseur & modèle discrets** : juste au-dessus du bloc de texte rédigé par
|
||||
l'assistant, un libellé discret rapporte le fournisseur et le modèle réellement
|
||||
utilisés (fournis par le backend via le flux SSE `{token, provider, model}`).
|
||||
3. **Bouton « Copier »** : sous le bloc soumis par l'utilisateur ainsi que sous le
|
||||
bloc rédigé par l'assistant, un bouton copie le texte brut de ce bloc dans le
|
||||
presse-papiers (avec toast de confirmation).
|
||||
- **Description :** Refonte de la présentation de la fenêtre de résultats de
|
||||
l'assistant IA (`frontend/js/bookslm.js`, `frontend/style.css`) sur le modèle de
|
||||
l'assistant Notion :
|
||||
1. **Ancrage en haut** : après l'envoi, le post de l'utilisateur est amené tout en
|
||||
haut de la zone visible (`scrollIntoView({ block: 'start' })` +
|
||||
`scroll-margin-top`) ; la réponse se diffuse **en dessous**, lisible sans
|
||||
défilement. Plus de scroll-into-bottom ni d'astuce `order: -1`.
|
||||
2. **Fil Notion-style** : messages en ordre chronologique ; post utilisateur =
|
||||
bulle arrondie alignée à droite (max 80 %) ; réponse assistant = texte pleine
|
||||
largeur **sans bulle de fond** ; les appels d'outils du mode agent se replient
|
||||
dans un bloc discret « N étapes » (`<details>`).
|
||||
3. **Fournisseur & modèle discrets** : libellé au-dessus de chaque réponse,
|
||||
issu du SSE réel du backend (`provider`/`model`).
|
||||
4. **Barre d'actions au survol** : sous chaque bloc non vide, « Copier »
|
||||
(utilisateur et assistant) ; sous une réponse, « Ajouter » qui insère le texte
|
||||
dans le document ouvert dans l'éditeur Forge (aucun bouton factice).
|
||||
|
||||
## A. Post toujours en haut — ✅ livré
|
||||
- [x] **A1.** Chaque message est enveloppé dans un bloc `.bookslm-msg` ; le conteneur
|
||||
`.bookslm-messages` est un flex column et les blocs du **dernier tour** (depuis le
|
||||
dernier message utilisateur inclus) portent `.latest` → `order: -1`, les épinglant
|
||||
en haut de la fenêtre.
|
||||
- [x] **A2.** La détection du dernier tour se fait au rendu (`_renderMessages()`) en
|
||||
recherchant le dernier message `role === 'user'` ; tout ce qui suit (sa réponse, ses
|
||||
actions/cartes) reste groupé au-dessus de l'historique antérieur.
|
||||
- [x] **A3.** L'auto-scroll vers le bas (`scrollTop = scrollHeight`) conserve le dernier
|
||||
tour en haut, hors d'atteinte du contenu précédent.
|
||||
## A. Positionnement automatique (ancre en haut) — ✅ livré
|
||||
- [x] **A1.** `_renderMessages({ anchor: true })` appelé à l'envoi du message, à
|
||||
l'ouverture d'un contexte, au rechargement d'une session et à la reprise après
|
||||
confirmation agent : cible = dernier `.bookslm-msg.user`, `scrollIntoView`
|
||||
fluide `block: 'start'`, repli calcul `scrollTop += delta` si indisponible.
|
||||
- [x] **A2.** Re-rendus de streaming : la position de scroll est **préservée**
|
||||
(plus de `scrollTop = scrollHeight`) — la réponse s'allonge sous la question
|
||||
sans jamais déplacer la vue.
|
||||
- [x] **A3.** Marge supérieure : `scroll-margin-top: 14px` sur `.bookslm-msg`
|
||||
pour que le post ne colle pas au bord.
|
||||
- [x] **A4.** Seule la zone du fil défile : `.bookslm-messages { flex: 1; overflow-y: auto }`
|
||||
(en-tête, toolbar, statut et barre de saisie fixes — structure déjà en place).
|
||||
|
||||
## B. Fournisseur & modèle discrets — ✅ livré
|
||||
- [x] **B1.** `_streamResponse()` capture `data.provider` et `data.model` du flux SSE
|
||||
(déjà émis par `/chat` et `/agent`) sur le message assistant.
|
||||
- [x] **B2.** Au rendu d'un message assistant, `.bookslm-msg-meta` affiche
|
||||
« `fournisseur · modèle` » au-dessus du bloc (visible uniquement si l'info est reçue).
|
||||
- [x] **B3.** Style discret (10px, `var(--text-secondary)`), non sélectionnable.
|
||||
## B. Fil chronologique Notion-style — ✅ livré
|
||||
- [x] **B1.** Ordre naturel (user puis assistant, empilés, `gap: 24px`).
|
||||
- [x] **B2.** Utilisateur : bulle `--surface2` alignée à droite, `max-width: 80%`,
|
||||
coins arrondis 18px.
|
||||
- [x] **B3.** Assistant : `width: 100%`, pas de fond ni de padding de bulle —
|
||||
le markdown occupe toute la largeur (titres, listes, code, sources inchangés).
|
||||
- [x] **B4.** Mode agent : `_renderToolActivity()` → `<details class="bookslm-tool-trace">`
|
||||
avec `<summary>` « N étapes » (i18n `ai.steps_count`) ; les lignes d'outils
|
||||
restent accessibles en déroulant le bloc.
|
||||
|
||||
## C. Bouton « Copier » — ✅ livré
|
||||
- [x] **C1.** `_appendCopyButton()` ajoute un bouton `.bookslm-copy-btn` sous chaque
|
||||
bloc non vide (utilisateur et assistant) avec le texte i18n `bookslm.copy`.
|
||||
- [x] **C2.** `_copyText()` copie le **texte brut** du message (`navigator.clipboard`,
|
||||
repli `_fallbackCopy`) et affiche le toast `bookslm.copied`.
|
||||
- [x] **C3.** Le bouton est masqué par défaut (opacité 0) et révélé au survol du bloc
|
||||
`.bookslm-msg`.
|
||||
## C. Fournisseur & modèle discrets — ✅ livré
|
||||
- [x] **C1.** `_streamResponse()` capture `data.provider` / `data.model` du flux SSE
|
||||
(déjà émis par `/chat` et `/agent`) sur le message assistant.
|
||||
- [x] **C2.** `.bookslm-msg-meta` au-dessus du bloc assistant :
|
||||
« fournisseur · modèle » (persisté avec la session, visible au rechargement).
|
||||
|
||||
## D. Tests & documentation — ✅ livré
|
||||
- [x] **D1.** `tests/frontend/ai.test.mjs` : dernier tour épinglé, tag fournisseur/modèle
|
||||
rendu, boutons copier présents sur les deux blocs, copie du texte brut, absence de
|
||||
bouton pour un contenu vide.
|
||||
- [x] **D2.** i18n FR/EN : `bookslm.copied`.
|
||||
- [x] **D3.** CHANGELOG + Roadmap.
|
||||
## D. Barre d'actions — ✅ livré
|
||||
- [x] **D1.** `_appendActionBar()` sous chaque bloc non vide : bouton Copier pour
|
||||
les deux rôles ; bouton Ajouter pour l'assistant uniquement.
|
||||
- [x] **D2.** Copier : texte brut du message (`navigator.clipboard`, repli
|
||||
`execCommand`) + toast `bookslm.copied`.
|
||||
- [x] **D3.** Ajouter (`_insertIntoEditor`) : insertion du texte à la position de
|
||||
fin de sélection dans `state.editorView` (Forge) ; toast `bookslm.inserted` ou
|
||||
`bookslm.insert_no_editor` si aucun éditeur ouvert.
|
||||
- [x] **D4.** Révélation au survol / focus clavier (`.bookslm-msg:hover`,
|
||||
`:focus-within`) — icônes Lucide `copy` / `corner-down-left`.
|
||||
|
||||
## E. Points d'attention
|
||||
- Le tag provient du **backend réel** (`provider`/`model` du SSE) et reflète donc le
|
||||
modèle effectif même lorsque l'utilisateur conserve la sélection « par défaut ».
|
||||
- Le copier utilise le texte brut (pas le HTML rendu) — cohérent avec l'export de
|
||||
conversation et évite d'embarquer des liens/balises de rendu.
|
||||
## E. Tests & documentation — ✅ livré
|
||||
- [x] **E1.** `tests/frontend/ai.test.mjs` : ordre chronologique, ancrage
|
||||
`scrollIntoView block:start`, tag provider/modèle, barre d'actions par rôle,
|
||||
copie du texte brut, insertion éditeur (mock `state.editorView`), bloc
|
||||
repliable des étapes, absence de barre pour contenu vide.
|
||||
- [x] **E2.** i18n FR/EN : `bookslm.copied`, `bookslm.insert`, `bookslm.insert_hint`,
|
||||
`bookslm.inserted`, `bookslm.insert_no_editor`, `ai.steps_count`.
|
||||
- [x] **E3.** CHANGELOG + Roadmap ; `SW_VERSION` bump (cache bust).
|
||||
|
||||
## F. Points d'attention
|
||||
- Le tag fournisseur provient du **backend réel** (SSE), donc correct même avec la
|
||||
sélection « par défaut » du fournisseur.
|
||||
- Le streaming appelle `_renderMessages()` à chaque chunk : l'ancre est posée une
|
||||
seule fois à l'envoi, les re-rendus préservent ensuite le scroll — pas de sauts.
|
||||
- L'ancien épinglage flex `order: -1` (première itération de #91) est **remplacé** :
|
||||
l'ordre est redevenu chronologique, l'ancrage est fait par défilement explicite.
|
||||
- Les pouces haut/bas Notion ne sont **pas** repris : aucun signal n'existe
|
||||
côté backend ; « Ajouter » (insertion éditeur réelle) les remplace utilement.
|
||||
Reference in New Issue
Block a user