use async_trait::async_trait;
use super::base::{SearchOptions, SearchProvider, SearchResult};
use crate::error::SearchError;
pub const SUPPORTED_PROVIDERS: [&str; 5] = web_capture::SEARCH_PROVIDERS;
pub struct WebCaptureProvider {
name: String,
engine: String,
enabled: bool,
weight: f64,
}
impl WebCaptureProvider {
pub fn new(engine: impl Into<String>) -> Self {
let engine = engine.into();
Self {
name: format!("wc:{engine}"),
engine,
enabled: true,
weight: 1.0,
}
}
pub fn engine(&self) -> &str {
&self.engine
}
pub fn adapt_items(&self, items: Vec<web_capture::SearchResultItem>) -> Vec<SearchResult> {
items
.into_iter()
.enumerate()
.filter_map(|(index, item)| {
if item.url.trim().is_empty() {
return None;
}
Some(SearchResult {
title: if item.title.trim().is_empty() {
"Untitled".to_string()
} else {
item.title
},
url: item.url,
snippet: item.snippet,
source: self.name.clone(),
rank: if item.rank == 0 { index + 1 } else { item.rank },
score: None,
sources: None,
})
})
.collect()
}
}
impl Default for WebCaptureProvider {
fn default() -> Self {
Self::new("wikipedia")
}
}
#[async_trait]
impl SearchProvider for WebCaptureProvider {
fn name(&self) -> &str {
&self.name
}
fn is_available(&self) -> bool {
self.enabled
}
fn weight(&self) -> f64 {
self.weight
}
fn set_weight(&mut self, weight: f64) {
self.weight = weight.clamp(0.0, 1.0);
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
async fn search(
&self,
query: &str,
options: &SearchOptions,
) -> Result<Vec<SearchResult>, SearchError> {
if query.trim().is_empty() {
return Ok(Vec::new());
}
let limit = options.limit.unwrap_or(web_capture::DEFAULT_LIMIT);
match web_capture::search(query, &self.engine, limit, "fetch", "").await {
Ok(result) => Ok(self.adapt_items(result.results)),
Err(message) => {
tracing::warn!(
provider = self.name(),
error = %message,
"WebCaptureProvider returned no results"
);
Ok(Vec::new())
}
}
}
}