feat: menu + de l'assistant en hub de contexte — phase 1/8 (v7.51.0)
- 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:<id> 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.
This commit is contained in:
+1
-1
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="fd-ap-mention-menu fd-ap-plus-menu" :class="{show: plusOpen}" x-ref="plusMenu">
|
||||
<div class="fd-plus-crumb" x-show="plusSection==='browse'">
|
||||
<span class="fd-plus-crumb-it" @click="plusGoto(-1)">Racine</span>
|
||||
<template x-for="(c, ci) in plusCrumb" :key="c.id">
|
||||
<span class="fd-plus-crumb-it" @click="plusGoto(ci)" x-text="'› ' + c.name"></span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="fd-mention-load" x-show="plusSection==='browse' && plusLoading" x-cloak>Chargement…</div>
|
||||
<div class="fd-mention-empty" x-show="plusSection==='browse' && !plusLoading && !plusItems.length" x-cloak>Dossier vide</div>
|
||||
<template x-for="(it, i) in plusItems" :key="it.key">
|
||||
<div>
|
||||
<div class="fd-mention-item" :class="{focus: i===plusFocus, disabled: !!it.disabled}"
|
||||
@click="plusAction(it)" @mouseenter="it.disabled ? null : plusFocus = i">
|
||||
<span class="fd-mention-ico" x-text="it.icon"></span>
|
||||
<div class="fd-mention-main">
|
||||
<div class="fd-mention-lbl" x-text="it.label"></div>
|
||||
<div class="fd-mention-sub" x-text="it.sub"></div>
|
||||
</div>
|
||||
<span class="fd-plus-chev" x-show="it.chev || it._folder">›</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fd-ap-chips" x-show="contextChips.length" x-cloak>
|
||||
<div class="fd-chip-row" x-show="mentionChips.length">
|
||||
@@ -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)"></div>
|
||||
<div class="fd-ap-toolbar">
|
||||
<button class="fd-ap-add" type="button" title="Ajouter un élément au contexte (@ / +)" @click="toggleAddPicker()">+</button>
|
||||
<button class="fd-ap-add" type="button" title="Ajouter au contexte : fichiers, répertoires, compétences…" aria-label="Menu d'ajout au contexte" aria-haspopup="menu" :aria-expanded="plusOpen" @click="toggleAddPicker()">+</button>
|
||||
<span class="fd-ap-tip" x-text="composerHint"></span>
|
||||
<div class="fd-ap-tools">
|
||||
<button class="fd-ap-model" :class="{off: !hasActiveProvider}" type="button" @click="modelOpen=!modelOpen" title="Choisir fournisseur et modèle">
|
||||
|
||||
Reference in New Issue
Block a user