174 lines
6.3 KiB
Rust
174 lines
6.3 KiB
Rust
use crate::config::get_config;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SearchRequest {
|
|
pub query: String,
|
|
pub limit: Option<usize>,
|
|
pub mode: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SearchResult {
|
|
pub query: String,
|
|
pub count: usize,
|
|
pub hits: Vec<SearchHit>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SearchHit {
|
|
pub id: String,
|
|
pub vid: String,
|
|
pub start_frame: i64,
|
|
pub end_frame: i64,
|
|
pub fps: f64,
|
|
pub start: f64,
|
|
pub end: f64,
|
|
pub text: String,
|
|
pub score: f64,
|
|
#[serde(default)]
|
|
pub title: Option<String>,
|
|
#[serde(default)]
|
|
pub file_path: Option<String>,
|
|
#[serde(default)]
|
|
pub has_visual_stats: Option<bool>,
|
|
#[serde(default)]
|
|
pub parent_id: Option<String>,
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn search_videos(
|
|
query: String,
|
|
limit: Option<usize>,
|
|
mode: Option<String>,
|
|
) -> Result<SearchResult, String> {
|
|
let config = get_config();
|
|
let client = reqwest::Client::new();
|
|
|
|
let search_mode = mode.unwrap_or_else(|| "vector".to_string());
|
|
|
|
let request_body = SearchRequest {
|
|
query: query.clone(),
|
|
limit: limit.or(Some(10)),
|
|
mode: Some(search_mode.clone()),
|
|
};
|
|
|
|
let url = format!("{}/api/v1/search", config.api_base_url);
|
|
|
|
let response = client
|
|
.post(&url)
|
|
.header("Content-Type", "application/json")
|
|
.header("x-api-key", &config.api_key)
|
|
.json(&request_body)
|
|
.send()
|
|
.await
|
|
.map_err(|e| format!("Request failed: {}", e))?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(format!("API error: {}", response.status()));
|
|
}
|
|
|
|
// Parse as generic Value to handle mapping manually
|
|
let json: serde_json::Value = response
|
|
.json()
|
|
.await
|
|
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
|
|
|
// Map Backend Response to Frontend SearchResult
|
|
// Backend: { "query": "...", "results": [ ... ], "total": N, ... }
|
|
// Frontend: { "query": "...", "hits": [ ... ], "count": N }
|
|
|
|
let backend_results = json.get("results").and_then(|r| r.as_array()).cloned().unwrap_or_default();
|
|
let total = json.get("total").and_then(|t| t.as_u64()).unwrap_or(0) as usize;
|
|
|
|
let hits: Vec<SearchHit> = backend_results.into_iter().filter_map(|item| {
|
|
Some(SearchHit {
|
|
id: item.get("chunk_id").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
vid: item.get("uuid").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
start_frame: item.get("start_frame").and_then(|v| v.as_i64()).unwrap_or(0),
|
|
end_frame: item.get("end_frame").and_then(|v| v.as_i64()).unwrap_or(0),
|
|
fps: item.get("fps").and_then(|v| v.as_f64()).unwrap_or(30.0),
|
|
start: item.get("start_time").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
|
end: item.get("end_time").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
|
text: item.get("text").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
score: item.get("score").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
|
title: item.get("file_name").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
|
file_path: item.get("file_path").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
|
has_visual_stats: item.get("visual_stats").map(|_| true),
|
|
parent_id: item.get("parent_chunk_id").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
|
})
|
|
}).collect();
|
|
|
|
Ok(SearchResult {
|
|
query: json.get("query").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
count: total,
|
|
hits,
|
|
})
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn search_chunks(query: String, uuid: Option<String>) -> Result<SearchResult, String> {
|
|
let config = get_config();
|
|
let client = reqwest::Client::new();
|
|
|
|
// Backend expects uuid in the body, not query params
|
|
let url = format!("{}/api/v1/search", config.api_base_url);
|
|
|
|
let mut request_body = serde_json::json!({
|
|
"query": query,
|
|
"limit": 10
|
|
});
|
|
|
|
if let Some(vid) = uuid {
|
|
request_body["uuid"] = serde_json::json!(vid);
|
|
}
|
|
|
|
let response = client
|
|
.post(&url)
|
|
.header("Content-Type", "application/json")
|
|
.header("x-api-key", &config.api_key)
|
|
.json(&request_body)
|
|
.send()
|
|
.await
|
|
.map_err(|e| format!("Request failed: {}", e))?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(format!("API error: {}", response.status()));
|
|
}
|
|
|
|
// Parse raw JSON to handle structure mapping
|
|
let json: serde_json::Value = response
|
|
.json()
|
|
.await
|
|
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
|
|
|
// Backend returns "total", frontend expects "count"
|
|
let count = json.get("total").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
|
|
|
|
// Backend returns "results", frontend expects "hits"
|
|
let results = json.get("results").and_then(|v| v.as_array()).cloned().unwrap_or_default();
|
|
|
|
let hits: Vec<SearchHit> = results.into_iter().filter_map(|item| {
|
|
Some(SearchHit {
|
|
id: item.get("chunk_id").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
vid: item.get("uuid").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
start_frame: item.get("start_frame").and_then(|v| v.as_i64()).unwrap_or(0),
|
|
end_frame: item.get("end_frame").and_then(|v| v.as_i64()).unwrap_or(0),
|
|
fps: item.get("fps").and_then(|v| v.as_f64()).unwrap_or(30.0),
|
|
start: item.get("start_time").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
|
end: item.get("end_time").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
|
text: item.get("text").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
score: item.get("score").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
|
title: item.get("file_name").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
|
file_path: item.get("file_path").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
|
has_visual_stats: item.get("visual_stats").map(|_| true),
|
|
parent_id: item.get("parent_chunk_id").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
|
})
|
|
}).collect();
|
|
|
|
Ok(SearchResult {
|
|
query: json.get("query").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
|
count,
|
|
hits,
|
|
})
|
|
} |