refactor: #85 T5 extrait le domaine search vers backend/routers + executor partage (comportement inchange)

This commit is contained in:
2026-09-26 13:14:31 -04:00
parent 750114a923
commit dadc055429
13 changed files with 608 additions and 485 deletions
+172
View File
@@ -214,6 +214,178 @@ class RestoreResponse(BaseModel):
current_backed_up: int | None = Field(default=None, description="Timestamp of the backup created from the current version before restore, if any")
# ---------------------------------------------------------------------------
# Search / suggest / graph (#85 — extrait de backend.main, inchangé)
# ---------------------------------------------------------------------------
class SearchResultItem(BaseModel):
"""A single search result."""
vault: str = Field(description="Vault name")
path: str = Field(description="Relative file path")
title: str = Field(description="File title")
tags: list[str] = Field(description="File tags")
score: int = Field(description="Relevance score")
snippet: str = Field(description="Content excerpt with highlights")
modified: str = Field(description="ISO 8601 modification timestamp")
class SearchResponse(BaseModel):
"""Full-text search response with optional pagination."""
query: str = Field(description="Original search query")
vault_filter: str = Field(description="Vault filter applied ('all' or vault name)")
tag_filter: str | None = Field(default=None, description="Tag filter applied")
count: int = Field(description="Number of results in this response")
total: int = Field(default=0, description="Total results before pagination")
offset: int = Field(default=0, description="Current pagination offset")
limit: int = Field(default=200, description="Page size")
results: list[SearchResultItem] = Field(description="Search result items")
class TagsResponse(BaseModel):
"""Tag aggregation response."""
vault_filter: str | None = Field(default=None, description="Vault filter applied")
tags: dict[str, int] = Field(description="Tag name → count mapping")
class TreeSearchResult(BaseModel):
"""A single tree search result item."""
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'")
matched_path: str = Field(description="Path segment that matched the query")
class TreeSearchResponse(BaseModel):
"""Tree search response with matching paths."""
query: str = Field(description="Search query")
vault_filter: str = Field(description="Vault filter applied")
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")
path: str = Field(description="Relative file path")
title: str = Field(description="File title")
tags: list[str] = Field(description="File tags")
score: float = Field(description="TF-IDF relevance score (or fused RRF score in semantic mode)")
semantic_score: float = Field(default=0.0, description="Cosine similarity from the semantic index (0 when unavailable)")
snippet: str = Field(description="Content excerpt with <mark> highlights")
modified: str = Field(description="ISO 8601 modification timestamp")
extension: str = Field(default="", description="File extension")
class SearchFacets(BaseModel):
"""Faceted counts for search results."""
tags: dict[str, int] = Field(default_factory=dict)
vaults: dict[str, int] = Field(default_factory=dict)
class AdvancedSearchResponse(BaseModel):
"""Advanced search response with TF-IDF scoring, facets, and pagination."""
results: list[AdvancedSearchResultItem] = Field(description="Search results")
total: int = Field(description="Total number of matching results")
offset: int = Field(description="Current pagination offset")
limit: int = Field(description="Page size")
facets: SearchFacets = Field(description="Faceted counts by tag and vault")
query_time_ms: float = Field(default=0, description="Server-side query time in milliseconds")
semantic_available: bool = Field(default=False, description="True when the semantic (embedding) index is ready")
class TitleSuggestion(BaseModel):
"""A file title suggestion for autocomplete."""
vault: str = Field(description="Vault name")
path: str = Field(description="Relative file path")
title: str = Field(description="File title")
class SuggestResponse(BaseModel):
"""Autocomplete suggestions for file titles."""
query: str = Field(description="Original query string")
suggestions: list[TitleSuggestion] = Field(description="Matching file suggestions")
class TagSuggestion(BaseModel):
"""A tag suggestion for autocomplete."""
tag: str = Field(description="Tag name")
count: int = Field(description="Number of files with this tag")
class TagSuggestResponse(BaseModel):
"""Autocomplete suggestions for tags."""
query: str = Field(description="Original query string")
suggestions: list[TagSuggestion] = Field(description="Matching tag suggestions")
class GraphNode(BaseModel):
"""A single node in the graph view."""
id: str = Field(description="Unique node identifier")
name: str = Field(description="Display name")
type: str = Field(description="'vault', 'directory', or 'file'")
path: str = Field(description="Relative path within vault")
size: int = Field(default=0, description="File size in bytes")
tags: list[str] = Field(default_factory=list, description="Tags from frontmatter")
incoming_count: int = Field(default=0, description="Number of incoming wikilinks")
outgoing_count: int = Field(default=0, description="Number of outgoing wikilinks")
class GraphEdge(BaseModel):
"""An edge between two nodes in the graph view."""
source: str = Field(description="Source node ID")
target: str = Field(description="Target node ID")
relation: str = Field(description="'parent', 'wikilink', or 'backlink'")
class GraphResponse(BaseModel):
"""Graph data for a vault or directory."""
vault: str = Field(description="Vault name")
path: str = Field(description="Root path for the graph")
scope: str = Field(default="directory", description="'directory' or 'full'")
nodes: list[GraphNode] = Field(description="Graph nodes (files and directories)")
edges: list[GraphEdge] = Field(description="Graph edges (parent and wikilink relations)")
class ReloadResponse(BaseModel):
"""Index reload confirmation with per-vault stats."""
status: str = Field(description="Reload status ('ok' or 'error')")
vaults: dict[str, Any] = Field(description="Per-vault file counts after reload")
# ---------------------------------------------------------------------------
# PDF
# ---------------------------------------------------------------------------