Spaces:
Sleeping
Sleeping
| use crate::error::{GeneratorError, Result}; | |
| use serde::{Deserialize, Serialize}; | |
| use std::path::Path; | |
| pub struct ApiRouterGenerator { | |
| pub router_url: String, | |
| pub api_secret: String, | |
| pub openrouter_api_key: Option<String>, | |
| } | |
| struct ImageRequest { | |
| prompt: String, | |
| model: String, | |
| } | |
| struct ImageResponse { | |
| statut: String, | |
| donnees: Option<ImageData>, | |
| } | |
| struct ImageData { | |
| image_url: Option<String>, | |
| } | |
| impl ApiRouterGenerator { | |
| pub fn new(router_url: String, api_secret: String, openrouter_api_key: Option<String>) -> Self { | |
| Self { | |
| router_url, | |
| api_secret, | |
| openrouter_api_key, | |
| } | |
| } | |
| async fn download_image(&self, url: &str, output_path: &Path) -> Result<bool> { | |
| let response = reqwest::get(url) | |
| .await | |
| .map_err(GeneratorError::RequestError)?; | |
| let bytes = response.bytes().await.map_err(GeneratorError::RequestError)?; | |
| std::fs::write(output_path, bytes)?; | |
| Ok(true) | |
| } | |
| pub async fn generate( | |
| &self, | |
| prompt: &str, | |
| output_path: &Path, | |
| scene_index: usize, | |
| is_last: bool, | |
| ) -> Result<bool> { | |
| let horror_hook = if scene_index == 1 { | |
| "SHOCKING HORROR: terrified face or lunging monster, " | |
| } else { | |
| "" | |
| }; | |
| let branding = if is_last { | |
| " with 'Darkmedia-X' branding text visible" | |
| } else { | |
| "" | |
| }; | |
| let styled_prompt = format!( | |
| "Dark Anime Horror, {}{}{branding}. 9:16 ratio, ink-wash, high resolution.", | |
| horror_hook, prompt | |
| ); | |
| let payload = ImageRequest { | |
| prompt: styled_prompt, | |
| model: "openai/dall-e-3".to_string(), | |
| }; | |
| let client = reqwest::Client::new(); | |
| let url = format!("{}/api/image", self.router_url.trim_end_matches('/')); | |
| let response = client | |
| .post(&url) | |
| .header("Authorization", format!("Bearer {}", self.api_secret)) | |
| .header("Content-Type", "application/json") | |
| .json(&payload) | |
| .timeout(std::time::Duration::from_secs(45)) | |
| .send() | |
| .await; | |
| match response { | |
| Ok(resp) => { | |
| let status = resp.status(); | |
| if status == 404 { | |
| eprintln!(" [WARN] Router 404 — fallback direct"); | |
| return self.fallback_direct(prompt, output_path, is_last).await; | |
| } | |
| if !status.is_success() { | |
| eprintln!(" [WARN] Router returned status {} — fallback direct", status); | |
| return self.fallback_direct(prompt, output_path, is_last).await; | |
| } | |
| if let Ok(data) = resp.json::<ImageResponse>().await { | |
| if data.statut == "actif" { | |
| if let Some(image_data) = data.donnees { | |
| if let Some(img_url) = image_data.image_url { | |
| return self.download_image(&img_url, output_path).await; | |
| } | |
| } | |
| } | |
| } | |
| Ok(false) | |
| } | |
| Err(e) => { | |
| eprintln!(" [WARN] Router inaccessible — fallback direct: {}", e); | |
| self.fallback_direct(prompt, output_path, is_last).await | |
| } | |
| } | |
| } | |
| async fn fallback_direct(&self, prompt: &str, output_path: &Path, is_last: bool) -> Result<bool> { | |
| let api_key = self.openrouter_api_key.as_ref() | |
| .ok_or_else(|| GeneratorError::ApiError("OPENROUTER_API_KEY not configured".to_string()))?; | |
| let branding = if is_last { | |
| " with 'Darkmedia-X' branding text visible" | |
| } else { | |
| "" | |
| }; | |
| let styled_prompt = format!( | |
| "Dark Anime Cinematic Horror Style: {}{branding}. High resolution, 9:16 vertical ratio, ink-wash textures, moody lighting.", | |
| prompt | |
| ); | |
| let client = reqwest::Client::new(); | |
| struct OpenRouterRequest { | |
| model: String, | |
| prompt: String, | |
| n: usize, | |
| size: String, | |
| } | |
| let payload = OpenRouterRequest { | |
| model: "openai/dall-e-3".to_string(), | |
| prompt: styled_prompt, | |
| n: 1, | |
| size: "1024x1792".to_string(), | |
| }; | |
| let response = client | |
| .post("https://openrouter.ai/api/v1/images/generations") | |
| .header("Authorization", format!("Bearer {}", api_key)) | |
| .json(&payload) | |
| .timeout(std::time::Duration::from_secs(120)) | |
| .send() | |
| .await?; | |
| if !response.status().is_success() { | |
| eprintln!(" 🔴 OpenRouter error: {} status", response.status()); | |
| if let Ok(text) = response.text().await { | |
| eprintln!(" Response: {}", &text[..std::cmp::min(200, text.len())]); | |
| } | |
| return Err(GeneratorError::ApiError("OpenRouter API error".to_string())); | |
| } | |
| struct OpenRouterResponse { | |
| data: Vec<OpenRouterImage>, | |
| } | |
| struct OpenRouterImage { | |
| url: String, | |
| } | |
| match response.json::<OpenRouterResponse>().await { | |
| Ok(data) => { | |
| if let Some(img) = data.data.first() { | |
| return self.download_image(&img.url, output_path).await; | |
| } | |
| Ok(false) | |
| } | |
| Err(e) => { | |
| eprintln!(" 🔴 OpenRouter JSON error: {}", e); | |
| Err(e.into()) | |
| } | |
| } | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| fn test_api_router_new() { | |
| let generator = ApiRouterGenerator::new( | |
| "http://localhost:8080".to_string(), | |
| "secret".to_string(), | |
| Some("openrouter_key".to_string()), | |
| ); | |
| assert_eq!(generator.router_url, "http://localhost:8080"); | |
| assert_eq!(generator.api_secret, "secret"); | |
| assert_eq!(generator.openrouter_api_key, Some("openrouter_key".to_string())); | |
| } | |
| } | |