use base64::Engine;
use serde::{Deserialize, Serialize};
use crate::error::{EncodeError, ProviderError};
use crate::image_generation::{ImageGenerationRequest, ImageGenerationResponse};
pub const IMAGE_01: &str = "image-01";
pub const IMAGE_01_LIVE: &str = "image-01-live";
pub(crate) fn request_body(
model: &str,
request: &ImageGenerationRequest,
) -> Result<serde_json::Value, EncodeError> {
let mut body = serde_json::json!({
"model": model,
"prompt": request.prompt,
"response_format": "base64",
});
let param = |key: &str| {
request
.additional_params
.as_ref()
.and_then(|params| params.get(key))
};
if model != IMAGE_01 || param("aspect_ratio").is_some() {
return Ok(body);
}
let side =
|key: &str, set: u32| param(key).map_or(Some(u64::from(set)), |value| value.as_u64());
let accepted = |side: Option<u64>| {
side.is_some_and(|side| (512..=2048).contains(&side) && side.is_multiple_of(8))
};
if !accepted(side("width", request.width)) || !accepted(side("height", request.height)) {
return Err(EncodeError::request(format!(
"MiniMax images are 512 to 2048 pixels wide and high in multiples of 8, not {}x{}; \
set the width and height, or an `aspect_ratio` in `additional_params`",
request.width, request.height
)));
}
if let Some(object) = body.as_object_mut() {
object.insert("width".to_owned(), request.width.into());
object.insert("height".to_owned(), request.height.into());
}
Ok(body)
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ImageGenerationReply {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub data: Option<ImageGenerationData>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ImageGenerationData {
#[serde(default)]
pub image_base64: Vec<String>,
#[serde(default)]
pub image_urls: Vec<String>,
}
impl ImageGenerationReply {
pub fn into_response(self) -> Result<ImageGenerationResponse, ProviderError> {
let Some(encoded) = self
.data
.and_then(|data| data.image_base64.into_iter().next())
.filter(|encoded| !encoded.is_empty())
else {
return Err(ProviderError::Response(
"MiniMax returned no base64 image; it sends one only for \
`response_format: base64`, and none for an image its content filter blocked"
.to_owned(),
));
};
let image = base64::prelude::BASE64_STANDARD
.decode(encoded)
.map_err(|error| ProviderError::Response(error.to_string()))?;
Ok(ImageGenerationResponse {
response_id: self.id.filter(|id| !id.is_empty()),
..ImageGenerationResponse::new(image)
})
}
}