diff --git a/src/api/agent_api.rs b/src/api/agent_api.rs new file mode 100644 index 0000000..168d48d --- /dev/null +++ b/src/api/agent_api.rs @@ -0,0 +1,87 @@ +use axum::{ + extract::State, + http::StatusCode, + response::Json, + routing::post, + Router, +}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use tracing; + +use crate::api::server::AppState; + +pub fn agent_routes() -> Router { + Router::new() + .route("/api/v1/agents/translate", post(translate_text)) +} + +#[derive(Debug, Deserialize)] +pub struct TranslationRequest { + pub text: String, + pub target_language: String, + pub source_language: Option, // "auto" if not specified +} + +#[derive(Debug, Serialize)] +pub struct TranslationResponse { + pub success: bool, + pub translated_text: String, + pub source_language_detected: String, + pub model_used: String, +} + +async fn translate_text( + State(_state): State, + Json(req): Json, +) -> Result, (StatusCode, String)> { + + let system_prompt = "You are a professional translator for Momentry Core, a digital asset management system specializing in video analysis. + + ## Guidelines: + 1. **Accuracy**: Translate the meaning accurately, maintaining the original tone. + 2. **Style**: + - For subtitles: Keep it concise and natural for reading. + - For technical terms (e.g., 5W1H, metadata): Use standard industry translations. + 3. **Output**: Return ONLY the translated text. Do not include explanations or notes."; + + let prompt = format!( + "Translate the following text to {}: \n\n{}", + req.target_language, req.text + ); + + // Call Ollama API + let client = Client::new(); + let ollama_url = "http://localhost:11434/api/generate"; + + // Using qwen3:latest which is available locally + let model = "qwen3:latest".to_string(); + + let body = serde_json::json!({ + "model": model, + "prompt": prompt, + "system": system_prompt, + "stream": false + }); + + let response = client.post(ollama_url) + .json(&body) + .send() + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to call LLM: {}", e)))?; + + let ollama_resp: serde_json::Value = response.json().await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to parse LLM response: {}", e)))?; + + let translated_text = ollama_resp.get("response") + .and_then(|v| v.as_str()) + .unwrap_or("Translation failed") + .to_string(); + + Ok(Json(TranslationResponse { + success: true, + translated_text, + source_language_detected: req.source_language.unwrap_or("unknown".to_string()), + model_used: model, + })) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 6af7f7e..b5a0ce5 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,4 @@ +pub mod agent_api; pub mod face_recognition; pub mod identities; pub mod identity_api; diff --git a/src/api/server.rs b/src/api/server.rs index 8c36407..e088e95 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -20,6 +20,7 @@ use crate::core::db::{Database, PostgresDb, QdrantDb, RedisClient, VideoRecord, use crate::core::text::tokenizer::tokenize_chinese_text; use crate::{Embedder, FileManager}; +use super::agent_api; use super::face_recognition; use super::identities; use super::identity_binding; @@ -2482,6 +2483,7 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> { post(search_visual_chunks_by_combination), ) .merge(identity_api::identity_routes()) // Phase 3 Routes + .merge(agent_api::agent_routes()) // Phase 6 Routes .merge(protected_routes) .layer(cors) .with_state(state);