darkmedia-x-api / engine /src /generators /api_router.rs
cybermedia's picture
Upload folder using huggingface_hub
343eed9 verified
use crate::error::{GeneratorError, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct ApiRouterGenerator {
pub router_url: String,
pub api_secret: String,
pub openrouter_api_key: Option<String>,
}
#[derive(Debug, Serialize)]
struct ImageRequest {
prompt: String,
model: String,
}
#[derive(Debug, Deserialize)]
struct ImageResponse {
statut: String,
donnees: Option<ImageData>,
}
#[derive(Debug, Deserialize)]
struct ImageData {
#[serde(rename = "imageUrl")]
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();
#[derive(Serialize)]
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()));
}
#[derive(Deserialize)]
struct OpenRouterResponse {
data: Vec<OpenRouterImage>,
}
#[derive(Deserialize)]
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())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
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()));
}
}