Skip to content

Commit 0581363

Browse files
committed
Search: Whole-drive file search
- In-memory search index (`indexing/search.rs`): loads all entries into a `Vec<SearchEntry>`, parallel scans with rayon, glob/regex pattern matching, path reconstruction via `id_to_index` HashMap - `WRITER_GENERATION` counter in `writer.rs` bumped on every mutation for staleness detection - 4 IPC commands: `prepare_search_index`, `search_files`, `release_search_index`, `translate_search_query` - Search dialog (`SearchDialog.svelte`): command-palette-style overlay with filename input, size/date filters, keyboard-navigable results, 200ms debounce - AI mode: refactored `chat_completion` to accept `ChatCompletionOptions`, `translate_search_query` converts natural language → structured `SearchQuery` via LLM, populates filter UI with parsed values - Index lifecycle: lazy load on dialog open, 5-min idle timer on close, 10-min backstop, cancellation via `AtomicBool` every 100K rows - Directory size post-filtering: batch `get_dir_stats_batch_by_ids` lookup, then removes dirs not meeting size criteria - Shortcuts: `⌘F` / `⌥F7`, native menu item on macOS + Linux, command palette entry - Accessibility: `role="dialog"`, `aria-labelledby`, `role="listbox"` with `aria-selected`, `aria-live="polite"` status bar
1 parent c629767 commit 0581363

31 files changed

Lines changed: 3969 additions & 25 deletions

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/coverage-allowlist.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@
201201
},
202202
"settings/sections/AiSection.svelte": {
203203
"reason": "UI section, depends on Tauri commands and event listeners"
204+
},
205+
"search/SearchDialog.svelte": {
206+
"reason": "Search dialog UI, depends on Tauri commands and event listeners"
207+
},
208+
"tauri-commands/search.ts": {
209+
"reason": "Tauri command wrappers, tested via integration"
204210
}
205211
}
206212
}

apps/desktop/src-tauri/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ uzers = "0.12.2"
4242
image = "0.25.9"
4343
base64 = "0.22.1"
4444
rayon = "1.11.0"
45+
regex = "1"
4546
uuid = { version = "1.19.0", features = ["v4"] }
4647
tauri-plugin-clipboard-manager = "2"
4748
notify-debouncer-full = "0.6.0"
@@ -77,6 +78,7 @@ xattr = "1"
7778
filetime = "0.2"
7879
exacl = "0.12"
7980
sysinfo = { version = "0.38.4", default-features = false, features = ["system"] }
81+
time = { version = "0.3", features = ["parsing", "macros"] }
8082

8183
[target.'cfg(unix)'.dependencies]
8284
libc = "0.2"

apps/desktop/src-tauri/src/ai/client.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,22 @@ struct ChatChoiceMessage {
7070
content: String,
7171
}
7272

73+
/// Options for a chat completion request.
74+
pub struct ChatCompletionOptions {
75+
pub system_prompt: String,
76+
pub temperature: f32,
77+
pub max_tokens: u32,
78+
pub top_p: f32,
79+
}
80+
7381
/// Sends a chat completion request to an AI backend (local or OpenAI-compatible).
7482
///
7583
/// Returns the assistant's response text, or an error.
76-
pub async fn chat_completion(backend: &AiBackend, prompt: &str) -> Result<String, AiError> {
84+
pub async fn chat_completion(
85+
backend: &AiBackend,
86+
prompt: &str,
87+
options: &ChatCompletionOptions,
88+
) -> Result<String, AiError> {
7789
let (url, model_name, auth_header) = match backend {
7890
AiBackend::Local { port } => (
7991
format!("http://127.0.0.1:{port}/v1/chat/completions"),
@@ -96,18 +108,16 @@ pub async fn chat_completion(backend: &AiBackend, prompt: &str) -> Result<String
96108
messages: vec![
97109
ChatMessage {
98110
role: String::from("system"),
99-
content: String::from(
100-
"You are a pattern-matching assistant. Carefully observe the style, language, and formatting of existing items, then generate new items that match exactly. Output only what is requested, no formatting or explanation.",
101-
),
111+
content: options.system_prompt.clone(),
102112
},
103113
ChatMessage {
104114
role: String::from("user"),
105115
content: prompt.to_string(),
106116
},
107117
],
108-
temperature: 0.6,
109-
top_p: 0.95,
110-
max_tokens: 150, // Just need 5 folder names
118+
temperature: options.temperature,
119+
top_p: options.top_p,
120+
max_tokens: options.max_tokens,
111121
stream: false,
112122
};
113123

apps/desktop/src-tauri/src/ai/suggestions.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Builds a prompt from the current directory listing, calls the configured AI backend,
44
//! and parses the response into validated folder name suggestions.
55
6-
use super::client::AiBackend;
6+
use super::client::{AiBackend, ChatCompletionOptions};
77
use crate::file_system::get_file_at;
88

99
/// Maximum number of file names to include in the prompt context.
@@ -148,7 +148,16 @@ async fn get_suggestions_from_backend(
148148
log::debug!("AI suggestions: calling AI with {} files in context", file_names.len());
149149
log::trace!("AI suggestions: prompt:\n{prompt}");
150150

151-
match super::client::chat_completion(&backend, &prompt).await {
151+
let options = ChatCompletionOptions {
152+
system_prompt: String::from(
153+
"You are a pattern-matching assistant. Carefully observe the style, language, and formatting of existing items, then generate new items that match exactly. Output only what is requested, no formatting or explanation.",
154+
),
155+
temperature: 0.6,
156+
max_tokens: 150,
157+
top_p: 0.95,
158+
};
159+
160+
match super::client::chat_completion(&backend, &prompt, &options).await {
152161
Ok(response) => {
153162
log::trace!("AI suggestions: raw response:\n{response}");
154163
let suggestions = parse_suggestions(&response, &file_names);

apps/desktop/src-tauri/src/commands/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ immediately to business-logic modules. No significant logic lives here.
2424
| `licensing.rs` | Licensing | Status query, activation, expiry, reminder, key validation |
2525
| `indexing.rs` | Drive index | `start_drive_index`, `stop_drive_index`, `get_index_status`, `get_dir_stats`, `get_dir_stats_batch`, `clear_drive_index`, `set_indexing_enabled`, `get_index_debug_status` (dev-only extended stats). Uses `State<IndexManagerState>`. |
2626
| `clipboard.rs` | Clipboard file ops | `copy_files_to_clipboard`, `cut_files_to_clipboard`, `read_clipboard_files`, `clear_clipboard_cut_state`. macOS uses NSPasteboard via `clipboard::pasteboard`; non-macOS stubs return errors. |
27+
| `search.rs` | Drive search | `prepare_search_index`, `search_files`, `release_search_index`, `translate_search_query`. Thin wrappers over `indexing::search` module. Post-filters directory sizes after `fill_directory_sizes`. |
2728
| `sync_status.rs` | Cloud sync status | `get_sync_status` — macOS delegates to `file_system::sync_status`; non-macOS returns empty map via `#[cfg]` on the function itself (not the module). |
2829

2930
## Key decisions

apps/desktop/src-tauri/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod mtp;
1515
#[cfg(any(target_os = "macos", target_os = "linux"))]
1616
pub mod network;
1717
pub mod rename;
18+
pub mod search;
1819
pub mod settings;
1920
pub mod sync_status; // Has both macOS and non-macOS implementations
2021
pub mod ui;

0 commit comments

Comments
 (0)