feat: integrate Google semantic search and branded desktop launcher
Core CI / core (ubuntu-latest) (push) Failing after 3m27s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m45s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
Core CI / core (ubuntu-latest) (push) Failing after 3m27s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m45s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
This commit is contained in:
1 parent
a90df15503
commit
caf6a2f82e
80 files changed
+14971
-837
No files matched your search
@@ -5,6 +5,7 @@ use std::path::PathBuf;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
use searchi_core::{IndexOptions, SearchIndex, SearchOptions};
|
||||
use searchi_service::HybridSearch;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "searchi", version, about = "Search your local files offline")]
|
||||
@@ -15,6 +16,12 @@ struct Cli {
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
/// Set up the required Google model in the default local cache.
|
||||
Setup {
|
||||
/// Override the platform-local model cache directory.
|
||||
#[arg(long)]
|
||||
model_dir: Option<PathBuf>,
|
||||
},
|
||||
/// Discover files and refresh an index for one folder.
|
||||
Index {
|
||||
/// SQLite index file; parent directory must exist.
|
||||
@@ -54,6 +61,25 @@ enum Command {
|
||||
modified_after_ms: Option<i64>,
|
||||
#[arg(long)]
|
||||
modified_before_ms: Option<i64>,
|
||||
/// Enable hybrid search with explicitly provisioned local model assets.
|
||||
#[arg(long, requires = "embedding_worker")]
|
||||
model_dir: Option<PathBuf>,
|
||||
/// Local native Google embedding worker, required with --model-dir.
|
||||
#[arg(long, requires = "model_dir")]
|
||||
embedding_worker: Option<PathBuf>,
|
||||
#[arg(long, default_value_t = 1, value_parser = clap::value_parser!(u8).range(1..=8))]
|
||||
cpu_threads: u8,
|
||||
},
|
||||
/// Build a persistent semantic generation from already indexed passages.
|
||||
Embed {
|
||||
#[arg(long)]
|
||||
db: PathBuf,
|
||||
#[arg(long)]
|
||||
model_dir: PathBuf,
|
||||
#[arg(long)]
|
||||
embedding_worker: PathBuf,
|
||||
#[arg(long, default_value_t = 1, value_parser = clap::value_parser!(u8).range(1..=8))]
|
||||
cpu_threads: u8,
|
||||
},
|
||||
/// Show the indexed file and content counts.
|
||||
Stats {
|
||||
@@ -66,6 +92,15 @@ enum Command {
|
||||
|
||||
fn main() -> Result<()> {
|
||||
match Cli::parse().command {
|
||||
Command::Setup { model_dir } => {
|
||||
let directory = model_dir
|
||||
.map(Ok)
|
||||
.unwrap_or_else(searchi_assets::default_model_directory)?;
|
||||
let progress = std::sync::atomic::AtomicU64::new(0);
|
||||
eprintln!("Setting up Google EmbeddingGemma 2; cached assets are reused (865 MB maximum download).");
|
||||
searchi_assets::provision(&directory, &progress)?;
|
||||
println!("Google model ready: {}", directory.display());
|
||||
}
|
||||
Command::Index { db, root, exclude } => {
|
||||
let mut options = IndexOptions::default();
|
||||
options.excluded_directory_names.extend(exclude);
|
||||
@@ -74,6 +109,9 @@ fn main() -> Result<()> {
|
||||
let report = index
|
||||
.index_folder_with_options(&root, &options)
|
||||
.with_context(|| format!("Cannot index folder {}", root.display()))?;
|
||||
index
|
||||
.prewarm()
|
||||
.context("Cannot prepare search statistics")?;
|
||||
println!(
|
||||
"Discovered: {} Indexed: {} Unchanged: {} Removed: {} Skipped: {} Extraction failures: {}",
|
||||
report.discovered, report.indexed, report.unchanged, report.removed, report.skipped, report.failures
|
||||
@@ -90,27 +128,32 @@ fn main() -> Result<()> {
|
||||
max_size,
|
||||
modified_after_ms,
|
||||
modified_before_ms,
|
||||
model_dir,
|
||||
embedding_worker,
|
||||
cpu_threads,
|
||||
} => {
|
||||
if query.trim().is_empty() {
|
||||
bail!("Search text cannot be empty");
|
||||
}
|
||||
require_existing_index(&db)?;
|
||||
let index = SearchIndex::open(&db)
|
||||
.with_context(|| format!("Cannot open index {}", db.display()))?;
|
||||
let results = index
|
||||
.search_with_options(
|
||||
&query,
|
||||
usize::from(limit),
|
||||
&SearchOptions {
|
||||
folder,
|
||||
extension,
|
||||
min_size,
|
||||
max_size,
|
||||
modified_after_unix_ms: modified_after_ms,
|
||||
modified_before_unix_ms: modified_before_ms,
|
||||
},
|
||||
)
|
||||
.context("Search failed")?;
|
||||
let options = SearchOptions {
|
||||
folder,
|
||||
extension,
|
||||
min_size,
|
||||
max_size,
|
||||
modified_after_unix_ms: modified_after_ms,
|
||||
modified_before_unix_ms: modified_before_ms,
|
||||
};
|
||||
let results = if let (Some(model), Some(runtime)) = (model_dir, embedding_worker) {
|
||||
HybridSearch::open(&db, model, runtime, usize::from(cpu_threads))?
|
||||
.search(&query, usize::from(limit), &options)
|
||||
.context("Hybrid search failed")?
|
||||
} else {
|
||||
SearchIndex::open(&db)
|
||||
.with_context(|| format!("Cannot open index {}", db.display()))?
|
||||
.search_with_options(&query, usize::from(limit), &options)
|
||||
.context("Search failed")?
|
||||
};
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&results)?);
|
||||
} else if results.is_empty() {
|
||||
@@ -130,6 +173,19 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Embed {
|
||||
db,
|
||||
model_dir,
|
||||
embedding_worker,
|
||||
cpu_threads,
|
||||
} => {
|
||||
require_existing_index(&db)?;
|
||||
let report =
|
||||
HybridSearch::open(&db, model_dir, embedding_worker, usize::from(cpu_threads))?
|
||||
.index_embeddings()
|
||||
.context("Semantic indexing failed; prior generation remains active")?;
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
}
|
||||
Command::Stats { db, json } => {
|
||||
require_existing_index(&db)?;
|
||||
let index = SearchIndex::open(&db)
|
||||
@@ -173,6 +229,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_complete_semantic_configuration_and_bounded_cpu_budget() {
|
||||
assert!(Cli::try_parse_from([
|
||||
"searchi",
|
||||
"search",
|
||||
"--db",
|
||||
"index.db",
|
||||
"query",
|
||||
"--model-dir",
|
||||
"models"
|
||||
])
|
||||
.is_err());
|
||||
assert!(Cli::try_parse_from([
|
||||
"searchi",
|
||||
"search",
|
||||
"--db",
|
||||
"index.db",
|
||||
"query",
|
||||
"--cpu-threads",
|
||||
"0"
|
||||
])
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_json_search_and_quoted_query() {
|
||||
let cli = Cli::try_parse_from([
|
||||
|
||||
Reference in new issue
Block a user