feat(ai): menu @ instantane, selecteurs agrandis et BooksLM racine (#82)
CI / lint (push) Successful in 1m11s
CI / security (push) Successful in 47s
CI / test (push) Successful in 2m12s
CI / build (push) Successful in 45s
CI / e2e (push) Successful in 10m21s

Nouvel endpoint GET /api/vault/{vault}/paths + prechargement et filtrage client du menu @ (affichage instantane des fichiers/repertoires). Selecteurs Fournisseur/Modele agrandis (0,8rem / 34px) pour s'aligner sur le reste du site. Ajout de BooksLM au menu contextuel de la racine des vaults.
This commit is contained in:
2026-09-12 20:09:08 -04:00
parent d6cdd670b1
commit 9274dbd49f
10 changed files with 218 additions and 26 deletions
+42 -1
View File
@@ -109,6 +109,10 @@ class BooksLM {
this._menuSeq = 0;
// Timer that hides the "done" activity indicator after a short delay.
this._activityHideTimer = null;
// Cache of every indexed path per vault, backing instant `@` filtering.
this._pathCache = null;
this._pathCacheVault = null;
this._pathCachePromise = null;
}
_readAgentMode() {
@@ -214,6 +218,8 @@ class BooksLM {
await this._loadContext();
this._isLoading = false;
// Warm the `@` path cache so the mention menu appears instantly.
this._ensurePaths().catch(() => { /* best effort */ });
}
/** Backwards-compatible helper: open the assistant on a directory. */
@@ -349,6 +355,31 @@ class BooksLM {
return (first && (first.name || first)) || null;
}
/**
* Fetch (once per vault) the flat list of indexed paths so the `@` menu can
* filter files/directories instantly on the client. Falls back to an empty
* list on failure; callers then use the tree-search endpoint.
*/
async _ensurePaths() {
const vault = this._activeVault();
if (!vault) return [];
if (this._pathCache && this._pathCacheVault === vault) return this._pathCache;
if (this._pathCachePromise && this._pathCacheVault === vault) return this._pathCachePromise;
this._pathCacheVault = vault;
this._pathCachePromise = api(`/api/vault/${encodeURIComponent(vault)}/paths?limit=5000`)
.then((data) => {
const paths = Array.isArray(data.results) ? data.results : [];
this._pathCache = paths;
return paths;
})
.catch(() => {
this._pathCache = [];
return [];
})
.finally(() => { this._pathCachePromise = null; });
return this._pathCachePromise;
}
/** Open a file mentioned in an assistant answer in the main viewer. */
async _openFileLink(path) {
const vault = this._activeVault();
@@ -856,9 +887,19 @@ class BooksLM {
if (!this._panel) return;
const seq = ++this._menuSeq;
const vault = this._activeVault();
const q = normalizeSearch(query);
try {
let results = [];
if (query) {
// Fast path: filter the prefetched flat path list on the client so the
// menu updates instantly while typing.
const cached = await this._ensurePaths();
if (seq !== this._menuSeq) return;
if (cached.length) {
const matches = q
? cached.filter((p) => normalizeSearch(p.path).includes(q))
: cached;
results = matches.slice(0, 20);
} else if (query) {
const searchVault = vault || 'all';
const data = await api(
`/api/tree-search?q=${encodeURIComponent(query)}&vault=${encodeURIComponent(searchVault)}`
+2
View File
@@ -1705,6 +1705,8 @@ export const ContextMenuManager = {
if (type === 'vault') {
this._addItem('folder-plus', 'Nouveau dossier', () => this._createDirectory(), isReadonly);
this._addItem('file-plus', 'Nouveau fichier', () => this._createFile(), isReadonly);
this._addSeparator();
this._addItem('brain', '🧠 BooksLM', () => { import('./bookslm.js').then(m => m.default.open(this._targetVault, '')); }, false);
} else if (type === 'directory') {
this._addItem('folder-plus', 'Nouveau sous-dossier', () => this._createDirectory(), isReadonly);
this._addItem('file-plus', 'Nouveau fichier ici', () => this._createFile(), isReadonly);