use anyhow::{anyhow, Context, Result};
use clap::Args;
use std::sync::Arc;
use colored::*;
use qdrant_client::{
qdrant::{Filter, Condition, SearchPointsBuilder},
Qdrant,
};
use crate::{
config::AppConfig,
cli::repo_commands::helpers,
cli::formatters::print_search_results,
vectordb::embedding_logic::EmbeddingHandler,
cli::commands::{FIELD_BRANCH, FIELD_LANGUAGE, FIELD_ELEMENT_TYPE},
};
#[derive(Args, Debug, Clone)]
pub struct RepoQueryArgs {
#[arg(required = true)]
pub query: String,
#[arg(short, long)]
pub name: Option<String>,
#[arg(short, long)]
pub branch: Option<String>,
#[arg(short, long, default_value_t = 10)]
pub limit: u64,
#[arg(long)]
pub lang: Option<String>,
#[arg(long = "type")]
pub element_type: Option<String>,
}
pub async fn handle_repo_query(
args: RepoQueryArgs,
config: &AppConfig, client: Arc<Qdrant>,
cli_args: &crate::cli::CliArgs,
) -> Result<()> {
let repo_name = args.name.as_ref().or(config.active_repository.as_ref())
.ok_or_else(|| anyhow!("No active repository set and no repository name provided with --name."))?;
let repo_config = config.repositories.iter()
.find(|r| &r.name == repo_name)
.ok_or_else(|| anyhow!("Configuration for repository '{}' not found.", repo_name))?;
let collection_name = helpers::get_collection_name(repo_name);
println!("Querying repository '{}' in collection '{}'...", repo_name.cyan(), collection_name.cyan());
let branch_filter = args.branch.as_ref().or(repo_config.active_branch.as_ref());
if let Some(branch) = branch_filter {
println!("Filtering by branch: {}", branch.yellow());
} else {
println!("{}", "Warning: No branch specified and repository has no active branch. Querying across all branches.".yellow());
}
let model_env_var = std::env::var("VECTORDB_ONNX_MODEL").ok();
let tokenizer_env_var = std::env::var("VECTORDB_ONNX_TOKENIZER_DIR").ok();
let onnx_model_path_str = cli_args.onnx_model_path_arg.as_ref()
.or(model_env_var.as_ref())
.or(config.onnx_model_path.as_ref())
.ok_or_else(|| anyhow!("ONNX model path must be provided via --onnx-model, VECTORDB_ONNX_MODEL, or config"))?;
let onnx_tokenizer_dir_str = cli_args.onnx_tokenizer_dir_arg.as_ref()
.or(tokenizer_env_var.as_ref())
.or(config.onnx_tokenizer_path.as_ref())
.ok_or_else(|| anyhow!("ONNX tokenizer path must be provided via --onnx-tokenizer-dir, VECTORDB_ONNX_TOKENIZER_DIR, or config"))?;
let embedding_handler = Arc::new(
EmbeddingHandler::new(
crate::vectordb::embedding::EmbeddingModelType::Onnx,
Some(onnx_model_path_str.into()),
Some(onnx_tokenizer_dir_str.into()),
)
.context("Failed to initialize embedding handler for query")?,
);
let embedding_dim = embedding_handler.dimension()?;
let query_vector = embedding_handler.embed(&[&args.query])?.remove(0);
if query_vector.len() != embedding_dim {
return Err(anyhow!(
"Query embedding dimension ({}) does not match model dimension ({}).",
query_vector.len(), embedding_dim
));
}
log::debug!("Query vector created with dimension: {}", query_vector.len());
let mut filter_conditions = vec![
];
if let Some(branch) = branch_filter {
filter_conditions.push(Condition::matches(FIELD_BRANCH, branch.clone()));
}
if let Some(lang) = &args.lang {
println!("Filtering by language: {}", lang.yellow());
filter_conditions.push(Condition::matches(FIELD_LANGUAGE, lang.clone()));
}
if let Some(el_type) = &args.element_type {
println!("Filtering by element type: {}", el_type.yellow());
filter_conditions.push(Condition::matches(FIELD_ELEMENT_TYPE, el_type.clone()));
}
let query_filter = Filter {
must: filter_conditions,
..Default::default() };
let search_request = SearchPointsBuilder::new(&collection_name, query_vector, args.limit)
.filter(query_filter)
.with_payload(true) .with_vectors(false);
println!("Performing search...");
let search_result = client.search_points(search_request).await
.context("Failed to execute search query against Qdrant")?;
println!("Search returned {} results.", search_result.result.len());
if search_result.result.is_empty() {
println!("{}", "No matching results found.".yellow());
return Ok(());
}
print_search_results(&search_result.result, &args.query)?;
Ok(())
}