use anyhow::Result; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::path::PathBuf; const DEFAULT_API_URL: &str = "http://localhost:3002"; const DEV_API_URL: &str = "http://localhost:3003"; fn get_api_url() -> String { std::env::var("MOMENTRY_API_URL").unwrap_or_else(|_| { std::env::var("MOMENTRY_SERVER_PORT") .ok() .map(|port| format!("http://localhost:{}", port)) .unwrap_or_else(|| DEFAULT_API_URL.to_string()) }) } fn get_api_key() -> Option { std::env::var("MOMENTRY_API_KEY").ok() } #[derive(Debug, Clone)] pub struct ApiClient { client: Client, base_url: String, } #[derive(Debug, Deserialize, Serialize)] pub struct RegisterRequest { pub path: String, } #[derive(Debug, Deserialize, Serialize)] pub struct RegisterResponse { pub uuid: String, pub video_id: i64, pub job_id: i64, pub file_name: String, pub duration: f64, pub width: u32, pub height: u32, } #[derive(Debug, Deserialize, Serialize)] pub struct SearchRequest { pub query: String, pub limit: Option, pub uuid: Option, } #[derive(Debug, Deserialize, Serialize)] pub struct SearchResult { pub uuid: String, pub chunk_id: String, pub chunk_type: String, pub start_time: f64, pub end_time: f64, pub text: String, pub score: f32, } #[derive(Debug, Deserialize, Serialize)] pub struct SearchResponse { pub results: Vec, pub query: String, } #[derive(Debug, Deserialize, Serialize)] #[allow(dead_code)] pub struct LookupQuery { pub path: Option, pub uuid: Option, } #[derive(Debug, Deserialize, Serialize)] pub struct LookupResponse { pub uuid: String, pub file_path: Option, pub file_name: Option, pub duration: Option, } #[derive(Debug, Deserialize, Serialize)] pub struct VideoInfo { pub uuid: String, pub file_path: String, pub file_name: String, pub duration: f64, pub width: u32, pub height: u32, } #[derive(Debug, Deserialize, Serialize)] pub struct VideosResponse { pub videos: Vec, } impl ApiClient { pub fn new() -> Self { let url = get_api_url(); Self { client: Client::new(), base_url: url, } } #[allow(dead_code)] pub fn with_url(url: &str) -> Self { Self { client: Client::new(), base_url: url.to_string(), } } pub async fn register_video(&self, path: &str) -> Result { let url = format!("{}/api/v1/register", self.base_url); let request = RegisterRequest { path: path.to_string(), }; let mut request_builder = self.client.post(&url).json(&request); if let Some(key) = get_api_key() { request_builder = request_builder.header("X-API-Key", key); } let response = request_builder.send().await?; let status = response.status(); let result = response.json::().await?; if !status.is_success() { anyhow::bail!("API request failed with status: {}", status); } Ok(result) } pub async fn search_chunks( &self, query: &str, uuid: Option<&str>, limit: Option, ) -> Result { let url = format!("{}/api/v1/search", self.base_url); let request = SearchRequest { query: query.to_string(), limit, uuid: uuid.map(|s| s.to_string()), }; let mut request_builder = self.client.post(&url).json(&request); if let Some(key) = get_api_key() { request_builder = request_builder.header("X-API-Key", key); } let response = request_builder.send().await?; let status = response.status(); let result = response.json::().await?; if !status.is_success() { anyhow::bail!("API request failed with status: {}", status); } Ok(result) } pub async fn lookup_video(&self, uuid: &str) -> Result { let url = format!("{}/api/v1/lookup?uuid={}", self.base_url, uuid); let mut request = self.client.get(&url); if let Some(key) = get_api_key() { request = request.header("X-API-Key", key); } let response = request.send().await?; let status = response.status(); if status == 200 { let result = response.json::().await?; if result.uuid.is_empty() { anyhow::bail!("影片不存在: {}", uuid); } Ok(result) } else { anyhow::bail!("API request failed with status: {}", status); } } pub async fn list_videos(&self) -> Result> { let url = format!("{}/api/v1/videos", self.base_url); let mut request = self.client.get(&url); if let Some(key) = get_api_key() { request = request.header("X-API-Key", key); } let response = request.send().await?; let status = response.status(); let result = response.json::().await?; if !status.is_success() { anyhow::bail!("API request failed with status: {}", status); } Ok(result.videos) } pub fn base_url(&self) -> &str { &self.base_url } } impl Default for ApiClient { fn default() -> Self { Self::new() } } pub fn find_video_path() -> Option { let test_dirs = vec![ PathBuf::from("/Users/accusys/Movies"), PathBuf::from("/Users/accusys/Downloads"), PathBuf::from("/Users/accusys/momentry_core_project/test_video"), PathBuf::from("."), ]; for dir in test_dirs { if dir.exists() { if let Ok(entries) = std::fs::read_dir(&dir) { for entry in entries.flatten() { let path = entry.path(); if let Some(ext) = path.extension() { let ext_str = ext.to_string_lossy().to_lowercase(); if matches!( ext_str.as_str(), "mp4" | "mov" | "m4v" | "avi" | "mkv" | "webm" ) { return Some(path.to_string_lossy().to_string()); } } } } } } None }