Initial commit: Momentry Portal v0.1.0
This commit is contained in:
175
src-tauri/src/api/search.rs
Normal file
175
src-tauri/src/api/search.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use crate::config::get_config;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user