Compare commits

..
291 changed files with 2259 additions and 6111 deletions
Generated
-1
View File
@@ -14793,7 +14793,6 @@ dependencies = [
"serde",
"serde_json",
"strum",
"test_data",
"tokio",
"uuid 1.22.0",
"workspace-build-acceleration",
@@ -15,7 +15,7 @@ body:
type: json
content: |
{
"user_token": "user_rtdhen9kz73kx", //
"user_token": "user_jg7ng4kdt2x5y", // newuser946
"payments_namespace": "artcraft"
}
order: 4100053395280358
@@ -2,7 +2,6 @@ use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_image_model::CommonImageModel;
use enums::common::generation::common_quality::CommonQuality;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation::model_creator::ModelCreator;
use enums::common::generation_provider::GenerationProvider;
use serde_derive::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
@@ -57,9 +56,6 @@ pub struct OmniGenImageProviderModelDetails {
pub struct OmniGenImageModelDetails {
pub model: CommonImageModel,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_creator: Option<ModelCreator>,
#[serde(skip_serializing_if = "Option::is_none")]
pub full_name: Option<String>,
@@ -133,7 +129,6 @@ impl Default for OmniGenImageModelDetails {
fn default() -> Self {
Self {
model: CommonImageModel::NanoBananaPro,
model_creator: None,
full_name: None,
text_prompt_supported: None,
text_prompt_max_length: None,
@@ -2,7 +2,6 @@ use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_quality::CommonQuality;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation::common_video_model::CommonVideoModel;
use enums::common::generation::model_creator::ModelCreator;
use enums::common::generation_provider::GenerationProvider;
use serde_derive::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
@@ -66,9 +65,6 @@ pub struct OmniGenVideoModelDetails {
pub model: CommonVideoModel,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_creator: Option<ModelCreator>,
#[serde(skip_serializing_if = "Option::is_none")]
pub full_name: Option<String>,
@@ -176,7 +172,6 @@ impl Default for OmniGenVideoModelDetails {
fn default() -> Self {
Self {
model: DEFAULT_VIDEO_MODEL,
model_creator: None,
full_name: None,
text_prompt_supported: None,
text_prompt_max_length: None,
@@ -1,7 +1,7 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Clone, Debug)]
pub enum AudioListRef {
MediaFileTokens(Vec<MediaFileToken>),
Urls(Vec<String>),
#[derive(Copy, Clone, Debug)]
pub enum AudioListRef<'a> {
MediaFileTokens(&'a Vec<MediaFileToken>),
Urls(&'a Vec<String>),
}
@@ -1,9 +1,9 @@
use tokens::tokens::characters::CharacterToken;
/// Forward-compatible character reference list.
#[derive(Clone, Debug)]
pub enum CharacterListRef {
CharacterTokens(Vec<CharacterToken>),
#[derive(Copy, Clone, Debug)]
pub enum CharacterListRef<'a> {
CharacterTokens(&'a Vec<CharacterToken>),
// In the future, we may have other identifiers for characters.
}
@@ -38,9 +38,6 @@ pub enum CommonVideoModel {
#[serde(rename = "seedance_2p0")]
Seedance2p0,
#[serde(rename = "seedance_2p0_fast")]
Seedance2p0Fast,
#[serde(rename = "sora_2")]
Sora2,
@@ -1,7 +1,7 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Clone, Debug)]
pub enum ImageListRef {
MediaFileTokens(Vec<MediaFileToken>),
Urls(Vec<String>),
#[derive(Copy, Clone, Debug)]
pub enum ImageListRef<'a> {
MediaFileTokens(&'a Vec<MediaFileToken>),
Urls(&'a Vec<String>),
}
@@ -1,7 +1,7 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Clone, Debug)]
pub enum ImageRef {
MediaFileToken(MediaFileToken),
Url(String),
#[derive(Copy, Clone, Debug)]
pub enum ImageRef<'a> {
MediaFileToken(&'a MediaFileToken),
Url(&'a str),
}
@@ -1,7 +1,7 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Clone, Debug)]
pub enum VideoListRef {
MediaFileTokens(Vec<MediaFileToken>),
Urls(Vec<String>),
#[derive(Copy, Clone, Debug)]
pub enum VideoListRef<'a> {
MediaFileTokens(&'a Vec<MediaFileToken>),
Urls(&'a Vec<String>),
}
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_dev::PlanArtcraftFlux1Dev;
pub(crate) fn estimate_image_cost_artcraft_flux_1_dev(
plan: &PlanArtcraftFlux1Dev,
plan: &PlanArtcraftFlux1Dev<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.02/image (2 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 2;
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_schnell::PlanArtcraftFlux1Schnell;
pub(crate) fn estimate_image_cost_artcraft_flux_1_schnell(
_plan: &PlanArtcraftFlux1Schnell,
_plan: &PlanArtcraftFlux1Schnell<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: not available
ImageGenerationCostEstimate {
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_2_lora_angles::PlanArtcraftFlux2LoraAngles;
pub(crate) fn estimate_image_cost_artcraft_flux_2_lora_angles(
plan: &PlanArtcraftFlux2LoraAngles,
plan: &PlanArtcraftFlux2LoraAngles<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 4;
@@ -43,7 +43,7 @@ mod tests {
model: CommonImageModel::Flux2LoraAngles,
provider: Provider::Artcraft,
prompt: None,
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
resolution: None,
aspect_ratio: None,
quality: None,
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1::PlanArtcraftFluxPro11;
pub(crate) fn estimate_image_cost_artcraft_flux_pro_1p1(
plan: &PlanArtcraftFluxPro11,
plan: &PlanArtcraftFluxPro11<'_>,
) -> ImageGenerationCostEstimate {
// Fal pricing: $0.04 per megapixel, billed by rounding up to the nearest
// megapixel. The default image_size values are ~1MP, so 4 cents per image.
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1_ultra::PlanArtcraftFluxPro11Ultra;
pub(crate) fn estimate_image_cost_artcraft_flux_pro_1p1_ultra(
plan: &PlanArtcraftFluxPro11Ultra,
plan: &PlanArtcraftFluxPro11Ultra<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.06/image (6 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 6;
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf
};
pub(crate) fn estimate_image_cost_artcraft_gpt_image_1(
plan: &PlanArtcraftGptImage1,
plan: &PlanArtcraftGptImage1<'_>,
) -> ImageGenerationCostEstimate {
// Pricing per the fal-ai GPT Image 1 docs, adapted for our artcraft billing:
//
@@ -62,7 +62,7 @@ mod tests {
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use tokens::tokens::media_files::MediaFileToken;
fn base() -> GenerateImageRequest {
fn base() -> GenerateImageRequest<'static> {
GenerateImageRequest {
model: CommonImageModel::GptImage1,
provider: Provider::Artcraft,
@@ -109,7 +109,7 @@ mod tests {
quality,
aspect_ratio,
image_batch_count: Some(batch),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base()
};
request.build().unwrap().estimate_costs()
@@ -7,7 +7,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_gpt_image_1p5::PlanArtcraftGptImage1p5;
pub(crate) fn estimate_image_cost_artcraft_gpt_image_1p5(
plan: &PlanArtcraftGptImage1p5,
plan: &PlanArtcraftGptImage1p5<'_>,
) -> ImageGenerationCostEstimate {
// Per fal docs (fal-ai/gpt-image-1.5 and fal-ai/gpt-image-1.5/edit):
//
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana::PlanArtcraftNanaBanana;
pub(crate) fn estimate_image_cost_artcraft_nano_banana(
plan: &PlanArtcraftNanaBanana,
plan: &PlanArtcraftNanaBanana<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 4;
@@ -6,7 +6,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_2::PlanArtcraftNanaBanana2;
pub(crate) fn estimate_image_cost_artcraft_nano_banana_2(
plan: &PlanArtcraftNanaBanana2,
plan: &PlanArtcraftNanaBanana2<'_>,
) -> ImageGenerationCostEstimate {
// Pricing (USD cents per image), scaled off the 1K base cost of $0.08:
// 0.5K → $0.06 (0.75×)
@@ -6,7 +6,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::PlanArtcraftNanaBananaPro;
pub(crate) fn estimate_image_cost_artcraft_nano_banana_pro(
plan: &PlanArtcraftNanaBananaPro,
plan: &PlanArtcraftNanaBananaPro<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.15/image at 1K or 2K resolution, $0.30/image at 4K.
// (Stored as USD cents.)
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::PlanArtcraftQwenEdit2511Angles;
pub(crate) fn estimate_image_cost_artcraft_qwen_edit_2511_angles(
plan: &PlanArtcraftQwenEdit2511Angles,
plan: &PlanArtcraftQwenEdit2511Angles<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 4;
@@ -43,7 +43,7 @@ mod tests {
model: CommonImageModel::QwenEdit2511Angles,
provider: Provider::Artcraft,
prompt: None,
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
resolution: None,
aspect_ratio: None,
quality: None,
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::PlanArtcraftSeedream4;
pub(crate) fn estimate_image_cost_artcraft_seedream_4(
plan: &PlanArtcraftSeedream4,
plan: &PlanArtcraftSeedream4<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.03/image (3 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 3;
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::PlanArtcraftSeedream4p5;
pub(crate) fn estimate_image_cost_artcraft_seedream_4p5(
plan: &PlanArtcraftSeedream4p5,
plan: &PlanArtcraftSeedream4p5<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 4;
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::PlanArtcraftSeedream5Lite;
pub(crate) fn estimate_image_cost_artcraft_seedream_5_lite(
plan: &PlanArtcraftSeedream5Lite,
plan: &PlanArtcraftSeedream5Lite<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
let cost_per_image: u64 = 4;
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev;
pub(crate) fn estimate_image_cost_fal_flux_1_dev(
plan: &PlanFalFlux1Dev,
plan: &PlanFalFlux1Dev<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.025 per megapixel, billed by rounding up to nearest megapixel.
// Default image sizes are ~1MP (1024x1024), so ~$0.025/image = 3 cents rounded up.
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell;
pub(crate) fn estimate_image_cost_fal_flux_1_schnell(
plan: &PlanFalFlux1Schnell,
plan: &PlanFalFlux1Schnell<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.003 per megapixel, billed by rounding up to nearest megapixel.
// Default image sizes are ~1MP (1024x1024), so ~$0.003/image = 1 cent rounded up.
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11;
pub(crate) fn estimate_image_cost_fal_flux_pro_1p1(
plan: &PlanFalFluxPro11,
plan: &PlanFalFluxPro11<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04 per megapixel, billed by rounding up to nearest megapixel.
// Default image sizes are ~1MP (1024x1024), so ~$0.04/image = 4 cents.
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra;
pub(crate) fn estimate_image_cost_fal_flux_pro_1p1_ultra(
plan: &PlanFalFluxPro11Ultra,
plan: &PlanFalFluxPro11Ultra<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.06 per image.
let cost_per_image: u64 = 6;
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_imag
};
pub(crate) fn estimate_image_cost_fal_gpt_image_1(
plan: &PlanFalGptImage1,
plan: &PlanFalGptImage1<'_>,
) -> ImageGenerationCostEstimate {
// Per fal docs (fal-ai/gpt-image-1/{text-to-image,edit-image}):
//
@@ -59,9 +59,9 @@ mod tests {
quality: FalGptImage1Quality,
image_size: Option<FalGptImage1ImageSize>,
num_images: FalGptImage1NumImages,
) -> PlanFalGptImage1 {
) -> PlanFalGptImage1<'static> {
PlanFalGptImage1 {
prompt: Some("p".to_string()),
prompt: Some("p"),
image_urls: vec![],
image_size,
quality,
@@ -74,12 +74,12 @@ mod tests {
image_size: Option<FalGptImage1ImageSize>,
num_images: FalGptImage1NumImages,
num_input_images: usize,
) -> PlanFalGptImage1 {
) -> PlanFalGptImage1<'static> {
let image_urls = (0..num_input_images)
.map(|i| format!("https://fake.example.com/img{}.png", i))
.collect();
PlanFalGptImage1 {
prompt: Some("p".to_string()),
prompt: Some("p"),
image_urls,
image_size,
quality,
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_imag
};
pub(crate) fn estimate_image_cost_fal_gpt_image_1p5(
plan: &PlanFalGptImage1p5,
plan: &PlanFalGptImage1p5<'_>,
) -> ImageGenerationCostEstimate {
// Per fal docs (fal-ai/gpt-image-1.5 and fal-ai/gpt-image-1.5/edit):
//
@@ -50,9 +50,9 @@ mod tests {
quality: FalGptImage1p5Quality,
image_size: Option<FalGptImage1p5ImageSize>,
num_images: FalGptImage1p5NumImages,
) -> PlanFalGptImage1p5 {
) -> PlanFalGptImage1p5<'static> {
PlanFalGptImage1p5 {
prompt: Some("p".to_string()),
prompt: Some("p"),
image_urls: vec![],
image_size,
quality,
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
};
pub(crate) fn estimate_image_cost_fal_nano_banana(
plan: &PlanFalNanoBanana,
plan: &PlanFalNanoBanana<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.039/image (no resolution multiplier — Gemini 25 Flash has no resolution option).
// Rounded to 4 cents per image.
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
};
pub(crate) fn estimate_image_cost_fal_nano_banana_2(
plan: &PlanFalNanaBanana2,
plan: &PlanFalNanaBanana2<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.08/image base (1K). Multipliers: 0.5K = 0.75x, 2K = 1.5x, 4K = 2x.
// (Stored as USD cents.)
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
};
pub(crate) fn estimate_image_cost_fal_nano_banana_pro(
plan: &PlanFalNanaBananaPro,
plan: &PlanFalNanaBananaPro<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.15/image at 1K or 2K resolution, $0.30/image at 4K.
// (Stored as USD cents.)
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4;
pub(crate) fn estimate_image_cost_fal_seedream_4(
plan: &PlanFalSeedream4,
plan: &PlanFalSeedream4<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.03 per image.
let cost_per_image: u64 = 3;
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5;
pub(crate) fn estimate_image_cost_fal_seedream_4p5(
plan: &PlanFalSeedream4p5,
plan: &PlanFalSeedream4p5<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04 per image.
let cost_per_image: u64 = 4;
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite;
pub(crate) fn estimate_image_cost_fal_seedream_5_lite(
plan: &PlanFalSeedream5Lite,
plan: &PlanFalSeedream5Lite<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.035 per image — round up to 4 cents.
let cost_per_image: u64 = 4;
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image:
use artcraft_client::endpoints::generate::image::text::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
pub async fn execute_artcraft_flux_1_dev(
plan: &PlanArtcraftFlux1Dev,
plan: &PlanArtcraftFlux1Dev<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = GenerateFlux1DevTextToImageRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
aspect_ratio: plan.aspect_ratio,
num_images: Some(plan.num_images),
};
@@ -49,7 +49,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_flux_1_dev_image_request()
};
@@ -72,7 +72,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::Square),
quality: None,
image_batch_count: Some(4),
prompt: Some("a dog surfing a wave, cinematic".to_string()),
prompt: Some("a dog surfing a wave, cinematic"),
..base_flux_1_dev_image_request()
};
@@ -98,7 +98,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a bear walking through an abandoned desert city at night".to_string()),
prompt: Some("a bear walking through an abandoned desert city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_flux_1_dev_image_request()
};
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_im
use artcraft_client::endpoints::generate::image::text::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
pub async fn execute_artcraft_flux_1_schnell(
plan: &PlanArtcraftFlux1Schnell,
plan: &PlanArtcraftFlux1Schnell<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = GenerateFlux1SchnellTextToImageRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
aspect_ratio: plan.aspect_ratio,
num_images: Some(plan.num_images),
};
@@ -48,7 +48,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a man and woman walking through a cyberpunk city at night".to_string()),
prompt: Some("a man and woman walking through a cyberpunk city at night"),
..base_flux_1_schnell_image_request()
};
@@ -71,7 +71,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::Square),
quality: None,
image_batch_count: Some(4),
prompt: Some("a dog surfing a wave, cinematic".to_string()),
prompt: Some("a dog surfing a wave, cinematic"),
..base_flux_1_schnell_image_request()
};
@@ -97,7 +97,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a man and woman walking through a futuristic city at evening".to_string()),
prompt: Some("a man and woman walking through a futuristic city at evening"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_flux_1_schnell_image_request()
};
@@ -9,7 +9,7 @@ use artcraft_api_defs::generate::image::angle::flux_2_lora_edit_image_angle::Flu
use artcraft_client::endpoints::generate::image::angle::flux_2_lora_edit_image_angle::flux_2_lora_edit_image_angle;
pub async fn execute_artcraft_flux_2_lora_angles(
plan: &PlanArtcraftFlux2LoraAngles,
plan: &PlanArtcraftFlux2LoraAngles<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = Flux2LoraEditImageAngleRequest {
@@ -53,7 +53,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::SquareHd),
quality: None,
image_batch_count: Some(1),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
horizontal_angle: Some(30.0),
vertical_angle: Some(0.0),
zoom: Some(1.0),
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
pub async fn execute_artcraft_flux_pro_1p1(
plan: &PlanArtcraftFluxPro11,
plan: &PlanArtcraftFluxPro11<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = GenerateFluxPro11TextToImageRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
aspect_ratio: plan.aspect_ratio,
num_images: Some(plan.num_images),
};
@@ -49,7 +49,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_flux_pro_1p1_image_request()
};
@@ -72,7 +72,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::Square),
quality: None,
image_batch_count: Some(4),
prompt: Some("a dog surfing a wave, cinematic".to_string()),
prompt: Some("a dog surfing a wave, cinematic"),
..base_flux_pro_1p1_image_request()
};
@@ -98,7 +98,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a bear walking through an abandoned desert city at night".to_string()),
prompt: Some("a bear walking through an abandoned desert city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_flux_pro_1p1_image_request()
};
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
pub async fn execute_artcraft_flux_pro_1p1_ultra(
plan: &PlanArtcraftFluxPro11Ultra,
plan: &PlanArtcraftFluxPro11Ultra<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = GenerateFluxPro11UltraTextToImageRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
aspect_ratio: plan.aspect_ratio,
num_images: Some(plan.num_images),
};
@@ -49,7 +49,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_flux_pro_1p1_ultra_image_request()
};
@@ -72,7 +72,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::Square),
quality: None,
image_batch_count: Some(4),
prompt: Some("a dog surfing a wave, cinematic".to_string()),
prompt: Some("a dog surfing a wave, cinematic"),
..base_flux_pro_1p1_ultra_image_request()
};
@@ -98,7 +98,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a t-rex walking through an abandoned desert city at night".to_string()),
prompt: Some("a t-rex walking through an abandoned desert city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_flux_pro_1p1_ultra_image_request()
};
@@ -14,14 +14,14 @@ use artcraft_client::endpoints::generate::image::text::generate_gpt_image_1_text
/// text-to-image, one for image-edit), so we dispatch on whether image refs
/// are present rather than calling a single multi-function endpoint.
pub async fn execute_artcraft_gpt_image_1(
plan: &PlanArtcraftGptImage1,
plan: &PlanArtcraftGptImage1<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let inference_job_token = match plan.image_inputs.clone() {
let inference_job_token = match plan.image_inputs {
Some(image_inputs) => {
let request = GptImage1EditImageRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: Some(image_inputs.to_owned()),
image_size: plan.image_size.map(|s| s.to_edit()),
num_images: Some(plan.num_images.to_edit()),
@@ -39,7 +39,7 @@ pub async fn execute_artcraft_gpt_image_1(
None => {
let request = GenerateGptImage1TextToImageRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_size: plan.image_size.map(|s| s.to_t2i()),
num_images: Some(plan.num_images.to_t2i()),
image_quality: Some(plan.quality.to_t2i()),
@@ -89,7 +89,7 @@ mod tests {
fn build_edit_image_plan_smoke() {
let tokens = vec![MediaFileToken::new_from_str("mf_test00000000000000000000000000")];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_gpt_image_1_image_request()
};
let plan = request.build().expect("plan should build");
@@ -106,7 +106,7 @@ mod tests {
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
image_batch_count: Some(1),
prompt: Some("a horse walking through a cyberpunk city at night".to_string()),
prompt: Some("a horse walking through a cyberpunk city at night"),
..base_gpt_image_1_image_request()
};
@@ -129,8 +129,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Change the background to a desert with cacti".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a desert with cacti"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(CommonAspectRatio::Square),
image_batch_count: Some(1),
..base_gpt_image_1_image_request()
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::gpt_image_1p5_multi_func
use artcraft_client::endpoints::generate::image::multi_function::gpt_image_1p5_multi_function_image_gen_image::gpt_image_1p5_multi_function_image_gen;
pub async fn execute_artcraft_gpt_image_1p5(
plan: &PlanArtcraftGptImage1p5,
plan: &PlanArtcraftGptImage1p5<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = GptImage1p5MultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
mask_image_token: None,
num_images: Some(plan.num_images),
image_size: plan.image_size,
@@ -56,7 +56,7 @@ mod tests {
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
image_batch_count: Some(1),
prompt: Some("a horse walking through a cyberpunk city at night".to_string()),
prompt: Some("a horse walking through a cyberpunk city at night"),
..base_gpt_image_1p5_image_request()
};
@@ -81,8 +81,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Change the background to a desert with cacti".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a desert with cacti"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(CommonAspectRatio::Auto),
image_batch_count: Some(1),
..base_gpt_image_1p5_image_request()
@@ -109,7 +109,7 @@ mod tests {
let request = GenerateImageRequest {
aspect_ratio: Some(ar),
image_batch_count: Some(1),
prompt: Some("a bear walking through a abandoned desert city at night".to_string()),
prompt: Some("a bear walking through a abandoned desert city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_gpt_image_1p5_image_request()
};
@@ -140,8 +140,8 @@ mod tests {
for ar in CommonAspectRatio::iter() {
println!("--- image-edit aspect ratio: {:?} ---", ar);
let request = GenerateImageRequest {
prompt: Some("Change the background to an alien world".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to an alien world"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(ar),
image_batch_count: Some(1),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_multi_functi
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_multi_function_image_gen_image::nano_banana_multi_function_image_gen;
pub async fn execute_artcraft_nano_banana(
plan: &PlanArtcraftNanaBanana,
plan: &PlanArtcraftNanaBanana<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = NanoBananaMultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
num_images: Some(plan.num_images),
aspect_ratio: plan.aspect_ratio,
};
@@ -52,7 +52,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_nano_banana_image_request()
};
@@ -77,8 +77,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Change the background to a sunny beach".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a sunny beach"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(CommonAspectRatio::Auto),
quality: None,
image_batch_count: Some(1),
@@ -107,7 +107,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a shiba walking through a cyberpunk city at night".to_string()),
prompt: Some("a shiba walking through a cyberpunk city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_nano_banana_image_request()
};
@@ -138,8 +138,8 @@ mod tests {
for ar in CommonAspectRatio::iter() {
println!("--- image-edit aspect ratio: {:?} ---", ar);
let request = GenerateImageRequest {
prompt: Some("Change the background to a sunny beach".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a sunny beach"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_func
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_2_multi_function_image_gen_image::nano_banana_2_multi_function_image_gen;
pub async fn execute_artcraft_nano_banana_2(
plan: &PlanArtcraftNanaBanana2,
plan: &PlanArtcraftNanaBanana2<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = NanaBanana2MultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
num_images: Some(plan.num_images),
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
@@ -51,7 +51,7 @@ mod tests {
resolution: Some(CommonResolution::OneK),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_nano_banana_2_image_request()
};
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_pro_multi_fu
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_pro_multi_function_image_gen_image::nano_banana_pro_multi_function_image_gen;
pub async fn execute_artcraft_nano_banana_pro(
plan: &PlanArtcraftNanaBananaPro,
plan: &PlanArtcraftNanaBananaPro<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = NanoBananaProMultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
num_images: Some(plan.num_images),
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
@@ -53,7 +53,7 @@ mod tests {
resolution: Some(CommonResolution::OneK),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_image_request()
};
@@ -77,7 +77,7 @@ mod tests {
resolution: Some(CommonResolution::OneK),
quality: None,
image_batch_count: Some(4),
prompt: Some("a dog surfing a wave, cinematic".to_string()),
prompt: Some("a dog surfing a wave, cinematic"),
..base_image_request()
};
@@ -104,8 +104,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Add the man in the first image into the fantasy scene in the second image. Keep his hoodie and attire, but match the vibe of the fantasy scene.".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Add the man in the first image into the fantasy scene in the second image. Keep his hoodie and attire, but match the vibe of the fantasy scene."),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(CommonAspectRatio::Auto), // edit mode: preserve source dimensions
resolution: Some(CommonResolution::OneK),
quality: None,
@@ -9,7 +9,7 @@ use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::
use artcraft_client::endpoints::generate::image::angle::qwen_edit_2511_edit_image_angle::qwen_edit_2511_edit_image_angle;
pub async fn execute_artcraft_qwen_edit_2511_angles(
plan: &PlanArtcraftQwenEdit2511Angles,
plan: &PlanArtcraftQwenEdit2511Angles<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = QwenEdit2511EditImageAngleRequest {
@@ -18,7 +18,7 @@ pub async fn execute_artcraft_qwen_edit_2511_angles(
horizontal_angle: plan.horizontal_angle,
vertical_angle: plan.vertical_angle,
zoom: plan.zoom,
additional_prompt: plan.prompt.clone(),
additional_prompt: plan.prompt.map(|p| p.to_string()),
num_images: Some(plan.num_images),
image_size: plan.image_size,
};
@@ -55,7 +55,7 @@ mod tests {
quality: None,
image_batch_count: Some(1),
prompt: None,
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
horizontal_angle: Some(30.0),
vertical_angle: Some(0.0),
zoom: Some(1.0),
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4_mu
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_v4_multi_function_image_gen_image::bytedance_seedream_v4_multi_function_image_gen;
pub async fn execute_artcraft_seedream_4(
plan: &PlanArtcraftSeedream4,
plan: &PlanArtcraftSeedream4<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = BytedanceSeedreamV4MultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
num_images: Some(plan.num_images),
image_size: plan.image_size,
max_images: None,
@@ -53,7 +53,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
prompt: Some("a cat walking through a cyberpunk city at night"),
..base_seedream_4_image_request()
};
@@ -78,8 +78,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Change the background to a sunny beach".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a sunny beach"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(CommonAspectRatio::Auto),
quality: None,
image_batch_count: Some(1),
@@ -108,7 +108,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a shiba walking through a cyberpunk city at night".to_string()),
prompt: Some("a shiba walking through a cyberpunk city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_seedream_4_image_request()
};
@@ -139,8 +139,8 @@ mod tests {
for ar in CommonAspectRatio::iter() {
println!("--- image-edit aspect ratio: {:?} ---", ar);
let request = GenerateImageRequest {
prompt: Some("Change the background to a sunny beach".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a sunny beach"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4p5_
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_v4p5_multi_function_image_gen_image::bytedance_seedream_v4p5_multi_function_image_gen;
pub async fn execute_artcraft_seedream_4p5(
plan: &PlanArtcraftSeedream4p5,
plan: &PlanArtcraftSeedream4p5<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = BytedanceSeedreamV4p5MultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
num_images: Some(plan.num_images),
image_size: plan.image_size,
max_images: None,
@@ -53,7 +53,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
prompt: Some("a corgi walking through a cyberpunk city at night"),
..base_seedream_4p5_image_request()
};
@@ -78,8 +78,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Change the background to a spooky haunted house".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a spooky haunted house"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(CommonAspectRatio::Auto2k),
quality: None,
image_batch_count: Some(1),
@@ -108,7 +108,7 @@ mod tests {
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
prompt: Some("a corgi walking through a cyberpunk city at night"),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
..base_seedream_4p5_image_request()
};
@@ -139,8 +139,8 @@ mod tests {
for ar in CommonAspectRatio::iter() {
println!("--- image-edit aspect ratio: {:?} ---", ar);
let request = GenerateImageRequest {
prompt: Some("Change the background to a spooky haunted house".to_string()),
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
prompt: Some("Change the background to a spooky haunted house"),
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
aspect_ratio: Some(ar),
quality: None,
image_batch_count: Some(1),
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lit
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen_image::bytedance_seedream_5_lite_multi_function_image_gen;
pub async fn execute_artcraft_seedream_5_lite(
plan: &PlanArtcraftSeedream5Lite,
plan: &PlanArtcraftSeedream5Lite<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = BytedanceSeedream5LiteMultiFunctionImageGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_tokens: plan.image_inputs.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
num_images: Some(plan.num_images),
image_size: plan.image_size,
max_images: None,
@@ -49,7 +49,7 @@ mod tests {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
quality: None,
image_batch_count: Some(1),
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
prompt: Some("a corgi walking through a cyberpunk city at night"),
..base_seedream_5_lite_image_request()
};
@@ -13,13 +13,13 @@ use fal_client::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image
};
pub async fn execute_fal_flux_1_dev(
plan: &PlanFalFlux1Dev,
plan: &PlanFalFlux1Dev<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
// Image-to-image mode
let args = Flux1DevEditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_url: image_url.clone(),
num_images: plan.num_images.to_edit(),
webhook_url: fal_client.webhook_url.as_str(),
@@ -31,7 +31,7 @@ pub async fn execute_fal_flux_1_dev(
} else {
// Text-to-image mode
let args = Flux1DevArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_t2i(),
webhook_url: fal_client.webhook_url.as_str(),
@@ -13,7 +13,7 @@ use fal_client::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_i
};
pub async fn execute_fal_flux_1_schnell(
plan: &PlanFalFlux1Schnell,
plan: &PlanFalFlux1Schnell<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
@@ -31,7 +31,7 @@ pub async fn execute_fal_flux_1_schnell(
} else {
// Text-to-image mode
let args = Flux1SchnellArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.t2i_aspect_ratio,
num_images: plan.num_images.to_t2i(),
webhook_url: fal_client.webhook_url.as_str(),
@@ -10,11 +10,11 @@ use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_imag
};
pub async fn execute_fal_flux_pro_1p1(
plan: &PlanFalFluxPro11,
plan: &PlanFalFluxPro11<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let args = FluxPro11Args {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_fal(),
webhook_url: fal_client.webhook_url.as_str(),
@@ -10,11 +10,11 @@ use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_t
};
pub async fn execute_fal_flux_pro_1p1_ultra(
plan: &PlanFalFluxPro11Ultra,
plan: &PlanFalFluxPro11Ultra<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let args = FluxPro11UltraArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_fal(),
webhook_url: fal_client.webhook_url.as_str(),
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_gpt_image_1_text_to_imag
};
pub async fn execute_fal_gpt_image_1(
plan: &PlanFalGptImage1,
plan: &PlanFalGptImage1<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueGptImage1TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: plan.num_images.to_t2i(),
image_size: plan.image_size.map(|s| s.to_t2i()),
quality: Some(plan.quality.to_t2i()),
@@ -32,7 +32,7 @@ pub async fn execute_fal_gpt_image_1(
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueGptImage1EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: plan.num_images.to_edit(),
mask_image_url: None,
@@ -67,11 +67,11 @@ mod tests {
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use crate::test_helpers::get_fal_client;
fn base_fal_request() -> GenerateImageRequest {
fn base_fal_request() -> GenerateImageRequest<'static> {
GenerateImageRequest {
model: CommonImageModel::GptImage1,
provider: Provider::Fal,
prompt: Some("a cat in space".to_string()),
prompt: Some("a cat in space"),
image_inputs: None,
resolution: None,
aspect_ratio: None,
@@ -106,7 +106,7 @@ mod tests {
fn build_edit_image_plan_smoke() {
let urls = vec!["https://example.com/img.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_fal_request()
};
let plan = request.build().expect("plan should build");
@@ -123,7 +123,7 @@ mod tests {
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
image_batch_count: Some(1),
prompt: Some("a horse walking through a cyberpunk city at night".to_string()),
prompt: Some("a horse walking through a cyberpunk city at night"),
..base_fal_request()
};
@@ -143,10 +143,10 @@ mod tests {
TREX_SKELETON_IMAGE_URL.to_string(),
];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
aspect_ratio: Some(CommonAspectRatio::Square),
image_batch_count: Some(1),
prompt: Some("change the background to a desert".to_string()),
prompt: Some("change the background to a desert"),
..base_fal_request()
};
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_im
};
pub async fn execute_fal_gpt_image_1p5(
plan: &PlanFalGptImage1p5,
plan: &PlanFalGptImage1p5<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueGptImage1p5TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: plan.num_images.to_t2i(),
image_size: plan.image_size.map(|s| s.to_t2i()),
background: None,
@@ -32,7 +32,7 @@ pub async fn execute_fal_gpt_image_1p5(
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueGptImage1p5EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: plan.num_images.to_edit(),
mask_image_url: None,
@@ -17,13 +17,13 @@ use fal_client::requests::webhook::image::text::enqueue_gemini_25_flash_text_to_
};
pub async fn execute_fal_nano_banana(
plan: &PlanFalNanoBanana,
plan: &PlanFalNanoBanana<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let args = Gemini25FlashTextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
aspect_ratio: plan.t2i_aspect_ratio,
webhook_url: fal_client.webhook_url.as_str(),
@@ -35,7 +35,7 @@ pub async fn execute_fal_nano_banana(
} else {
// Image-edit mode
let args = Gemini25FlashEditArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
aspect_ratio: plan.edit_aspect_ratio,
@@ -17,13 +17,13 @@ use fal_client::requests::webhook::image::text::enqueue_nano_banana_2_text_to_im
};
pub async fn execute_fal_nano_banana_2(
plan: &PlanFalNanaBanana2,
plan: &PlanFalNanaBanana2<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let args = EnqueueNanoBanana2TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
resolution: plan.resolution.map(to_t2i_resolution),
aspect_ratio: plan.t2i_aspect_ratio,
@@ -36,7 +36,7 @@ pub async fn execute_fal_nano_banana_2(
} else {
// Image-edit mode
let args = EnqueueNanoBanana2EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
resolution: plan.resolution.map(to_edit_resolution),
@@ -16,13 +16,13 @@ use fal_client::requests::webhook::image::text::enqueue_nano_banana_pro_text_to_
EnqueueNanoBananaProTextToImageNumImages, EnqueueNanoBananaProTextToImageResolution,
};
pub async fn execute_fal_nano_banana_pro(
plan: &PlanFalNanaBananaPro,
plan: &PlanFalNanaBananaPro<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let args = EnqueueNanoBananaProTextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
resolution: plan.resolution.map(to_t2i_resolution),
aspect_ratio: plan.t2i_aspect_ratio,
@@ -35,7 +35,7 @@ pub async fn execute_fal_nano_banana_pro(
} else {
// Image-edit mode
let args = EnqueueNanoBananaProEditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
resolution: plan.resolution.map(to_edit_resolution),
@@ -107,7 +107,7 @@ mod tests {
resolution: Some(CommonResolution::TwoK),
quality: None,
image_batch_count: Some(1),
prompt: Some("a cyberpunk city skyline at dusk, neon lights reflecting on rain-soaked streets".to_string()),
prompt: Some("a cyberpunk city skyline at dusk, neon lights reflecting on rain-soaked streets"),
..base_fal_image_request()
};
@@ -132,7 +132,7 @@ mod tests {
resolution: Some(CommonResolution::OneK),
quality: None,
image_batch_count: Some(4),
prompt: Some("a golden retriever surfing a wave, cinematic, 4K".to_string()),
prompt: Some("a golden retriever surfing a wave, cinematic, 4K"),
..base_fal_image_request()
};
@@ -159,8 +159,8 @@ mod tests {
];
let request = GenerateImageRequest {
prompt: Some("Add the ghost from the first image hovering above the T-Rex skeleton in the second image, make it look spooky but friendly".to_string()),
image_inputs: Some(ImageListRef::Urls(image_urls.clone())),
prompt: Some("Add the ghost from the first image hovering above the T-Rex skeleton in the second image, make it look spooky but friendly"),
image_inputs: Some(ImageListRef::Urls(&image_urls)),
aspect_ratio: Some(CommonAspectRatio::Auto), // edit mode: preserve source dimensions
resolution: Some(CommonResolution::TwoK),
quality: None,
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_te
};
pub async fn execute_fal_seedream_4(
plan: &PlanFalSeedream4,
plan: &PlanFalSeedream4<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueBytedanceSeedreamV4TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
@@ -30,7 +30,7 @@ pub async fn execute_fal_seedream_4(
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueBytedanceSeedreamV4EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_
};
pub async fn execute_fal_seedream_4p5(
plan: &PlanFalSeedream4p5,
plan: &PlanFalSeedream4p5<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueBytedanceSeedreamV4p5TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
@@ -30,7 +30,7 @@ pub async fn execute_fal_seedream_4p5(
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueBytedanceSeedreamV4p5EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_li
};
pub async fn execute_fal_seedream_5_lite(
plan: &PlanFalSeedream5Lite,
plan: &PlanFalSeedream5Lite<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
@@ -30,7 +30,7 @@ pub async fn execute_fal_seedream_5_lite(
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
@@ -37,7 +37,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::plan_generate_image_fal_nano_banana_pro;
#[derive(Clone, Debug)]
pub struct GenerateImageRequest {
pub struct GenerateImageRequest<'a> {
/// Which model to use.
pub model: CommonImageModel,
@@ -45,12 +45,12 @@ pub struct GenerateImageRequest {
pub provider: Provider,
/// The prompt for the image generation.
pub prompt: Option<String>,
pub prompt: Option<&'a str>,
/// Input images for image editing.
/// If present, we're doing image editing (image-to-image).
/// If absent, we're doing text-to-image.
pub image_inputs: Option<ImageListRef>,
pub image_inputs: Option<ImageListRef<'a>>,
/// The resolution to use.
pub resolution: Option<CommonResolution>,
@@ -83,12 +83,12 @@ pub struct GenerateImageRequest {
/// Some providers support idempotency.
/// If not supplied, we'll generate one for the required providers.
pub idempotency_token: Option<String>,
pub idempotency_token: Option<&'a str>,
}
impl GenerateImageRequest {
impl<'a> GenerateImageRequest<'a> {
/// Read the image generation request, construct a plan, then yield a means to execute it.
pub fn build(&self) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn build(&self) -> Result<ImageGenerationPlan<'_>, ArtcraftRouterError> {
match self.provider {
Provider::Artcraft => self.build_artcraft(),
Provider::Fal => self.build_fal(),
@@ -96,7 +96,7 @@ impl GenerateImageRequest {
}
}
fn build_artcraft(&self) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
fn build_artcraft(&self) -> Result<ImageGenerationPlan<'_>, ArtcraftRouterError> {
match self.model {
CommonImageModel::Flux1Dev => plan_generate_image_artcraft_flux_1_dev(self),
CommonImageModel::Flux1Schnell => plan_generate_image_artcraft_flux_1_schnell(self),
@@ -115,7 +115,7 @@ impl GenerateImageRequest {
}
}
fn build_fal(&self) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
fn build_fal(&self) -> Result<ImageGenerationPlan<'_>, ArtcraftRouterError> {
match self.model {
CommonImageModel::Flux1Dev => plan_generate_image_fal_flux_1_dev(self),
CommonImageModel::Flux1Schnell => plan_generate_image_fal_flux_1_schnell(self),
@@ -138,14 +138,14 @@ impl GenerateImageRequest {
}
}
fn unsupported_provider(&self) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
fn unsupported_provider(&self) -> Result<ImageGenerationPlan<'_>, ArtcraftRouterError> {
Err(ArtcraftRouterError::UnsupportedModel(
format!("Image generation for model `{:?}` is not supported for provider {:?}", self.model, self.provider)
))
}
pub fn get_or_generate_idempotency_token(&self) -> String {
self.idempotency_token.clone()
self.idempotency_token.map(|t| t.to_string())
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string())
}
}
@@ -82,36 +82,36 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::PlanFalNanaBananaPro;
#[derive(Debug)]
pub enum ImageGenerationPlan {
ArtcraftFlux1Dev(PlanArtcraftFlux1Dev),
ArtcraftFlux1Schnell(PlanArtcraftFlux1Schnell),
ArtcraftFluxPro11(PlanArtcraftFluxPro11),
ArtcraftFluxPro11Ultra(PlanArtcraftFluxPro11Ultra),
ArtcraftGptImage1(PlanArtcraftGptImage1),
ArtcraftGptImage1p5(PlanArtcraftGptImage1p5),
ArtcraftNanaBanana(PlanArtcraftNanaBanana),
ArtcraftNanaBanana2(PlanArtcraftNanaBanana2),
ArtcraftNanaBananaPro(PlanArtcraftNanaBananaPro),
ArtcraftSeedream4(PlanArtcraftSeedream4),
ArtcraftSeedream4p5(PlanArtcraftSeedream4p5),
ArtcraftSeedream5Lite(PlanArtcraftSeedream5Lite),
ArtcraftQwenEdit2511Angles(PlanArtcraftQwenEdit2511Angles),
ArtcraftFlux2LoraAngles(PlanArtcraftFlux2LoraAngles),
FalFlux1Dev(PlanFalFlux1Dev),
FalFlux1Schnell(PlanFalFlux1Schnell),
FalFluxPro11(PlanFalFluxPro11),
FalFluxPro11Ultra(PlanFalFluxPro11Ultra),
FalGptImage1(PlanFalGptImage1),
FalGptImage1p5(PlanFalGptImage1p5),
FalSeedream4(PlanFalSeedream4),
FalSeedream4p5(PlanFalSeedream4p5),
FalSeedream5Lite(PlanFalSeedream5Lite),
FalNanoBanana(PlanFalNanoBanana),
FalNanaBanana2(PlanFalNanaBanana2),
FalNanaBananaPro(PlanFalNanaBananaPro),
pub enum ImageGenerationPlan<'a> {
ArtcraftFlux1Dev(PlanArtcraftFlux1Dev<'a>),
ArtcraftFlux1Schnell(PlanArtcraftFlux1Schnell<'a>),
ArtcraftFluxPro11(PlanArtcraftFluxPro11<'a>),
ArtcraftFluxPro11Ultra(PlanArtcraftFluxPro11Ultra<'a>),
ArtcraftGptImage1(PlanArtcraftGptImage1<'a>),
ArtcraftGptImage1p5(PlanArtcraftGptImage1p5<'a>),
ArtcraftNanaBanana(PlanArtcraftNanaBanana<'a>),
ArtcraftNanaBanana2(PlanArtcraftNanaBanana2<'a>),
ArtcraftNanaBananaPro(PlanArtcraftNanaBananaPro<'a>),
ArtcraftSeedream4(PlanArtcraftSeedream4<'a>),
ArtcraftSeedream4p5(PlanArtcraftSeedream4p5<'a>),
ArtcraftSeedream5Lite(PlanArtcraftSeedream5Lite<'a>),
ArtcraftQwenEdit2511Angles(PlanArtcraftQwenEdit2511Angles<'a>),
ArtcraftFlux2LoraAngles(PlanArtcraftFlux2LoraAngles<'a>),
FalFlux1Dev(PlanFalFlux1Dev<'a>),
FalFlux1Schnell(PlanFalFlux1Schnell<'a>),
FalFluxPro11(PlanFalFluxPro11<'a>),
FalFluxPro11Ultra(PlanFalFluxPro11Ultra<'a>),
FalGptImage1(PlanFalGptImage1<'a>),
FalGptImage1p5(PlanFalGptImage1p5<'a>),
FalSeedream4(PlanFalSeedream4<'a>),
FalSeedream4p5(PlanFalSeedream4p5<'a>),
FalSeedream5Lite(PlanFalSeedream5Lite<'a>),
FalNanoBanana(PlanFalNanoBanana<'a>),
FalNanaBanana2(PlanFalNanaBanana2<'a>),
FalNanaBananaPro(PlanFalNanaBananaPro<'a>),
}
impl ImageGenerationPlan {
impl<'a> ImageGenerationPlan<'a> {
pub async fn generate_image(
&self,
client: &RouterClient,
@@ -10,16 +10,16 @@ use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image:
};
#[derive(Debug, Clone)]
pub struct PlanArtcraftFlux1Dev {
pub prompt: Option<String>,
pub struct PlanArtcraftFlux1Dev<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: Option<GenerateFlux1DevTextToImageAspectRatio>,
pub num_images: GenerateFlux1DevTextToImageNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_flux_1_dev(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_flux_1_dev<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
// Flux 1 Dev is text-to-image only. Abort if image inputs are provided and
// the caller has opted into strict mode.
if request.image_inputs.is_some() {
@@ -37,7 +37,7 @@ pub fn plan_generate_image_artcraft_flux_1_dev(
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftFlux1Dev(PlanArtcraftFlux1Dev {
prompt: request.prompt.clone(),
prompt: request.prompt,
aspect_ratio,
num_images,
idempotency_token: request.get_or_generate_idempotency_token(),
@@ -147,7 +147,7 @@ mod tests {
fn image_inputs_with_abort_returns_error() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
..base_flux_1_dev_image_request()
};
@@ -162,7 +162,7 @@ mod tests {
fn image_inputs_with_generate_anyway_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
..base_flux_1_dev_image_request()
};
@@ -173,7 +173,7 @@ mod tests {
fn image_inputs_with_none_strategy_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: None,
..base_flux_1_dev_image_request()
};
@@ -10,16 +10,16 @@ use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_im
};
#[derive(Debug, Clone)]
pub struct PlanArtcraftFlux1Schnell {
pub prompt: Option<String>,
pub struct PlanArtcraftFlux1Schnell<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: Option<GenerateFlux1SchnellTextToImageAspectRatio>,
pub num_images: GenerateFlux1SchnellTextToImageNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_flux_1_schnell(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_flux_1_schnell<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
// Flux 1 Schnell is text-to-image only. Abort if image inputs are provided and
// the caller has opted into strict mode.
if request.image_inputs.is_some() {
@@ -37,7 +37,7 @@ pub fn plan_generate_image_artcraft_flux_1_schnell(
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftFlux1Schnell(PlanArtcraftFlux1Schnell {
prompt: request.prompt.clone(),
prompt: request.prompt,
aspect_ratio,
num_images,
idempotency_token: request.get_or_generate_idempotency_token(),
@@ -147,7 +147,7 @@ mod tests {
fn image_inputs_with_abort_returns_error() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
..base_flux_1_schnell_image_request()
};
@@ -162,7 +162,7 @@ mod tests {
fn image_inputs_with_generate_anyway_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
..base_flux_1_schnell_image_request()
};
@@ -173,7 +173,7 @@ mod tests {
fn image_inputs_with_none_strategy_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: None,
..base_flux_1_schnell_image_request()
};
@@ -12,8 +12,8 @@ use artcraft_api_defs::generate::image::angle::flux_2_lora_edit_image_angle::{
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftFlux2LoraAngles {
pub image_input: MediaFileToken,
pub struct PlanArtcraftFlux2LoraAngles<'a> {
pub image_input: &'a MediaFileToken,
pub horizontal_angle: Option<f64>,
pub vertical_angle: Option<f64>,
pub zoom: Option<f64>,
@@ -22,13 +22,13 @@ pub struct PlanArtcraftFlux2LoraAngles {
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_flux_2_lora_angles(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_flux_2_lora_angles<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
// Angle models require exactly one input image.
let image_input = resolve_single_image_input(request.image_inputs.clone())?;
let image_input = resolve_single_image_input(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
@@ -43,9 +43,9 @@ pub fn plan_generate_image_artcraft_flux_2_lora_angles(
}))
}
fn resolve_single_image_input(
image_list_ref: Option<ImageListRef>,
) -> Result<MediaFileToken, ArtcraftRouterError> {
fn resolve_single_image_input<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<&'a MediaFileToken, ArtcraftRouterError> {
match image_list_ref {
None => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
@@ -54,13 +54,13 @@ fn resolve_single_image_input(
}))
}
Some(ImageListRef::MediaFileTokens(tokens)) => {
if let Some(token) = tokens.first() {
return Ok(token.clone());
if tokens.len() != 1 {
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: format!("Angle models require exactly one input image, got {}", tokens.len()),
}));
}
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: format!("Angle models require exactly one input image, got {}", tokens.len()),
}))
Ok(&tokens[0])
}
Some(ImageListRef::Urls(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
@@ -165,7 +165,7 @@ mod tests {
fn rejects_urls() {
let urls = vec!["https://example.com/img.png".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_flux_2_lora_angles_image_request()
};
let result = request.build();
@@ -176,7 +176,7 @@ mod tests {
fn accepts_single_media_token() {
let tokens = vec![make_token()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_flux_2_lora_angles_image_request()
};
let result = request.build();
@@ -187,7 +187,7 @@ mod tests {
fn rejects_multiple_media_tokens() {
let tokens = vec![make_token(), make_token()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_flux_2_lora_angles_image_request()
};
let result = request.build();
@@ -199,7 +199,7 @@ mod tests {
let tokens = vec![make_token()];
let request = GenerateImageRequest {
aspect_ratio: None,
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_flux_2_lora_angles_image_request()
};
let ImageGenerationPlan::ArtcraftFlux2LoraAngles(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux2LoraAngles") };
@@ -211,7 +211,7 @@ mod tests {
let tokens = vec![make_token()];
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::Square),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_flux_2_lora_angles_image_request()
};
let ImageGenerationPlan::ArtcraftFlux2LoraAngles(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux2LoraAngles") };
@@ -226,7 +226,7 @@ mod tests {
let request = GenerateImageRequest {
quality: None,
image_batch_count: Some(count),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_flux_2_lora_angles_image_request()
};
let ImageGenerationPlan::ArtcraftFlux2LoraAngles(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux2LoraAngles") };
@@ -244,7 +244,7 @@ mod tests {
horizontal_angle: Some(45.0),
vertical_angle: Some(-30.0),
zoom: Some(1.5),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_flux_2_lora_angles_image_request()
};
let ImageGenerationPlan::ArtcraftFlux2LoraAngles(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux2LoraAngles") };
@@ -10,16 +10,16 @@ use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image
};
#[derive(Debug, Clone)]
pub struct PlanArtcraftFluxPro11 {
pub prompt: Option<String>,
pub struct PlanArtcraftFluxPro11<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: Option<GenerateFluxPro11TextToImageAspectRatio>,
pub num_images: GenerateFluxPro11TextToImageNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_flux_pro_1p1(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_flux_pro_1p1<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
// Flux Pro 1.1 is text-to-image only. Abort if image inputs are provided and
// the caller has opted into strict mode.
if request.image_inputs.is_some() {
@@ -37,7 +37,7 @@ pub fn plan_generate_image_artcraft_flux_pro_1p1(
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftFluxPro11(PlanArtcraftFluxPro11 {
prompt: request.prompt.clone(),
prompt: request.prompt,
aspect_ratio,
num_images,
idempotency_token: request.get_or_generate_idempotency_token(),
@@ -147,7 +147,7 @@ mod tests {
fn image_inputs_with_abort_returns_error() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
..base_flux_pro_1p1_image_request()
};
@@ -162,7 +162,7 @@ mod tests {
fn image_inputs_with_generate_anyway_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
..base_flux_pro_1p1_image_request()
};
@@ -173,7 +173,7 @@ mod tests {
fn image_inputs_with_none_strategy_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: None,
..base_flux_pro_1p1_image_request()
};
@@ -10,16 +10,16 @@ use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to
};
#[derive(Debug, Clone)]
pub struct PlanArtcraftFluxPro11Ultra {
pub prompt: Option<String>,
pub struct PlanArtcraftFluxPro11Ultra<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: Option<GenerateFluxPro11UltraTextToImageAspectRatio>,
pub num_images: GenerateFluxPro11UltraTextToImageNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_flux_pro_1p1_ultra(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_flux_pro_1p1_ultra<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
// Flux Pro 1.1 Ultra is text-to-image only. Abort if image inputs are provided and
// the caller has opted into strict mode.
if request.image_inputs.is_some() {
@@ -37,7 +37,7 @@ pub fn plan_generate_image_artcraft_flux_pro_1p1_ultra(
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftFluxPro11Ultra(PlanArtcraftFluxPro11Ultra {
prompt: request.prompt.clone(),
prompt: request.prompt,
aspect_ratio,
num_images,
idempotency_token: request.get_or_generate_idempotency_token(),
@@ -150,7 +150,7 @@ mod tests {
fn image_inputs_with_abort_returns_error() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
..base_flux_pro_1p1_ultra_image_request()
};
@@ -165,7 +165,7 @@ mod tests {
fn image_inputs_with_generate_anyway_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
..base_flux_pro_1p1_ultra_image_request()
};
@@ -176,7 +176,7 @@ mod tests {
fn image_inputs_with_none_strategy_succeeds() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
generation_mode_mismatch_strategy: None,
..base_flux_pro_1p1_ultra_image_request()
};
@@ -43,10 +43,10 @@ pub enum ArtcraftGptImage1NumImages {
}
#[derive(Debug, Clone)]
pub struct PlanArtcraftGptImage1 {
pub prompt: Option<String>,
pub struct PlanArtcraftGptImage1<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
/// Number of input images (0 for text-to-image). This is counted from either
/// media tokens or hydrated URLs, so the cost estimator can use it without
/// needing to inspect image_inputs (which is None for URL-form inputs).
@@ -59,19 +59,19 @@ pub struct PlanArtcraftGptImage1 {
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_gpt_image_1(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_gpt_image_1<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let num_input_images = count_input_images(request.image_inputs.clone());
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let num_input_images = count_input_images(request.image_inputs);
let image_size = plan_image_size(request.aspect_ratio);
let quality = plan_quality(request.quality);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftGptImage1(PlanArtcraftGptImage1 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
num_input_images,
image_size,
@@ -81,9 +81,9 @@ pub fn plan_generate_image_artcraft_gpt_image_1(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -95,7 +95,7 @@ fn resolve_image_list_ref(
}
}
fn count_input_images(image_inputs: Option<ImageListRef>) -> u64 {
fn count_input_images(image_inputs: Option<ImageListRef<'_>>) -> u64 {
match image_inputs {
None => 0,
Some(ImageListRef::MediaFileTokens(tokens)) => tokens.len() as u64,
@@ -435,7 +435,7 @@ mod tests {
fn media_token_image_inputs_is_edit_mode() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_gpt_image_1_image_request()
};
let ImageGenerationPlan::ArtcraftGptImage1(plan) = request.build().unwrap() else {
@@ -451,7 +451,7 @@ mod tests {
// cost only depends on quality + size + num_images.
let urls = vec!["https://example.com/image.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_gpt_image_1_image_request()
};
let ImageGenerationPlan::ArtcraftGptImage1(plan) = request.build().unwrap() else {
@@ -474,7 +474,7 @@ mod tests {
#[test]
fn idempotency_token_passthrough() {
let token = "11111111-1111-1111-1111-111111111111";
let request = GenerateImageRequest { idempotency_token: Some(token.to_string()), ..base_gpt_image_1_image_request() };
let request = GenerateImageRequest { idempotency_token: Some(token), ..base_gpt_image_1_image_request() };
let ImageGenerationPlan::ArtcraftGptImage1(plan) = request.build().unwrap() else {
panic!("expected ArtcraftGptImage1")
};
@@ -13,10 +13,10 @@ use artcraft_api_defs::generate::image::multi_function::gpt_image_1p5_multi_func
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftGptImage1p5 {
pub prompt: Option<String>,
pub struct PlanArtcraftGptImage1p5<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub image_size: Option<GptImage1p5MultiFunctionImageGenSize>,
/// Quality defaults to Medium when not specified in the request.
pub quality: GptImage1p5MultiFunctionImageGenQuality,
@@ -24,18 +24,18 @@ pub struct PlanArtcraftGptImage1p5 {
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_gpt_image_1p5(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_gpt_image_1p5<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, strategy)?;
let quality = plan_quality(request.quality);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftGptImage1p5(PlanArtcraftGptImage1p5 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
image_size,
quality,
@@ -44,9 +44,9 @@ pub fn plan_generate_image_artcraft_gpt_image_1p5(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -293,7 +293,7 @@ mod tests {
fn url_image_inputs_are_accepted_for_cost_path() {
let urls = vec!["https://example.com/image.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_gpt_image_1p5_image_request()
};
let ImageGenerationPlan::ArtcraftGptImage1p5(plan) = request.build().unwrap() else {
@@ -313,7 +313,7 @@ mod tests {
fn media_token_image_inputs_is_edit_mode() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_gpt_image_1p5_image_request()
};
let ImageGenerationPlan::ArtcraftGptImage1p5(plan) = request.build().unwrap() else { panic!("expected ArtcraftGptImage1p5") };
@@ -11,27 +11,27 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_multi_functi
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftNanaBanana {
pub prompt: Option<String>,
pub struct PlanArtcraftNanaBanana<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub aspect_ratio: Option<NanoBananaMultiFunctionImageGenAspectRatio>,
pub num_images: NanoBananaMultiFunctionImageGenNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_nano_banana(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_nano_banana<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftNanaBanana(PlanArtcraftNanaBanana {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
aspect_ratio,
num_images,
@@ -39,9 +39,9 @@ pub fn plan_generate_image_artcraft_nano_banana(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -222,7 +222,7 @@ mod tests {
let tokens = vec![];
let request = GenerateImageRequest {
aspect_ratio: Some(auto_ar),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_nano_banana_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBanana(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana") };
@@ -369,7 +369,7 @@ mod tests {
// cost only depends on num_images + is_edit_mode.
let urls = vec!["https://example.com/image.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_nano_banana_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBanana(plan) = request.build().unwrap() else {
@@ -391,7 +391,7 @@ mod tests {
fn media_token_image_inputs_is_edit_mode() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_nano_banana_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBanana(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana") };
@@ -13,29 +13,29 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_func
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftNanaBanana2 {
pub prompt: Option<String>,
pub struct PlanArtcraftNanaBanana2<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub aspect_ratio: Option<NanaBanana2MultiFunctionImageGenAspectRatio>,
pub resolution: Option<NanaBanana2MultiFunctionImageGenImageResolution>,
pub num_images: NanaBanana2MultiFunctionImageGenNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_nano_banana_2(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_nano_banana_2<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let resolution = plan_resolution(request.resolution, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftNanaBanana2(PlanArtcraftNanaBanana2 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
aspect_ratio,
resolution,
@@ -44,9 +44,9 @@ pub fn plan_generate_image_artcraft_nano_banana_2(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -211,7 +211,7 @@ mod tests {
let tokens = vec![];
let request = GenerateImageRequest {
aspect_ratio: Some(auto_ar),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_nano_banana_2_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
@@ -13,29 +13,29 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_pro_multi_fu
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftNanaBananaPro {
pub prompt: Option<String>,
pub struct PlanArtcraftNanaBananaPro<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub aspect_ratio: Option<NanoBananaProMultiFunctionImageGenAspectRatio>,
pub resolution: Option<NanoBananaProMultiFunctionImageGenImageResolution>,
pub num_images: NanoBananaProMultiFunctionImageGenNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_nano_banana_pro(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_nano_banana_pro<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let resolution = plan_resolution(request.resolution, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftNanaBananaPro(PlanArtcraftNanaBananaPro {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
aspect_ratio,
resolution,
@@ -44,9 +44,9 @@ pub fn plan_generate_image_artcraft_nano_banana_pro(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -249,7 +249,7 @@ mod tests {
let tokens = vec![];
let request = GenerateImageRequest {
aspect_ratio: Some(auto_ar),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBananaPro(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBananaPro") };
@@ -432,7 +432,7 @@ mod tests {
// cost only depends on num_images + resolution + is_edit_mode.
let urls = vec!["https://example.com/image.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBananaPro(plan) = request.build().unwrap() else {
@@ -452,7 +452,7 @@ mod tests {
fn media_token_image_inputs_is_edit_mode() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_image_request()
};
let ImageGenerationPlan::ArtcraftNanaBananaPro(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBananaPro") };
@@ -12,9 +12,9 @@ use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftQwenEdit2511Angles {
pub prompt: Option<String>,
pub image_input: MediaFileToken,
pub struct PlanArtcraftQwenEdit2511Angles<'a> {
pub prompt: Option<&'a str>,
pub image_input: &'a MediaFileToken,
pub horizontal_angle: Option<f64>,
pub vertical_angle: Option<f64>,
pub zoom: Option<f64>,
@@ -23,18 +23,18 @@ pub struct PlanArtcraftQwenEdit2511Angles {
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_qwen_edit_2511_angles(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_qwen_edit_2511_angles<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
// Angle models require exactly one input image.
let image_input = resolve_single_image_input(request.image_inputs.clone())?;
let image_input = resolve_single_image_input(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftQwenEdit2511Angles(PlanArtcraftQwenEdit2511Angles {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_input,
horizontal_angle: request.horizontal_angle,
vertical_angle: request.vertical_angle,
@@ -45,9 +45,9 @@ pub fn plan_generate_image_artcraft_qwen_edit_2511_angles(
}))
}
fn resolve_single_image_input(
image_list_ref: Option<ImageListRef>,
) -> Result<MediaFileToken, ArtcraftRouterError> {
fn resolve_single_image_input<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<&'a MediaFileToken, ArtcraftRouterError> {
match image_list_ref {
None => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
@@ -56,13 +56,13 @@ fn resolve_single_image_input(
}))
}
Some(ImageListRef::MediaFileTokens(tokens)) => {
if let Some(token) = tokens.first() {
return Ok(token.clone());
if tokens.len() != 1 {
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: format!("Angle models require exactly one input image, got {}", tokens.len()),
}));
}
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: format!("Angle models require exactly one input image, got {}", tokens.len()),
}))
Ok(&tokens[0])
}
Some(ImageListRef::Urls(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
@@ -167,7 +167,7 @@ mod tests {
fn rejects_urls() {
let urls = vec!["https://example.com/img.png".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_qwen_edit_2511_angles_image_request()
};
let result = request.build();
@@ -178,7 +178,7 @@ mod tests {
fn accepts_single_media_token() {
let tokens = vec![make_token()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_qwen_edit_2511_angles_image_request()
};
let result = request.build();
@@ -189,7 +189,7 @@ mod tests {
fn rejects_multiple_media_tokens() {
let tokens = vec![make_token(), make_token()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_qwen_edit_2511_angles_image_request()
};
let result = request.build();
@@ -201,7 +201,7 @@ mod tests {
let tokens = vec![make_token()];
let request = GenerateImageRequest {
aspect_ratio: None,
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_qwen_edit_2511_angles_image_request()
};
let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") };
@@ -213,7 +213,7 @@ mod tests {
let tokens = vec![make_token()];
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::Square),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_qwen_edit_2511_angles_image_request()
};
let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") };
@@ -228,7 +228,7 @@ mod tests {
let request = GenerateImageRequest {
quality: None,
image_batch_count: Some(count),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_qwen_edit_2511_angles_image_request()
};
let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") };
@@ -246,7 +246,7 @@ mod tests {
horizontal_angle: Some(45.0),
vertical_angle: Some(-30.0),
zoom: Some(1.5),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_qwen_edit_2511_angles_image_request()
};
let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") };
@@ -12,27 +12,27 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4_mu
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftSeedream4 {
pub prompt: Option<String>,
pub struct PlanArtcraftSeedream4<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub image_size: Option<BytedanceSeedreamV4MultiFunctionImageGenImageSize>,
pub num_images: BytedanceSeedreamV4MultiFunctionImageGenNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_seedream_4(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_seedream_4<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftSeedream4(PlanArtcraftSeedream4 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
image_size,
num_images,
@@ -40,9 +40,9 @@ pub fn plan_generate_image_artcraft_seedream_4(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -220,7 +220,7 @@ mod tests {
let tokens = vec![];
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::Auto),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_seedream_4_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream4(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream4") };
@@ -355,7 +355,7 @@ mod tests {
fn url_image_inputs_are_accepted_for_cost_path() {
let urls = vec!["https://example.com/image.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_seedream_4_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream4(plan) = request.build().unwrap() else {
@@ -375,7 +375,7 @@ mod tests {
fn media_token_image_inputs_is_edit_mode() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_seedream_4_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream4(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream4") };
@@ -12,27 +12,27 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4p5_
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftSeedream4p5 {
pub prompt: Option<String>,
pub struct PlanArtcraftSeedream4p5<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub image_size: Option<BytedanceSeedreamV4p5MultiFunctionImageGenImageSize>,
pub num_images: BytedanceSeedreamV4p5MultiFunctionImageGenNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_seedream_4p5(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_seedream_4p5<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftSeedream4p5(PlanArtcraftSeedream4p5 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
image_size,
num_images,
@@ -40,9 +40,9 @@ pub fn plan_generate_image_artcraft_seedream_4p5(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -221,7 +221,7 @@ mod tests {
let tokens = vec![];
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::Auto),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_seedream_4p5_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream4p5(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream4p5") };
@@ -356,7 +356,7 @@ mod tests {
fn url_image_inputs_are_accepted_for_cost_path() {
let urls = vec!["https://example.com/image.jpg".to_string()];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_seedream_4p5_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream4p5(plan) = request.build().unwrap() else {
@@ -376,7 +376,7 @@ mod tests {
fn media_token_image_inputs_is_edit_mode() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_seedream_4p5_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream4p5(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream4p5") };
@@ -12,27 +12,27 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lit
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftSeedream5Lite {
pub prompt: Option<String>,
pub struct PlanArtcraftSeedream5Lite<'a> {
pub prompt: Option<&'a str>,
/// Input images for image editing. None means text-to-image mode.
pub image_inputs: Option<Vec<MediaFileToken>>,
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
pub image_size: Option<BytedanceSeedream5LiteMultiFunctionImageGenImageSize>,
pub num_images: BytedanceSeedream5LiteMultiFunctionImageGenNumImages,
pub idempotency_token: String,
}
pub fn plan_generate_image_artcraft_seedream_5_lite(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_artcraft_seedream_5_lite<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_inputs = resolve_image_list_ref(request.image_inputs.clone())?;
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::ArtcraftSeedream5Lite(PlanArtcraftSeedream5Lite {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_inputs,
image_size,
num_images,
@@ -40,9 +40,9 @@ pub fn plan_generate_image_artcraft_seedream_5_lite(
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<Vec<MediaFileToken>>, ArtcraftRouterError> {
fn resolve_image_list_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
@@ -167,7 +167,7 @@ mod tests {
let tokens = vec![];
let request = GenerateImageRequest {
aspect_ratio: Some(CommonAspectRatio::Auto),
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_seedream_5_lite_image_request()
};
let ImageGenerationPlan::ArtcraftSeedream5Lite(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream5Lite") };
@@ -20,24 +20,24 @@ pub enum FalFlux1DevNumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalFlux1Dev {
pub prompt: Option<String>,
pub struct PlanFalFlux1Dev<'a> {
pub prompt: Option<&'a str>,
/// Image URL for editing. None = text-to-image mode.
pub maybe_image_url: Option<String>,
pub aspect_ratio: Flux1DevAspectRatio,
pub num_images: FalFlux1DevNumImages,
}
pub fn plan_generate_image_fal_flux_1_dev(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_flux_1_dev<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let maybe_image_url = resolve_single_image_url(request.image_inputs.clone())?;
let maybe_image_url = resolve_single_image_url(request.image_inputs)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFlux1Dev(PlanFalFlux1Dev {
prompt: request.prompt.clone(),
prompt: request.prompt,
maybe_image_url,
aspect_ratio,
num_images,
@@ -45,7 +45,7 @@ pub fn plan_generate_image_fal_flux_1_dev(
}
fn resolve_single_image_url(
image_inputs: Option<ImageListRef>,
image_inputs: Option<ImageListRef<'_>>,
) -> Result<Option<String>, ArtcraftRouterError> {
match image_inputs {
None => Ok(None),
@@ -22,8 +22,8 @@ pub enum FalFlux1SchnellNumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalFlux1Schnell {
pub prompt: Option<String>,
pub struct PlanFalFlux1Schnell<'a> {
pub prompt: Option<&'a str>,
/// Image URL for editing. None = text-to-image mode.
pub maybe_image_url: Option<String>,
pub t2i_aspect_ratio: Flux1SchnellAspectRatio,
@@ -31,17 +31,17 @@ pub struct PlanFalFlux1Schnell {
pub num_images: FalFlux1SchnellNumImages,
}
pub fn plan_generate_image_fal_flux_1_schnell(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_flux_1_schnell<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let maybe_image_url = resolve_single_image_url(request.image_inputs.clone())?;
let maybe_image_url = resolve_single_image_url(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_image_size = plan_edit_image_size(request.aspect_ratio);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFlux1Schnell(PlanFalFlux1Schnell {
prompt: request.prompt.clone(),
prompt: request.prompt,
maybe_image_url,
t2i_aspect_ratio,
edit_image_size,
@@ -50,7 +50,7 @@ pub fn plan_generate_image_fal_flux_1_schnell(
}
fn resolve_single_image_url(
image_inputs: Option<ImageListRef>,
image_inputs: Option<ImageListRef<'_>>,
) -> Result<Option<String>, ArtcraftRouterError> {
match image_inputs {
None => Ok(None),
@@ -17,15 +17,15 @@ pub enum FalFluxPro11NumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalFluxPro11 {
pub prompt: Option<String>,
pub struct PlanFalFluxPro11<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: FluxPro11AspectRatio,
pub num_images: FalFluxPro11NumImages,
}
pub fn plan_generate_image_fal_flux_pro_1p1(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_flux_pro_1p1<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
if request.image_inputs.is_some() {
@@ -39,7 +39,7 @@ pub fn plan_generate_image_fal_flux_pro_1p1(
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFluxPro11(PlanFalFluxPro11 {
prompt: request.prompt.clone(),
prompt: request.prompt,
aspect_ratio,
num_images,
}))
@@ -17,15 +17,15 @@ pub enum FalFluxPro11UltraNumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalFluxPro11Ultra {
pub prompt: Option<String>,
pub struct PlanFalFluxPro11Ultra<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: FluxPro11UltraAspectRatio,
pub num_images: FalFluxPro11UltraNumImages,
}
pub fn plan_generate_image_fal_flux_pro_1p1_ultra(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_flux_pro_1p1_ultra<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
if request.image_inputs.is_some() {
@@ -39,7 +39,7 @@ pub fn plan_generate_image_fal_flux_pro_1p1_ultra(
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFluxPro11Ultra(PlanFalFluxPro11Ultra {
prompt: request.prompt.clone(),
prompt: request.prompt,
aspect_ratio,
num_images,
}))
@@ -40,8 +40,8 @@ pub enum FalGptImage1ImageSize {
}
#[derive(Debug, Clone)]
pub struct PlanFalGptImage1 {
pub prompt: Option<String>,
pub struct PlanFalGptImage1<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
pub image_size: Option<FalGptImage1ImageSize>,
@@ -51,17 +51,17 @@ pub struct PlanFalGptImage1 {
pub num_images: FalGptImage1NumImages,
}
pub fn plan_generate_image_fal_gpt_image_1(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_gpt_image_1<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio);
let quality = plan_quality(request.quality);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalGptImage1(PlanFalGptImage1 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
image_size,
quality,
@@ -70,7 +70,7 @@ pub fn plan_generate_image_fal_gpt_image_1(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -219,11 +219,11 @@ mod tests {
use crate::api::common_image_model::CommonImageModel;
use crate::api::provider::Provider;
fn base_fal_request() -> GenerateImageRequest {
fn base_fal_request() -> GenerateImageRequest<'static> {
GenerateImageRequest {
model: CommonImageModel::GptImage1,
provider: Provider::Fal,
prompt: Some("a cat in space".to_string()),
prompt: Some("a cat in space"),
image_inputs: None,
resolution: None,
aspect_ratio: None,
@@ -238,7 +238,7 @@ mod tests {
}
}
fn build_plan(request: &GenerateImageRequest) -> PlanFalGptImage1 {
fn build_plan<'a>(request: &'a GenerateImageRequest<'a>) -> PlanFalGptImage1<'a> {
let ImageGenerationPlan::FalGptImage1(plan) =
plan_generate_image_fal_gpt_image_1(request).expect("plan should succeed")
else {
@@ -263,7 +263,7 @@ mod tests {
"https://example.com/b.jpg".to_string(),
];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_fal_request()
};
let plan = build_plan(&request);
@@ -274,7 +274,7 @@ mod tests {
fn media_token_inputs_return_error() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_fal_request()
};
let result = plan_generate_image_fal_gpt_image_1(&request);
@@ -40,25 +40,25 @@ pub enum FalGptImage1p5ImageSize {
}
#[derive(Debug, Clone)]
pub struct PlanFalGptImage1p5 {
pub prompt: Option<String>,
pub struct PlanFalGptImage1p5<'a> {
pub prompt: Option<&'a str>,
pub image_urls: Vec<String>,
pub image_size: Option<FalGptImage1p5ImageSize>,
pub quality: FalGptImage1p5Quality,
pub num_images: FalGptImage1p5NumImages,
}
pub fn plan_generate_image_fal_gpt_image_1p5(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_gpt_image_1p5<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio);
let quality = plan_quality(request.quality);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalGptImage1p5(PlanFalGptImage1p5 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
image_size,
quality,
@@ -67,7 +67,7 @@ pub fn plan_generate_image_fal_gpt_image_1p5(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -18,8 +18,8 @@ pub enum FalNbNumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalNanoBanana {
pub prompt: Option<String>,
pub struct PlanFalNanoBanana<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
/// Pre-resolved aspect ratio for text-to-image mode.
@@ -30,18 +30,18 @@ pub struct PlanFalNanoBanana {
pub num_images: FalNbNumImages,
}
pub fn plan_generate_image_fal_nano_banana(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_nano_banana<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_aspect_ratio = plan_edit_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalNanoBanana(PlanFalNanoBanana {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
t2i_aspect_ratio,
edit_aspect_ratio,
@@ -50,7 +50,7 @@ pub fn plan_generate_image_fal_nano_banana(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -28,8 +28,8 @@ pub enum FalNb2NumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalNanaBanana2 {
pub prompt: Option<String>,
pub struct PlanFalNanaBanana2<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
/// Pre-resolved aspect ratio for text-to-image mode.
@@ -42,19 +42,19 @@ pub struct PlanFalNanaBanana2 {
pub num_images: FalNb2NumImages,
}
pub fn plan_generate_image_fal_nano_banana_2(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_nano_banana_2<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_aspect_ratio = plan_edit_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let resolution = plan_resolution(request.resolution)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalNanaBanana2(PlanFalNanaBanana2 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
t2i_aspect_ratio,
edit_aspect_ratio,
@@ -64,7 +64,7 @@ pub fn plan_generate_image_fal_nano_banana_2(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -29,8 +29,8 @@ pub enum FalNbpNumImages {
}
#[derive(Debug, Clone)]
pub struct PlanFalNanaBananaPro {
pub prompt: Option<String>,
pub struct PlanFalNanaBananaPro<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
/// Pre-resolved aspect ratio for text-to-image mode.
@@ -43,19 +43,19 @@ pub struct PlanFalNanaBananaPro {
pub num_images: FalNbpNumImages,
}
pub fn plan_generate_image_fal_nano_banana_pro(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_nano_banana_pro<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_aspect_ratio = plan_edit_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let resolution = plan_resolution(request.resolution)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalNanaBananaPro(PlanFalNanaBananaPro {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
t2i_aspect_ratio,
edit_aspect_ratio,
@@ -65,7 +65,7 @@ pub fn plan_generate_image_fal_nano_banana_pro(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -248,11 +248,11 @@ mod tests {
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::EnqueueNanoBananaProEditImageAspectRatio as EditAr;
use fal_client::requests::webhook::image::text::enqueue_nano_banana_pro_text_to_image_webhook::EnqueueNanoBananaProTextToImageAspectRatio as T2iAr;
fn base_fal_request() -> GenerateImageRequest {
fn base_fal_request() -> GenerateImageRequest<'static> {
GenerateImageRequest {
model: CommonImageModel::NanoBananaPro,
provider: Provider::Fal,
prompt: Some("a cat in space".to_string()),
prompt: Some("a cat in space"),
image_inputs: None,
resolution: None,
aspect_ratio: None,
@@ -267,9 +267,9 @@ mod tests {
}
}
fn build_plan(
request: &GenerateImageRequest,
) -> PlanFalNanaBananaPro {
fn build_plan<'a>(
request: &'a GenerateImageRequest<'a>,
) -> PlanFalNanaBananaPro<'a> {
let ImageGenerationPlan::FalNanaBananaPro(plan) = plan_generate_image_fal_nano_banana_pro(request).expect("plan should succeed") else {
panic!("expected FalNanaBananaPro variant")
};
@@ -292,7 +292,7 @@ mod tests {
"https://example.com/b.jpg".to_string(),
];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_fal_request()
};
let plan = build_plan(&request);
@@ -303,7 +303,7 @@ mod tests {
fn media_token_inputs_return_error() {
let tokens = vec![];
let request = GenerateImageRequest {
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
..base_fal_request()
};
let result = plan_generate_image_fal_nano_banana_pro(&request);
@@ -406,7 +406,7 @@ mod tests {
for auto_ar in [CommonAspectRatio::Auto, CommonAspectRatio::Auto2k, CommonAspectRatio::Auto4k] {
let request = GenerateImageRequest {
aspect_ratio: Some(auto_ar),
image_inputs: Some(ImageListRef::Urls(urls.clone())),
image_inputs: Some(ImageListRef::Urls(&urls)),
..base_fal_request()
};
let plan = build_plan(&request);
@@ -35,25 +35,25 @@ pub enum FalSeedream4ImageSize {
}
#[derive(Debug, Clone)]
pub struct PlanFalSeedream4 {
pub prompt: Option<String>,
pub struct PlanFalSeedream4<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
pub image_size: Option<FalSeedream4ImageSize>,
pub num_images: FalSeedream4NumImages,
}
pub fn plan_generate_image_fal_seedream_4(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_seedream_4<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalSeedream4(PlanFalSeedream4 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
image_size,
num_images,
@@ -61,7 +61,7 @@ pub fn plan_generate_image_fal_seedream_4(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -34,23 +34,23 @@ pub enum FalSeedream4p5ImageSize {
}
#[derive(Debug, Clone)]
pub struct PlanFalSeedream4p5 {
pub prompt: Option<String>,
pub struct PlanFalSeedream4p5<'a> {
pub prompt: Option<&'a str>,
pub image_urls: Vec<String>,
pub image_size: Option<FalSeedream4p5ImageSize>,
pub num_images: FalSeedream4p5NumImages,
}
pub fn plan_generate_image_fal_seedream_4p5(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_seedream_4p5<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalSeedream4p5(PlanFalSeedream4p5 {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
image_size,
num_images,
@@ -58,7 +58,7 @@ pub fn plan_generate_image_fal_seedream_4p5(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -35,23 +35,23 @@ pub enum FalSeedream5LiteImageSize {
}
#[derive(Debug, Clone)]
pub struct PlanFalSeedream5Lite {
pub prompt: Option<String>,
pub struct PlanFalSeedream5Lite<'a> {
pub prompt: Option<&'a str>,
pub image_urls: Vec<String>,
pub image_size: Option<FalSeedream5LiteImageSize>,
pub num_images: FalSeedream5LiteNumImages,
}
pub fn plan_generate_image_fal_seedream_5_lite(
request: &GenerateImageRequest,
) -> Result<ImageGenerationPlan, ArtcraftRouterError> {
pub fn plan_generate_image_fal_seedream_5_lite<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs.clone())?;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, request.resolution, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalSeedream5Lite(PlanFalSeedream5Lite {
prompt: request.prompt.clone(),
prompt: request.prompt,
image_urls,
image_size,
num_images,
@@ -59,7 +59,7 @@ pub fn plan_generate_image_fal_seedream_5_lite(
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef>,
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
@@ -4,7 +4,7 @@ use worldlabs_api_client::api::api_types::world_labs_model::WorldLabsModel;
use worldlabs_api_client::pricing::check_pricing::{calculate_cost, InputType};
pub(crate) fn estimate_splat_cost_artcraft_marble_0p1_mini(
plan: &PlanArtcraftMarble0p1Mini,
plan: &PlanArtcraftMarble0p1Mini<'_>,
) -> SplatGenerationCostEstimate {
let input_type = if plan.reference_image.is_some() {
InputType::ImageNonPanorama
@@ -33,11 +33,11 @@ mod tests {
use tokens::tokens::media_files::MediaFileToken;
fn estimate_usd_cents(prompt: Option<&str>, image_tokens: Option<Vec<MediaFileToken>>) -> u64 {
fn estimate_usd_cents(prompt: Option<&str>, image_tokens: Option<&Vec<MediaFileToken>>) -> u64 {
let request = GenerateSplatRequest {
model: CommonSplatModel::Marble0p1Mini,
provider: Provider::Artcraft,
prompt: prompt.map(|s| s.to_string()),
prompt,
reference_images: image_tokens.map(crate::api::image_list_ref::ImageListRef::MediaFileTokens),
idempotency_token: None,
};
@@ -63,14 +63,14 @@ mod tests {
#[test]
fn test_estimate_cost_image() {
let tokens = vec![MediaFileToken("test_token".to_string())];
let cost = estimate_usd_cents(None, Some(tokens.clone()));
let cost = estimate_usd_cents(None, Some(&tokens));
assert_eq!(cost, 18); // 230 credits → 18 cents (same as text for mini)
}
#[test]
fn test_estimate_cost_image_with_prompt() {
let tokens = vec![MediaFileToken("test_token".to_string())];
let cost = estimate_usd_cents(Some("a cozy room"), Some(tokens.clone()));
let cost = estimate_usd_cents(Some("a cozy room"), Some(&tokens));
assert_eq!(cost, 18); // 230 credits → 18 cents
}
}
@@ -4,7 +4,7 @@ use worldlabs_api_client::api::api_types::world_labs_model::WorldLabsModel;
use worldlabs_api_client::pricing::check_pricing::{calculate_cost, InputType};
pub(crate) fn estimate_splat_cost_artcraft_marble_0p1_plus(
plan: &PlanArtcraftMarble0p1Plus,
plan: &PlanArtcraftMarble0p1Plus<'_>,
) -> SplatGenerationCostEstimate {
let input_type = if plan.reference_image.is_some() {
InputType::ImageNonPanorama
@@ -33,11 +33,11 @@ mod tests {
use tokens::tokens::media_files::MediaFileToken;
fn estimate_usd_cents(prompt: Option<&str>, image_tokens: Option<Vec<MediaFileToken>>) -> u64 {
fn estimate_usd_cents(prompt: Option<&str>, image_tokens: Option<&Vec<MediaFileToken>>) -> u64 {
let request = GenerateSplatRequest {
model: CommonSplatModel::Marble0p1Plus,
provider: Provider::Artcraft,
prompt: prompt.map(|s| s.to_string()),
prompt,
reference_images: image_tokens.map(crate::api::image_list_ref::ImageListRef::MediaFileTokens),
idempotency_token: None,
};
@@ -63,14 +63,14 @@ mod tests {
#[test]
fn test_estimate_cost_image() {
let tokens = vec![MediaFileToken("test_token".to_string())];
let cost = estimate_usd_cents(None, Some(tokens.clone()));
let cost = estimate_usd_cents(None, Some(&tokens));
assert_eq!(cost, 126); // 1580 credits → 126 cents (same as text for plus)
}
#[test]
fn test_estimate_cost_image_with_prompt() {
let tokens = vec![MediaFileToken("test_token".to_string())];
let cost = estimate_usd_cents(Some("a cozy room"), Some(tokens.clone()));
let cost = estimate_usd_cents(Some("a cozy room"), Some(&tokens));
assert_eq!(cost, 126); // 1580 credits → 126 cents
}
}
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_mini_splat
use artcraft_client::endpoints::generate::splat::generate_worldlabs_marble_0p1_mini_splat::generate_worldlabs_marble_0p1_mini_splat;
pub async fn execute_artcraft_marble_0p1_mini(
plan: &PlanArtcraftMarble0p1Mini,
plan: &PlanArtcraftMarble0p1Mini<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateSplatResponse, ArtcraftRouterError> {
let request = GenerateWorldlabsMarble0p1MiniSplatRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
image_media_file_token: plan.reference_image.clone(),
prompt: plan.prompt.clone(),
image_media_file_token: plan.reference_image.cloned(),
prompt: plan.prompt.map(|p| p.to_string()),
};
let response = generate_worldlabs_marble_0p1_mini_splat(
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_plus_splat
use artcraft_client::endpoints::generate::splat::generate_worldlabs_marble_0p1_plus_splat::generate_worldlabs_marble_0p1_plus_splat;
pub async fn execute_artcraft_marble_0p1_plus(
plan: &PlanArtcraftMarble0p1Plus,
plan: &PlanArtcraftMarble0p1Plus<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateSplatResponse, ArtcraftRouterError> {
let request = GenerateWorldlabsMarble0p1PlusSplatRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
image_media_file_token: plan.reference_image.clone(),
prompt: plan.prompt.clone(),
image_media_file_token: plan.reference_image.cloned(),
prompt: plan.prompt.map(|p| p.to_string()),
};
let response = generate_worldlabs_marble_0p1_plus_splat(
@@ -6,7 +6,7 @@ use crate::generate::generate_splat::plan::artcraft::plan_generate_splat_artcraf
use crate::generate::generate_splat::plan::artcraft::plan_generate_splat_artcraft_marble_0p1_plus::plan_generate_splat_artcraft_marble_0p1_plus;
use crate::generate::generate_splat::splat_generation_plan::SplatGenerationPlan;
pub struct GenerateSplatRequest {
pub struct GenerateSplatRequest<'a> {
/// Which model to use.
pub model: CommonSplatModel,
@@ -14,40 +14,40 @@ pub struct GenerateSplatRequest {
pub provider: Provider,
/// The prompt for splat generation (optional).
pub prompt: Option<String>,
pub prompt: Option<&'a str>,
/// Reference images (optional).
pub reference_images: Option<ImageListRef>,
pub reference_images: Option<ImageListRef<'a>>,
/// Some providers support idempotency.
/// If not supplied, we'll generate one for the required providers.
pub idempotency_token: Option<String>,
pub idempotency_token: Option<&'a str>,
}
impl GenerateSplatRequest {
impl<'a> GenerateSplatRequest<'a> {
/// Read the splat generation request, construct a plan, then yield a means to execute it.
pub fn build(&self) -> Result<SplatGenerationPlan, ArtcraftRouterError> {
pub fn build(&self) -> Result<SplatGenerationPlan<'_>, ArtcraftRouterError> {
match self.provider {
Provider::Artcraft => self.build_artcraft(),
_ => self.unsupported_provider(),
}
}
fn build_artcraft(&self) -> Result<SplatGenerationPlan, ArtcraftRouterError> {
fn build_artcraft(&self) -> Result<SplatGenerationPlan<'_>, ArtcraftRouterError> {
match self.model {
CommonSplatModel::Marble0p1Mini => plan_generate_splat_artcraft_marble_0p1_mini(self),
CommonSplatModel::Marble0p1Plus => plan_generate_splat_artcraft_marble_0p1_plus(self),
}
}
fn unsupported_provider(&self) -> Result<SplatGenerationPlan, ArtcraftRouterError> {
fn unsupported_provider(&self) -> Result<SplatGenerationPlan<'_>, ArtcraftRouterError> {
Err(ArtcraftRouterError::UnsupportedModel(
format!("Splat generation for model `{:?}` is not supported for provider {:?}", self.model, self.provider)
))
}
pub fn get_or_generate_idempotency_token(&self) -> String {
self.idempotency_token.clone()
self.idempotency_token.map(|t| t.to_string())
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string())
}
}
@@ -6,31 +6,31 @@ use crate::generate::generate_splat::splat_generation_plan::SplatGenerationPlan;
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftMarble0p1Mini {
pub prompt: Option<String>,
pub reference_image: Option<MediaFileToken>,
pub struct PlanArtcraftMarble0p1Mini<'a> {
pub prompt: Option<&'a str>,
pub reference_image: Option<&'a MediaFileToken>,
pub idempotency_token: String,
}
pub fn plan_generate_splat_artcraft_marble_0p1_mini(
request: &GenerateSplatRequest,
) -> Result<SplatGenerationPlan, ArtcraftRouterError> {
let reference_image = resolve_single_image_ref(request.reference_images.clone())?;
pub fn plan_generate_splat_artcraft_marble_0p1_mini<'a>(
request: &'a GenerateSplatRequest<'a>,
) -> Result<SplatGenerationPlan<'a>, ArtcraftRouterError> {
let reference_image = resolve_single_image_ref(request.reference_images)?;
Ok(SplatGenerationPlan::ArtcraftMarble0p1Mini(PlanArtcraftMarble0p1Mini {
prompt: request.prompt.clone(),
prompt: request.prompt,
reference_image,
idempotency_token: request.get_or_generate_idempotency_token(),
}))
}
fn resolve_single_image_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<MediaFileToken>, ArtcraftRouterError> {
fn resolve_single_image_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a MediaFileToken>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => {
Ok(tokens.into_iter().next())
Ok(tokens.first())
}
Some(ImageListRef::Urls(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
@@ -6,31 +6,31 @@ use crate::generate::generate_splat::splat_generation_plan::SplatGenerationPlan;
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftMarble0p1Plus {
pub prompt: Option<String>,
pub reference_image: Option<MediaFileToken>,
pub struct PlanArtcraftMarble0p1Plus<'a> {
pub prompt: Option<&'a str>,
pub reference_image: Option<&'a MediaFileToken>,
pub idempotency_token: String,
}
pub fn plan_generate_splat_artcraft_marble_0p1_plus(
request: &GenerateSplatRequest,
) -> Result<SplatGenerationPlan, ArtcraftRouterError> {
let reference_image = resolve_single_image_ref(request.reference_images.clone())?;
pub fn plan_generate_splat_artcraft_marble_0p1_plus<'a>(
request: &'a GenerateSplatRequest<'a>,
) -> Result<SplatGenerationPlan<'a>, ArtcraftRouterError> {
let reference_image = resolve_single_image_ref(request.reference_images)?;
Ok(SplatGenerationPlan::ArtcraftMarble0p1Plus(PlanArtcraftMarble0p1Plus {
prompt: request.prompt.clone(),
prompt: request.prompt,
reference_image,
idempotency_token: request.get_or_generate_idempotency_token(),
}))
}
fn resolve_single_image_ref(
image_list_ref: Option<ImageListRef>,
) -> Result<Option<MediaFileToken>, ArtcraftRouterError> {
fn resolve_single_image_ref<'a>(
image_list_ref: Option<ImageListRef<'a>>,
) -> Result<Option<&'a MediaFileToken>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(None),
Some(ImageListRef::MediaFileTokens(tokens)) => {
Ok(tokens.into_iter().next())
Ok(tokens.first())
}
Some(ImageListRef::Urls(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
@@ -10,12 +10,12 @@ use crate::generate::generate_splat::plan::artcraft::plan_generate_splat_artcraf
use crate::generate::generate_splat::splat_generation_cost_estimate::SplatGenerationCostEstimate;
#[derive(Debug)]
pub enum SplatGenerationPlan {
ArtcraftMarble0p1Mini(PlanArtcraftMarble0p1Mini),
ArtcraftMarble0p1Plus(PlanArtcraftMarble0p1Plus),
pub enum SplatGenerationPlan<'a> {
ArtcraftMarble0p1Mini(PlanArtcraftMarble0p1Mini<'a>),
ArtcraftMarble0p1Plus(PlanArtcraftMarble0p1Plus<'a>),
}
impl SplatGenerationPlan {
impl<'a> SplatGenerationPlan<'a> {
pub async fn generate_splat(
&self,
client: &RouterClient,
@@ -9,7 +9,7 @@ use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraf
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling3p0_pro(
plan: &PlanArtcraftKling3p0Pro,
plan: &PlanArtcraftKling3p0Pro<'_>,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {
@@ -9,7 +9,7 @@ use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraf
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling3p0_standard(
plan: &PlanArtcraftKling3p0Standard,
plan: &PlanArtcraftKling3p0Standard<'_>,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {

Some files were not shown because too many files have changed in this diff Show More