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
+39 -1
View File
@@ -136,7 +136,7 @@ from backend.services.mutations import (
restore_backup as service_restore_backup,
)
from backend.services.recent import humanize_mtime, list_recent
from backend.services.search import advanced_search_vaults, search_paths, search_vaults
from backend.services.search import advanced_search_vaults, list_paths, search_paths, search_vaults
from backend.services.search import list_tags as service_list_tags
from backend.services.vaults import (
browse_directory,
@@ -276,6 +276,21 @@ class TreeSearchResponse(BaseModel):
results: list[TreeSearchResult] = Field(description="Matching files and directories")
class VaultPathEntry(BaseModel):
"""A single indexed path (file or directory) in a vault."""
vault: str = Field(description="Vault name")
path: str = Field(description="Full relative path")
name: str = Field(description="File or directory name")
type: str = Field(description="'file' or 'directory'")
class VaultPathsResponse(BaseModel):
"""Flat list of every indexed path in a vault (capped)."""
vault: str = Field(description="Vault name")
count: int = Field(description="Number of returned entries")
results: list[VaultPathEntry] = Field(description="Indexed files and directories")
class AdvancedSearchResultItem(BaseModel):
"""A single advanced search result with highlighted snippet."""
vault: str = Field(description="Vault name")
@@ -2580,6 +2595,29 @@ async def api_tree_search(
return search_paths(q, vault)
@app.get("/api/vault/{vault_name}/paths", response_model=VaultPathsResponse)
async def api_vault_paths(
vault_name: str,
limit: int = Query(5000, ge=1, le=20000, description="Maximum number of indexed paths to return"),
current_user=Depends(require_auth),
):
"""Return a flat list of every indexed file and directory in a vault.
Used by the AI assistant ``@`` mention menu to filter paths instantly on
the client (one request instead of one per keystroke).
Args:
vault_name: Name of the vault.
limit: Maximum number of entries returned.
Returns:
``VaultPathsResponse`` with the vault's indexed paths.
"""
if not check_vault_access(vault_name, current_user):
raise HTTPException(status_code=403, detail=f"Accès refusé à la vault '{vault_name}'")
return list_paths(vault_name, limit=limit)
@app.get("/api/search/advanced", response_model=AdvancedSearchResponse)
async def api_advanced_search(
q: str = Query("", description="Advanced search query (supports tag:, vault:, title:, path:, ext: operators)"),
+29
View File
@@ -113,3 +113,32 @@ def search_paths(q: str, vault: str = "all") -> dict[str, Any]:
})
return {"query": q, "vault_filter": vault, "results": results}
def list_paths(vault: str, limit: int = 5000) -> dict[str, Any]:
"""Return a flat, capped list of every indexed path in a vault.
Backs the AI assistant ``@`` mention menu: fetching the whole path index
once lets the client filter files/directories instantly instead of issuing
a request per keystroke.
Args:
vault: Vault name.
limit: Maximum number of entries returned.
Returns:
``{"vault", "count", "results": [{vault, path, name, type}]}``.
"""
from backend.indexer import path_index
entries = path_index.get(vault, [])
results = [
{
"vault": vault,
"path": entry["path"],
"name": entry["name"],
"type": entry["type"],
}
for entry in entries[: max(0, limit)]
]
return {"vault": vault, "count": len(results), "results": results}