From f271ac9b7a739ceb359986ed026fa4287d5c12b4 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 6 Oct 2026 20:01:47 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20menu=20=EF=BC=8B=20de=20l'assistant=20e?= =?UTF-8?q?n=20hub=20de=20contexte=20=E2=80=94=20phase=201/8=20(v7.51.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bouton + : menu à sections (fichiers/répertoires, compétences-skills, connecteurs, Design System – Canevas, Add plugins, Mémoire on/off). Sections pas encore livrées affichées « bientôt (phase N) » mais désactivées, navigation clavier ↑/↓/Entrée/Échap, focus visible, la frappe referme le menu. - Parcours « Parcourir… » : un niveau par appel via GET /api/nav/menu (contrat : dossier = icon 'folder'), fil d'Ariane cliquable, épingle de dossier via la ligne « 📌 Épingler le dossier ». - Jeton folder: résolu par ContextBuilder._single_folder() : titre du dossier + documents directs, budget ~12k caractères (marqueur « tronqué »), enfants directs seulement. - « Rechercher… » conserve l'ancien sélecteur de mentions (@ inline + recherche par nom) : régression zéro sur le chemin existant. - Tests : tests/test_v751_plus_menu.py (7 tests). Suite complète 1273 verts (-n auto), ruff check app tests propre, eslint static/js 0 erreur. - Docs : CHANGELOG, ROADMAP (phase 1 cochée), docs/V74_Agent_Plus_Menu.md statut. --- CHANGELOG.md | 30 +++++++ ROADMAP.md | 37 +++++--- VERSION | 2 +- app/main.py | 2 +- app/services/context_builder.py | 35 +++++++- app/templates/agent_panel.html | 33 ++++++- docs/V74_Agent_Plus_Menu.md | 4 +- static/js/agent_panel_2.js | 149 +++++++++++++++++++++++++++++++- tests/test_v751_plus_menu.py | 126 +++++++++++++++++++++++++++ 9 files changed, 396 insertions(+), 22 deletions(-) create mode 100644 tests/test_v751_plus_menu.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a7d979c..3912b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog - FlowDeck +## v7.51.0 (2026-10-06) — Menu + de l'assistant : hub de contexte (phase 1/8) + +### Added + +- **Menu à sections sur le bouton +** (`agent_panel.html` + `agent_panel_2.js`) : + le + ouvre *Ajouter des fichiers ou répertoires*, *Compétences-skills*, + *Connecteurs*, *Design System – Canevas*, *Add plugins*, *Mémoire (on/off)*. + Sections pas encore livrées affichées « bientôt (phase N) » et **désactivées** + (décision D3 : le menu ne promet rien que le code ne fait). Navigation clavier + ↑/↓/Entrée/Échap, focus visible, Échap revient d'un niveau. +- **Parcours fichiers & répertoires** — « Parcourir… » affiche un niveau à la fois + via `/api/nav/menu` (contrat : dossier = `icon === 'folder'`) avec fil d'Ariane + cliquable ; un document s'épingle (`document:`), un dossier s'épingle depuis + la ligne « 📌 Épingler le dossier » (`folder:`). +- **Jeton `folder:`** résolu par `ContextBuilder._mentions_context` : titre du + dossier + documents directs, budget ~12k caractères (marqueur « contenu du + dossier tronqué »). Enfants directs seulement — récursion à ajouter si les + arbres profonds deviennent réels. +- **« Rechercher… »** conserve l'ancien sélecteur de mentions (recherche par nom, + `@` inline) à l'identique : zéro régression du chemin existant. + +### Tests + +- `tests/test_v751_plus_menu.py` — **7 tests** : résolution du dossier, budget + borné, id inconnu, contrat `/api/nav/menu`, rendu du menu dans la page, + 6 sections dont 5 désactivées, jeton `folder:` côté front et côté backend. +- **Suite complète : 1273 passed / 0 failed** (`-n auto`), `ruff check app tests` + propre, `eslint static/js` **0 erreur**. + + ## v7.50.2 (2026-10-06) — Peek de Library : nu comme local-workspace (plus de bande ni double scrollbar) ### Fixed diff --git a/ROADMAP.md b/ROADMAP.md index c507e8e..40b2ff3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -284,7 +284,7 @@ Propriétés custom, AI keywords, sync API, 12 tables DB --- -## v7.51.0 → v7.58.0 — Menu + de l'assistant : hub de contexte (planifié 2026-10-06) +## v7.51.0 → v7.58.0 — Menu + de l'assistant : hub de contexte (phase 1 ✅ 2026-10-06 · phases 2-8 planifiées) > **Objectif** : faire du bouton **+** du panneau agent (à gauche de la zone d'édition) > un menu à sections, tel que demandé : @@ -301,7 +301,7 @@ Propriétés custom, AI keywords, sync API, 12 tables DB > **Doc de design** : [`docs/V74_Agent_Plus_Menu.md`](docs/V74_Agent_Plus_Menu.md) > — maquette, découpage front/back, modèle de données, endpoints, décisions, pièges. -**État des lieux lu dans le code le 2026-10-06** (rien n'est livré, tout est `- [ ]`) : +**État des lieux lu dans le code le 2026-10-06** (constat **avant** la phase 1) : - Le **+** = `toggleAddPicker()` (`static/js/agent_panel_2.js:1074`) → menu **plat** de mentions (`GET /api/agent/mentions`, `app/routers/agent.py:596`) : documents, @@ -318,18 +318,27 @@ Propriétés custom, AI keywords, sync API, 12 tables DB **Ordre** : phases 1 → 8, chacune indépendante et livrable (version + tests verts + CHANGELOG). Chiffrage : S < ½ j · M = 1–2 j · L = 3–5 j. -### Phase 1 — v7.51.0 — Menu hub + fichiers/répertoires · effort S–M +### Phase 1 — v7.51.0 — Menu hub + fichiers/répertoires · effort S–M ✅ (livrée 2026-10-06) -- [ ] **Structure du menu** — sections + sous-menus, navigation clavier (↑/↓/Entrée/Échap), +- [x] **Structure du menu** — sections + sous-menus, navigation clavier (↑/↓/Entrée/Échap), styles réutilisés depuis `.fd-ap-mention-menu` ; les sections pas encore livrées s'affichent « bientôt » et désactivées (le menu ne promet rien que le code ne fait) -- [ ] **Fichiers & répertoires** — picker actuel (recherche) **+ mode « Parcourir… »** : - arborescence via `GET /api/local-workspace/tree?folder=` + enfants de page - (`parent_id`) ; un répertoire s'épingle comme un fichier -- [ ] **Jeton `folder:`** résolu par `ContextBuilder._mentions_context` - (contenu des pages du dossier, borné en tokens) -- [ ] **Tests** — parcours, résolution du jeton dossier, rendu/états du menu -- [ ] **Livraison** — `ruff` + suite verte, bump `VERSION` + `app/main.py`, CHANGELOG + → `FD_PLUS_MENU` / `FD_PLUS_FILES` (`static/js/agent_panel_2.js`), markup + `.fd-ap-plus-menu` (`agent_panel.html`), Échap remonte d'un niveau, la frappe + referme le menu +- [x] **Fichiers & répertoires** — picker actuel (recherche) **+ mode « Parcourir… »** : + arborescence **un niveau par appel via `GET /api/nav/menu?parent_id=`** + (contrat : dossier = `icon === 'folder'` — plus simple que le tree récursif, + déjà testé), fil d'Ariane cliquable ; un répertoire s'épingle comme un fichier + (ligne « 📌 Épingler le dossier ») +- [x] **Jeton `folder:`** résolu par `ContextBuilder._mentions_context` → + `_single_folder()` : titre du dossier + documents directs, budget ~12k caractères + (marqueur « tronqué ») ; enfants directs seulement (récursion si besoin réel) +- [x] **Tests** — `tests/test_v751_plus_menu.py` : **7 tests** (résolution, budget, + id inconnu, contrat `/api/nav/menu`, rendu du menu, 6 sections / 5 désactivées, + jeton `folder:` front+back) ; **suite complète 1273 verts**, `ruff` 0, + `eslint` 0 erreur +- [x] **Livraison** — `VERSION` + `app/main.py` = **7.51.0**, CHANGELOG, ce ROADMAP ### Phase 2 — v7.52.0 — Compétences : « Gérer » + « Parcourir » · effort M @@ -407,9 +416,9 @@ CHANGELOG). Chiffrage : S < ½ j · M = 1–2 j · L = 3–5 j. --- -*Plan produit le 2026-10-06 à partir du code réel (aucune phase commencée) — à valider -phase par phase avant « go ». Chaque phase se clôt par tests verts, bump de version, -CHANGELOG et ROADMAP à jour.* +*Plan produit le 2026-10-06 à partir du code réel — **phase 1 livrée le 2026-10-06 +(v7.51.0)**, phases 2-8 à valider une par une avant « go ». Chaque phase se clôt par +tests verts, bump de version, CHANGELOG et ROADMAP à jour.* --- diff --git a/VERSION b/VERSION index ae0a37e..a260e0b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.50.2 +7.51.0 diff --git a/app/main.py b/app/main.py index 7d2bde8..4fc5d81 100644 --- a/app/main.py +++ b/app/main.py @@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="7.50.2", + version="7.51.0", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan, diff --git a/app/services/context_builder.py b/app/services/context_builder.py index 2b3efd1..c67c325 100644 --- a/app/services/context_builder.py +++ b/app/services/context_builder.py @@ -118,7 +118,7 @@ class ContextBuilder: return "\n".join(lines) def _mentions_context(self, mentions: list[str]) -> str: - """Resolve @document:x / @collection:x / @page:y / @repo:o/r mentions. + """Resolve @document:x / @collection:x / @page:y / @folder:d / @repo:o/r mentions. Mentions bring the *actual content* of the referenced object into the context so the LLM can summarise / rewrite / analyse it directly without @@ -135,6 +135,8 @@ class ContextBuilder: elif m.startswith("page:"): pid = m.split(":", 1)[1] lines.append(self._single_page(pid)) + elif m.startswith("folder:"): + lines.append(self._single_folder(m.split(":", 1)[1])) elif m.startswith("repo:"): lines.append(f"- @repo: {m.split(':', 1)[1]} (Gitea issues available via read_gitea_issues)") elif m == "ws": @@ -209,6 +211,37 @@ class ContextBuilder: return f"{head}:\n{body[:9000]}" return head + def _single_folder(self, fid: str) -> str: + """Folder (répertoire) mention : titre du dossier + ses documents directs. + + ponytail : enfants directs seulement, budget ~12k caractères — + ajouter une profondeur récursive si les arbres profonds deviennent réels. + """ + with get_conn() as conn: + row = conn.execute( + "SELECT id, title FROM pages WHERE id=? AND deleted_at IS NULL", + (fid,), + ).fetchone() + if not row: + return f"- dossier #{fid}: introuvable" + kids = conn.execute( + "SELECT id FROM pages WHERE parent_id=? AND deleted_at IS NULL " + "ORDER BY sort_order ASC, created_at DESC", + (fid,), + ).fetchall() + title = row["title"] or "Sans titre" + head = f"- dossier #{row['id']} **{title}** ({len(kids)} élément(s))" + out = [head] + used = len(head) + for k in kids: + part = self._single_document(str(k["id"])) + if used + len(part) + 1 > 12000: + out.append("- … (contenu du dossier tronqué)") + break + used += len(part) + 1 + out.append(part) + return "\n".join(out) + def _single_collection(self, cid: str) -> str: with get_conn() as conn: row = conn.execute("SELECT id, name, icon, schema_json FROM collections WHERE id=?", (cid,)).fetchone() diff --git a/app/templates/agent_panel.html b/app/templates/agent_panel.html index 9208fbf..a28d102 100644 --- a/app/templates/agent_panel.html +++ b/app/templates/agent_panel.html @@ -180,6 +180,14 @@ body.fd-ap-resizing *{cursor:col-resize!important} .fd-mention-sep{font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-dim,#888);padding:8px 14px 2px} .fd-mention-empty,.fd-mention-load{padding:11px 12px;font-size:12px;color:var(--text-dim,#aaa);text-align:center} +/* Menu + (sections) — réutilise le skin du menu de mentions */ +.fd-ap-plus-menu .fd-mention-item.disabled{opacity:.45;cursor:default;background:none} +.fd-ap-plus-menu .fd-mention-item.disabled:hover{background:none} +.fd-plus-chev{margin-left:auto;color:var(--text-dim,#999);opacity:.75;font-size:15px;flex-shrink:0} +.fd-plus-crumb{display:flex;gap:5px;flex-wrap:wrap;padding:7px 11px 3px;font-size:11px;border-bottom:1px solid var(--border,rgba(255,255,255,.08));margin-bottom:3px} +.fd-plus-crumb-it{color:var(--accent,#2383E2);cursor:pointer;white-space:nowrap} +.fd-plus-crumb-it:hover{text-decoration:underline} + /* Toast */ .fd-ap-toast{position:absolute;left:14px;right:14px;bottom:calc(100% + 10px);background:var(--bg-modal,#1c1c1c);border:1px solid rgba(35,131,226,.5);color:var(--text,#fff);font-size:12px;border-radius:9px;padding:8px 12px;box-shadow:0 10px 30px rgba(0,0,0,.45);z-index:90;display:flex;gap:8px;align-items:center;justify-content:space-between} .fd-ap-toast.err{border-color:rgba(227,98,98,.55)} @@ -380,6 +388,29 @@ body.fd-ap-resizing *{cursor:col-resize!important} +
+
+ Racine + +
+
Chargement…
+
Dossier vide
+ +
@@ -406,7 +437,7 @@ body.fd-ap-resizing *{cursor:col-resize!important} data-placeholder="Demandez à l'agent… Tapez @ pour référencer une page/base, / pour un skill. Entrée = envoyer, Maj+Entrée = ligne" @input="onInput()" @keydown="onComposerKeydown($event)" @paste="onPaste($event)">
- +