use std::path::PathBuf;
use super::{RagAdapter, RagChunk};
use crate::config::RagConfig;
pub struct AlcoveRag {
docs_root: PathBuf,
max_results: usize,
}
impl AlcoveRag {
pub fn from_config(cfg: &RagConfig) -> Option<Self> {
let docs_root = cfg
.alcove
.as_ref()
.and_then(|a| a.docs_root.as_deref())
.map(|s| {
if let Some(rest) = s.strip_prefix("~/") {
dirs::home_dir().unwrap_or_default().join(rest)
} else {
PathBuf::from(s)
}
})
.or_else(|| {
#[cfg(feature = "rag-alcove")]
{
Some(alcove::default_docs_root())
}
#[cfg(not(feature = "rag-alcove"))]
{
None
}
})?;
if !docs_root.exists() {
tracing::debug!("alcove docs_root {:?} not found, skipping RAG", docs_root);
return None;
}
let max_results = cfg.alcove.as_ref().and_then(|a| a.max_results).unwrap_or(5);
Some(Self {
docs_root,
max_results,
})
}
}
#[async_trait::async_trait]
impl RagAdapter for AlcoveRag {
fn name(&self) -> &'static str {
"alcove"
}
async fn retrieve(&self, query: &str, project: Option<&str>) -> Vec<RagChunk> {
#[cfg(feature = "rag-alcove")]
{
let docs_root = self.docs_root.clone();
let query = query.to_string();
let project = project.map(|s| s.to_string());
let max_results = self.max_results;
let result = tokio::task::spawn_blocking(move || {
alcove::ensure_index_fresh(&docs_root);
alcove::search_indexed(&docs_root, &query, max_results, project.as_deref())
})
.await;
match result {
Ok(Ok(json)) => parse_alcove_results(&json),
Ok(Err(e)) => {
tracing::warn!("alcove search failed: {e}");
Vec::new()
}
Err(e) => {
tracing::warn!("alcove search task panicked: {e}");
Vec::new()
}
}
}
#[cfg(not(feature = "rag-alcove"))]
{
let _ = (query, project);
Vec::new()
}
}
}
fn parse_alcove_results(json: &serde_json::Value) -> Vec<RagChunk> {
let matches = match json.get("matches").and_then(|m| m.as_array()) {
Some(m) => m,
None => return Vec::new(),
};
matches
.iter()
.filter_map(|m| {
let source = format!(
"{}/{}",
m.get("project").and_then(|v| v.as_str()).unwrap_or(""),
m.get("file").and_then(|v| v.as_str()).unwrap_or("")
);
let content = m.get("snippet").and_then(|v| v.as_str())?.to_string();
let score = m.get("score").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
Some(RagChunk {
adapter: "alcove",
source,
content,
score,
})
})
.collect()
}