feat(ai): menu @ instantane, selecteurs agrandis et BooksLM racine (#82)
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:
+42
-1
@@ -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)}`
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user