feat: Rust commands - PersonInfo starred, faces data key, video temp file, +5 new commands

This commit is contained in:
2026-06-14 13:06:54 +08:00
parent d910bac0bb
commit 5687a56fd6

View File

@@ -27,6 +27,7 @@ struct FileInfo {
struct PersonInfo {
identity_uuid: String,
name: String,
starred: bool,
}
#[derive(Serialize)]
@@ -34,6 +35,8 @@ struct FaceInfo {
id: i64,
file_uuid: String,
frame_number: i64,
timestamp_secs: f64,
face_id: Option<String>,
confidence: f64,
}
@@ -47,6 +50,27 @@ struct TraceInfo {
frame_count: i64,
}
#[derive(Serialize)]
struct FaceCandidate {
id: i64,
face_id: Option<String>,
file_uuid: String,
frame_number: i64,
confidence: f64,
}
#[derive(Serialize)]
struct SearchIdentityResult {
identity_id: i64,
name: String,
source: String,
tmdb_id: Option<i64>,
file_uuid: Option<String>,
start_time: f64,
end_time: f64,
text_content: Option<String>,
}
const CORE_API: &str = "http://localhost:3002";
const API_KEY: &str = "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69";
@@ -133,9 +157,6 @@ async fn get_people(page: usize, per_page: usize) -> Result<Vec<PersonInfo>, Str
let json: serde_json::Value = response.json().await.map_err(|e| format!("Parse failed: {}", e))?;
eprintln!("[get_people] identities count: {}", json["identities"].as_array().map(|a| a.len()).unwrap_or(0));
let response = client.get(&url).send().await.map_err(|e| format!("Request failed: {}", e))?;
let json: serde_json::Value = response.json().await.map_err(|e| format!("Parse failed: {}", e))?;
let people: Vec<PersonInfo> = json["identities"]
.as_array()
.unwrap_or(&vec![])
@@ -144,6 +165,7 @@ async fn get_people(page: usize, per_page: usize) -> Result<Vec<PersonInfo>, Str
Some(PersonInfo {
identity_uuid: i["identity_uuid"].as_str()?.to_string(),
name: i["name"].as_str()?.to_string(),
starred: i["metadata"]["starred"].as_bool().unwrap_or(false),
})
})
.collect();
@@ -160,7 +182,7 @@ async fn get_faces(uuid: String, per_page: usize) -> Result<Vec<FaceInfo>, Strin
let response = client.get(&url).send().await.map_err(|e| format!("Request failed: {}", e))?;
let json: serde_json::Value = response.json().await.map_err(|e| format!("Parse failed: {}", e))?;
let faces: Vec<FaceInfo> = json["faces"]
let faces: Vec<FaceInfo> = json["data"]
.as_array()
.unwrap_or(&vec![])
.iter()
@@ -169,6 +191,8 @@ async fn get_faces(uuid: String, per_page: usize) -> Result<Vec<FaceInfo>, Strin
id: f["id"].as_i64().unwrap_or(0),
file_uuid: f["file_uuid"].as_str()?.to_string(),
frame_number: f["frame_number"].as_i64().unwrap_or(0),
timestamp_secs: f["timestamp_secs"].as_f64().unwrap_or(0.0),
face_id: f["face_id"].as_str().map(|s| s.to_string()),
confidence: f["confidence"].as_f64().unwrap_or(0.0),
})
})
@@ -221,7 +245,13 @@ async fn get_video_stream(uuid: String, start_time: f64, end_time: f64) -> Resul
.map_err(|e| format!("Video request failed: {}", e))?
.bytes().await
.map_err(|e| format!("Video read failed: {}", e))?;
Ok(format!("data:video/mp4;base64,{}", STANDARD.encode(&bytes)))
let tmp_dir = std::env::temp_dir();
let file_path = tmp_dir.join(format!("momentry_{}.mp4", uuid));
std::fs::write(&file_path, &bytes)
.map_err(|e| format!("Write temp file failed: {}", e))?;
Ok(format!("file://{}", file_path.display()))
}
const PROFILE_DIRS: &[&str] = &[
@@ -274,6 +304,98 @@ async fn delete_identity(uuid: String) -> Result<(), String> {
Ok(())
}
#[tauri::command]
async fn search_identities(query: String, limit: usize) -> Result<Vec<SearchIdentityResult>, String> {
let client = reqwest::Client::new();
let url = format!("{}/api/v1/identities/search?api_key={}&q={}", CORE_API, API_KEY, query);
let resp = client.get(&url).send().await.map_err(|e| format!("Request failed: {}", e))?;
let json: serde_json::Value = resp.json().await.map_err(|e| format!("Parse failed: {}", e))?;
let results: Vec<SearchIdentityResult> = json["results"]
.as_array()
.unwrap_or(&vec![])
.iter()
.take(limit)
.filter_map(|r| {
Some(SearchIdentityResult {
identity_id: r["identity_id"].as_i64().unwrap_or(0),
name: r["name"].as_str()?.to_string(),
source: r["source"].as_str().unwrap_or("").to_string(),
tmdb_id: r["tmdb_id"].as_i64(),
file_uuid: r["file_uuid"].as_str().map(|s| s.to_string()),
start_time: r["start_time"].as_f64().unwrap_or(0.0),
end_time: r["end_time"].as_f64().unwrap_or(0.0),
text_content: r["text_content"].as_str().map(|s| s.to_string()),
})
})
.collect();
Ok(results)
}
#[tauri::command]
async fn get_face_candidates(page: usize, per_page: usize) -> Result<Vec<FaceCandidate>, String> {
let client = reqwest::Client::new();
let url = format!("{}/api/v1/faces/candidates?api_key={}&page={}&page_size={}", CORE_API, API_KEY, page, per_page);
let resp = client.get(&url).send().await.map_err(|e| format!("Request failed: {}", e))?;
let json: serde_json::Value = resp.json().await.map_err(|e| format!("Parse failed: {}", e))?;
let candidates: Vec<FaceCandidate> = json["candidates"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|c| {
Some(FaceCandidate {
id: c["id"].as_i64().unwrap_or(0),
face_id: c["face_id"].as_str().map(|s| s.to_string()),
file_uuid: c["file_uuid"].as_str()?.to_string(),
frame_number: c["frame_number"].as_i64().unwrap_or(0),
confidence: c["confidence"].as_f64().unwrap_or(0.0),
})
})
.collect();
Ok(candidates)
}
#[tauri::command]
async fn merge_identities(uuid: String, into_uuid: String) -> Result<(), String> {
let client = reqwest::Client::new();
let url = format!("{}/api/v1/identity/{}/mergeinto?api_key={}", CORE_API, uuid, API_KEY);
let resp = client.post(&url)
.json(&serde_json::json!({"into_uuid": into_uuid}))
.send().await
.map_err(|e| format!("Request failed: {}", e))?;
if !resp.status().is_success() {
return Err(format!("Merge failed: {}", resp.status()));
}
Ok(())
}
#[tauri::command]
async fn bind_face(uuid: String, face_id: String, file_uuid: String) -> Result<(), String> {
let client = reqwest::Client::new();
let url = format!("{}/api/v1/identity/{}/bind?api_key={}", CORE_API, uuid, API_KEY);
let resp = client.post(&url)
.json(&serde_json::json!({"face_id": face_id, "file_uuid": file_uuid}))
.send().await
.map_err(|e| format!("Request failed: {}", e))?;
if !resp.status().is_success() {
return Err(format!("Bind failed: {}", resp.status()));
}
Ok(())
}
#[tauri::command]
async fn unbind_face(uuid: String, face_id: String, file_uuid: String) -> Result<(), String> {
let client = reqwest::Client::new();
let url = format!("{}/api/v1/identity/{}/unbind?api_key={}", CORE_API, uuid, API_KEY);
let resp = client.post(&url)
.json(&serde_json::json!({"face_id": face_id, "file_uuid": file_uuid}))
.send().await
.map_err(|e| format!("Request failed: {}", e))?;
if !resp.status().is_success() {
return Err(format!("Unbind failed: {}", resp.status()));
}
Ok(())
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
@@ -287,7 +409,12 @@ fn main() {
get_video_stream,
get_identity_profile,
update_identity_name,
delete_identity
delete_identity,
search_identities,
get_face_candidates,
merge_identities,
bind_face,
unbind_face
])
.run(tauri::generate_context!())
.expect("error while running tauri application");