From 8b735692380e677131b46e9c5cb3fa280ee714ec Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 7 Apr 2026 03:38:23 -0400 Subject: [PATCH] omni image models (#1314) --- .../estimate_image_cost_fal_flux_pro_1p1.rs | 21 ++ ...imate_image_cost_fal_flux_pro_1p1_ultra.rs | 20 ++ .../estimate_image_cost_fal_gpt_image_1p5.rs | 33 +++ .../fal/estimate_image_cost_fal_seedream_4.rs | 19 ++ .../estimate_image_cost_fal_seedream_4p5.rs | 19 ++ ...estimate_image_cost_fal_seedream_5_lite.rs | 19 ++ .../generate/generate_image/cost/fal/mod.rs | 6 + .../fal/generate_image_fal_flux_pro_1p1.rs | 32 +++ .../generate_image_fal_flux_pro_1p1_ultra.rs | 32 +++ .../fal/generate_image_fal_gpt_image_1p5.rs | 56 +++++ .../fal/generate_image_fal_seedream_4.rs | 50 +++++ .../fal/generate_image_fal_seedream_4p5.rs | 50 +++++ .../fal/generate_image_fal_seedream_5_lite.rs | 50 +++++ .../generate_image/execute/fal/mod.rs | 6 + .../generate_image/generate_image_request.rs | 12 + .../generate_image/image_generation_plan.rs | 66 ++++++ .../generate/generate_image/plan/fal/mod.rs | 6 + .../plan_generate_image_fal_flux_pro_1p1.rs | 127 +++++++++++ ...n_generate_image_fal_flux_pro_1p1_ultra.rs | 126 +++++++++++ .../plan_generate_image_fal_gpt_image_1p5.rs | 197 ++++++++++++++++ .../fal/plan_generate_image_fal_seedream_4.rs | 209 +++++++++++++++++ .../plan_generate_image_fal_seedream_4p5.rs | 201 +++++++++++++++++ ...plan_generate_image_fal_seedream_5_lite.rs | 210 ++++++++++++++++++ ...queue_flux_pro_11_text_to_image_webhook.rs | 5 +- .../src/configs/omni_gen/image_models.rs | 151 ++++++++++++- 25 files changed, 1720 insertions(+), 3 deletions(-) create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1_ultra.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_gpt_image_1p5.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4p5.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_5_lite.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1_ultra.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_gpt_image_1p5.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4p5.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_5_lite.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1_ultra.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_gpt_image_1p5.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4p5.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_5_lite.rs diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1.rs new file mode 100644 index 0000000000..6d2687c634 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1.rs @@ -0,0 +1,21 @@ +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +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<'_>, +) -> 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. + let cost_per_image: u64 = 4; + let num_images = plan.num_images.as_u64(); + let cost_in_usd_cents = cost_per_image * num_images; + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1_ultra.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1_ultra.rs new file mode 100644 index 0000000000..b9ea7f84a3 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_flux_pro_1p1_ultra.rs @@ -0,0 +1,20 @@ +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +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<'_>, +) -> ImageGenerationCostEstimate { + // Pricing: $0.06 per image. + let cost_per_image: u64 = 6; + let num_images = plan.num_images.as_u64(); + let cost_in_usd_cents = cost_per_image * num_images; + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_gpt_image_1p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_gpt_image_1p5.rs new file mode 100644 index 0000000000..78164cb0de --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_gpt_image_1p5.rs @@ -0,0 +1,33 @@ +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::{ + FalGptImage1p5ImageSize, FalGptImage1p5Quality, PlanFalGptImage1p5, +}; + +pub(crate) fn estimate_image_cost_fal_gpt_image_1p5( + plan: &PlanFalGptImage1p5<'_>, +) -> ImageGenerationCostEstimate { + // Per fal docs: + // Low: $0.009 (1024x1024) / $0.013 (other) + // Medium: $0.034 (1024x1024) / $0.050–0.051 (other) + // High: $0.133 (1024x1024) / $0.199–0.200 (other) + // Round up to whole cents. + let is_square = matches!(plan.image_size, None | Some(FalGptImage1p5ImageSize::Square)); + let cost_per_image: u64 = match (plan.quality, is_square) { + (FalGptImage1p5Quality::Low, true) => 1, + (FalGptImage1p5Quality::Low, false) => 2, + (FalGptImage1p5Quality::Medium, true) => 4, + (FalGptImage1p5Quality::Medium, false) => 6, + (FalGptImage1p5Quality::High, true) => 14, + (FalGptImage1p5Quality::High, false) => 20, + }; + let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64(); + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4.rs new file mode 100644 index 0000000000..4af2687f14 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4.rs @@ -0,0 +1,19 @@ +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +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<'_>, +) -> ImageGenerationCostEstimate { + // Pricing: $0.03 per image. + let cost_per_image: u64 = 3; + let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64(); + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4p5.rs new file mode 100644 index 0000000000..129ca8e783 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_4p5.rs @@ -0,0 +1,19 @@ +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +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<'_>, +) -> ImageGenerationCostEstimate { + // Pricing: $0.04 per image. + let cost_per_image: u64 = 4; + let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64(); + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_5_lite.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_5_lite.rs new file mode 100644 index 0000000000..efa8af58fd --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_seedream_5_lite.rs @@ -0,0 +1,19 @@ +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +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<'_>, +) -> ImageGenerationCostEstimate { + // Pricing: $0.035 per image — round up to 4 cents. + let cost_per_image: u64 = 4; + let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64(); + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/mod.rs index 04ceba2786..c25bdfd25d 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/mod.rs @@ -1,5 +1,11 @@ pub(crate) mod estimate_image_cost_fal_flux_1_dev; pub(crate) mod estimate_image_cost_fal_flux_1_schnell; +pub(crate) mod estimate_image_cost_fal_flux_pro_1p1; +pub(crate) mod estimate_image_cost_fal_flux_pro_1p1_ultra; +pub(crate) mod estimate_image_cost_fal_gpt_image_1p5; +pub(crate) mod estimate_image_cost_fal_seedream_4; +pub(crate) mod estimate_image_cost_fal_seedream_4p5; +pub(crate) mod estimate_image_cost_fal_seedream_5_lite; pub(crate) mod estimate_image_cost_fal_nano_banana; pub(crate) mod estimate_image_cost_fal_nano_banana_2; pub(crate) mod estimate_image_cost_fal_nano_banana_pro; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1.rs new file mode 100644 index 0000000000..9aaa5342ce --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1.rs @@ -0,0 +1,32 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + FalImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11; +use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{ + enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args, +}; + +pub async fn execute_fal_flux_pro_1p1( + plan: &PlanFalFluxPro11<'_>, + fal_client: &RouterFalClient, +) -> Result { + let args = FluxPro11Args { + 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(), + api_key: &fal_client.api_key, + }; + + let webhook_response = enqueue_flux_pro_11_text_to_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateImageResponse::Fal(FalImageResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1_ultra.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1_ultra.rs new file mode 100644 index 0000000000..6cdc7ec97c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_flux_pro_1p1_ultra.rs @@ -0,0 +1,32 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + FalImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra; +use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_to_image_webhook::{ + enqueue_flux_pro_11_ultra_text_to_image_webhook, FluxPro11UltraArgs, +}; + +pub async fn execute_fal_flux_pro_1p1_ultra( + plan: &PlanFalFluxPro11Ultra<'_>, + fal_client: &RouterFalClient, +) -> Result { + let args = FluxPro11UltraArgs { + 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(), + api_key: &fal_client.api_key, + }; + + let webhook_response = enqueue_flux_pro_11_ultra_text_to_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateImageResponse::Fal(FalImageResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_gpt_image_1p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_gpt_image_1p5.rs new file mode 100644 index 0000000000..e519918623 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_gpt_image_1p5.rs @@ -0,0 +1,56 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + FalImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::PlanFalGptImage1p5; +use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{ + enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs, +}; +use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{ + enqueue_gpt_image_1p5_text_to_image_webhook, EnqueueGptImage1p5TextToImageArgs, +}; + +pub async fn execute_fal_gpt_image_1p5( + plan: &PlanFalGptImage1p5<'_>, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = if plan.image_urls.is_empty() { + let args = EnqueueGptImage1p5TextToImageArgs { + prompt: plan.prompt.unwrap_or(""), + num_images: plan.num_images.to_t2i(), + image_size: plan.image_size.map(|s| s.to_t2i()), + background: None, + quality: Some(plan.quality.to_t2i()), + output_format: None, + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_gpt_image_1p5_text_to_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + } else { + let args = EnqueueGptImage1p5EditImageArgs { + prompt: plan.prompt.unwrap_or(""), + image_urls: plan.image_urls.clone(), + num_images: plan.num_images.to_edit(), + mask_image_url: None, + image_size: plan.image_size.map(|s| s.to_edit()), + background: None, + quality: Some(plan.quality.to_edit()), + input_fidelity: None, + output_format: None, + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_gpt_image_1p5_image_edit_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + }; + + Ok(GenerateImageResponse::Fal(FalImageResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4.rs new file mode 100644 index 0000000000..954f5dc900 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4.rs @@ -0,0 +1,50 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + FalImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4; +use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{ + enqueue_bytedance_seedream_v4_edit_image_webhook, EnqueueBytedanceSeedreamV4EditImageArgs, +}; +use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{ + enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs, +}; + +pub async fn execute_fal_seedream_4( + plan: &PlanFalSeedream4<'_>, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = if plan.image_urls.is_empty() { + let args = EnqueueBytedanceSeedreamV4TextToImageArgs { + 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()), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_bytedance_seedream_v4_text_to_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + } else { + let args = EnqueueBytedanceSeedreamV4EditImageArgs { + prompt: plan.prompt.unwrap_or(""), + image_urls: plan.image_urls.clone(), + num_images: Some(plan.num_images.to_edit()), + max_images: None, + image_size: plan.image_size.map(|s| s.to_edit()), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_bytedance_seedream_v4_edit_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + }; + + Ok(GenerateImageResponse::Fal(FalImageResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4p5.rs new file mode 100644 index 0000000000..20b1cdea58 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_4p5.rs @@ -0,0 +1,50 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + FalImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5; +use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{ + enqueue_bytedance_seedream_v4p5_edit_image_webhook, EnqueueBytedanceSeedreamV4p5EditImageArgs, +}; +use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{ + enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs, +}; + +pub async fn execute_fal_seedream_4p5( + plan: &PlanFalSeedream4p5<'_>, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = if plan.image_urls.is_empty() { + let args = EnqueueBytedanceSeedreamV4p5TextToImageArgs { + 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()), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + } else { + let args = EnqueueBytedanceSeedreamV4p5EditImageArgs { + prompt: plan.prompt.unwrap_or(""), + image_urls: plan.image_urls.clone(), + num_images: Some(plan.num_images.to_edit()), + max_images: None, + image_size: plan.image_size.map(|s| s.to_edit()), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_bytedance_seedream_v4p5_edit_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + }; + + Ok(GenerateImageResponse::Fal(FalImageResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_5_lite.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_5_lite.rs new file mode 100644 index 0000000000..062df008dc --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/generate_image_fal_seedream_5_lite.rs @@ -0,0 +1,50 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + FalImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite; +use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{ + enqueue_bytedance_seedream_v5_lite_edit_image_webhook, EnqueueBytedanceSeedreamV5LiteEditImageArgs, +}; +use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{ + enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs, +}; + +pub async fn execute_fal_seedream_5_lite( + plan: &PlanFalSeedream5Lite<'_>, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = if plan.image_urls.is_empty() { + let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs { + 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()), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + } else { + let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs { + prompt: plan.prompt.unwrap_or(""), + image_urls: plan.image_urls.clone(), + num_images: Some(plan.num_images.to_edit()), + max_images: None, + image_size: plan.image_size.map(|s| s.to_edit()), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))? + }; + + Ok(GenerateImageResponse::Fal(FalImageResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/mod.rs index 2316648143..3304dfb2dd 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/fal/mod.rs @@ -1,5 +1,11 @@ pub mod generate_image_fal_flux_1_dev; pub mod generate_image_fal_flux_1_schnell; +pub mod generate_image_fal_flux_pro_1p1; +pub mod generate_image_fal_flux_pro_1p1_ultra; +pub mod generate_image_fal_gpt_image_1p5; +pub mod generate_image_fal_seedream_4; +pub mod generate_image_fal_seedream_4p5; +pub mod generate_image_fal_seedream_5_lite; pub mod generate_image_fal_nano_banana; pub mod generate_image_fal_nano_banana_2; pub mod generate_image_fal_nano_banana_pro; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs index 9807b01dbd..3e96fa2938 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs @@ -23,6 +23,12 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::plan_generate_image_artcraft_seedream_5_lite; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::plan_generate_image_fal_flux_1_dev; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::plan_generate_image_fal_flux_1_schnell; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::plan_generate_image_fal_flux_pro_1p1; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::plan_generate_image_fal_flux_pro_1p1_ultra; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::plan_generate_image_fal_gpt_image_1p5; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::plan_generate_image_fal_seedream_4; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::plan_generate_image_fal_seedream_4p5; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::plan_generate_image_fal_seedream_5_lite; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana::plan_generate_image_fal_nano_banana; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_2::plan_generate_image_fal_nano_banana_2; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::plan_generate_image_fal_nano_banana_pro; @@ -106,6 +112,12 @@ impl<'a> GenerateImageRequest<'a> { match self.model { CommonImageModel::Flux1Dev => plan_generate_image_fal_flux_1_dev(self), CommonImageModel::Flux1Schnell => plan_generate_image_fal_flux_1_schnell(self), + CommonImageModel::FluxPro11 => plan_generate_image_fal_flux_pro_1p1(self), + CommonImageModel::FluxPro11Ultra => plan_generate_image_fal_flux_pro_1p1_ultra(self), + CommonImageModel::GptImage1p5 => plan_generate_image_fal_gpt_image_1p5(self), + CommonImageModel::Seedream4 => plan_generate_image_fal_seedream_4(self), + CommonImageModel::Seedream4p5 => plan_generate_image_fal_seedream_4p5(self), + CommonImageModel::Seedream5Lite => plan_generate_image_fal_seedream_5_lite(self), CommonImageModel::NanoBanana => plan_generate_image_fal_nano_banana(self), CommonImageModel::NanoBanana2 => plan_generate_image_fal_nano_banana_2(self), CommonImageModel::NanoBananaPro => plan_generate_image_fal_nano_banana_pro(self), diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs index b9630ff27b..21695f2ba4 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs @@ -15,6 +15,12 @@ use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraf use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_flux_2_lora_angles::estimate_image_cost_artcraft_flux_2_lora_angles; use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_1_dev::estimate_image_cost_fal_flux_1_dev; use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_1_schnell::estimate_image_cost_fal_flux_1_schnell; +use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_pro_1p1::estimate_image_cost_fal_flux_pro_1p1; +use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_pro_1p1_ultra::estimate_image_cost_fal_flux_pro_1p1_ultra; +use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_gpt_image_1p5::estimate_image_cost_fal_gpt_image_1p5; +use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_seedream_4::estimate_image_cost_fal_seedream_4; +use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_seedream_4p5::estimate_image_cost_fal_seedream_4p5; +use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_seedream_5_lite::estimate_image_cost_fal_seedream_5_lite; use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana::estimate_image_cost_fal_nano_banana; use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana_2::estimate_image_cost_fal_nano_banana_2; use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana_pro::estimate_image_cost_fal_nano_banana_pro; @@ -33,6 +39,12 @@ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_2_lora_angles::execute_artcraft_flux_2_lora_angles; use crate::generate::generate_image::execute::fal::generate_image_fal_flux_1_dev::execute_fal_flux_1_dev; use crate::generate::generate_image::execute::fal::generate_image_fal_flux_1_schnell::execute_fal_flux_1_schnell; +use crate::generate::generate_image::execute::fal::generate_image_fal_flux_pro_1p1::execute_fal_flux_pro_1p1; +use crate::generate::generate_image::execute::fal::generate_image_fal_flux_pro_1p1_ultra::execute_fal_flux_pro_1p1_ultra; +use crate::generate::generate_image::execute::fal::generate_image_fal_gpt_image_1p5::execute_fal_gpt_image_1p5; +use crate::generate::generate_image::execute::fal::generate_image_fal_seedream_4::execute_fal_seedream_4; +use crate::generate::generate_image::execute::fal::generate_image_fal_seedream_4p5::execute_fal_seedream_4p5; +use crate::generate::generate_image::execute::fal::generate_image_fal_seedream_5_lite::execute_fal_seedream_5_lite; use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana::execute_fal_nano_banana; use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana_2::execute_fal_nano_banana_2; use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana_pro::execute_fal_nano_banana_pro; @@ -53,6 +65,12 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_2_lora_angles::PlanArtcraftFlux2LoraAngles; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::PlanFalGptImage1p5; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5; +use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana::PlanFalNanoBanana; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_2::PlanFalNanaBanana2; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::PlanFalNanaBananaPro; @@ -74,6 +92,12 @@ pub enum ImageGenerationPlan<'a> { ArtcraftFlux2LoraAngles(PlanArtcraftFlux2LoraAngles<'a>), FalFlux1Dev(PlanFalFlux1Dev<'a>), FalFlux1Schnell(PlanFalFlux1Schnell<'a>), + FalFluxPro11(PlanFalFluxPro11<'a>), + FalFluxPro11Ultra(PlanFalFluxPro11Ultra<'a>), + FalGptImage1p5(PlanFalGptImage1p5<'a>), + FalSeedream4(PlanFalSeedream4<'a>), + FalSeedream4p5(PlanFalSeedream4p5<'a>), + FalSeedream5Lite(PlanFalSeedream5Lite<'a>), FalNanoBanana(PlanFalNanoBanana<'a>), FalNanaBanana2(PlanFalNanaBanana2<'a>), FalNanaBananaPro(PlanFalNanaBananaPro<'a>), @@ -145,6 +169,30 @@ impl<'a> ImageGenerationPlan<'a> { let fal_client = client.get_fal_client_ref()?; execute_fal_flux_1_schnell(plan, fal_client).await } + ImageGenerationPlan::FalFluxPro11(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_flux_pro_1p1(plan, fal_client).await + } + ImageGenerationPlan::FalFluxPro11Ultra(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_flux_pro_1p1_ultra(plan, fal_client).await + } + ImageGenerationPlan::FalGptImage1p5(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_gpt_image_1p5(plan, fal_client).await + } + ImageGenerationPlan::FalSeedream4(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_seedream_4(plan, fal_client).await + } + ImageGenerationPlan::FalSeedream4p5(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_seedream_4p5(plan, fal_client).await + } + ImageGenerationPlan::FalSeedream5Lite(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_seedream_5_lite(plan, fal_client).await + } ImageGenerationPlan::FalNanoBanana(plan) => { let fal_client = client.get_fal_client_ref()?; execute_fal_nano_banana(plan, fal_client).await @@ -207,6 +255,24 @@ impl<'a> ImageGenerationPlan<'a> { ImageGenerationPlan::FalFlux1Schnell(plan) => { estimate_image_cost_fal_flux_1_schnell(plan) } + ImageGenerationPlan::FalFluxPro11(plan) => { + estimate_image_cost_fal_flux_pro_1p1(plan) + } + ImageGenerationPlan::FalFluxPro11Ultra(plan) => { + estimate_image_cost_fal_flux_pro_1p1_ultra(plan) + } + ImageGenerationPlan::FalGptImage1p5(plan) => { + estimate_image_cost_fal_gpt_image_1p5(plan) + } + ImageGenerationPlan::FalSeedream4(plan) => { + estimate_image_cost_fal_seedream_4(plan) + } + ImageGenerationPlan::FalSeedream4p5(plan) => { + estimate_image_cost_fal_seedream_4p5(plan) + } + ImageGenerationPlan::FalSeedream5Lite(plan) => { + estimate_image_cost_fal_seedream_5_lite(plan) + } ImageGenerationPlan::FalNanoBanana(plan) => { estimate_image_cost_fal_nano_banana(plan) } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/mod.rs index ede63ac3c9..8358b0a38a 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/mod.rs @@ -1,5 +1,11 @@ pub mod plan_generate_image_fal_flux_1_dev; pub mod plan_generate_image_fal_flux_1_schnell; +pub mod plan_generate_image_fal_flux_pro_1p1; +pub mod plan_generate_image_fal_flux_pro_1p1_ultra; +pub mod plan_generate_image_fal_gpt_image_1p5; +pub mod plan_generate_image_fal_seedream_4; +pub mod plan_generate_image_fal_seedream_4p5; +pub mod plan_generate_image_fal_seedream_5_lite; pub mod plan_generate_image_fal_nano_banana; pub mod plan_generate_image_fal_nano_banana_2; pub mod plan_generate_image_fal_nano_banana_pro; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1.rs new file mode 100644 index 0000000000..13a5aedf7a --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1.rs @@ -0,0 +1,127 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; +use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{ + FluxPro11AspectRatio, FluxPro11NumImages, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FalFluxPro11NumImages { + One, + Two, + Three, + Four, +} + +#[derive(Debug, Clone)] +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<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + if request.image_inputs.is_some() { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_inputs", + value: "Flux Pro 1.1 is text-to-image only".to_string(), + })); + } + + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let num_images = plan_num_images(request.image_batch_count, strategy)?; + + Ok(ImageGenerationPlan::FalFluxPro11(PlanFalFluxPro11 { + prompt: request.prompt, + aspect_ratio, + num_images, + })) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + use FluxPro11AspectRatio as Ar; + match aspect_ratio { + None => Ok(Ar::Square), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Ar::Square), + + Some(CommonAspectRatio::Square) => Ok(Ar::Square), + Some(CommonAspectRatio::SquareHd) => Ok(Ar::SquareHd), + Some(CommonAspectRatio::WideFourByThree) => Ok(Ar::LandscapeFourByThree), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::LandscapeSixteenByNine), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Ar::PortraitThreeByFour), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::PortraitNineBySixteen), + + Some(CommonAspectRatio::WideFiveByFour) => Ok(Ar::LandscapeFourByThree), + Some(CommonAspectRatio::WideThreeByTwo) => Ok(Ar::LandscapeFourByThree), + Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Ar::LandscapeSixteenByNine), + Some(CommonAspectRatio::TallFourByFive) => Ok(Ar::PortraitThreeByFour), + Some(CommonAspectRatio::TallTwoByThree) => Ok(Ar::PortraitThreeByFour), + Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Ar::PortraitNineBySixteen), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Ar::Square), + }, + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(FalFluxPro11NumImages::One), + 2 => Ok(FalFluxPro11NumImages::Two), + 3 => Ok(FalFluxPro11NumImages::Three), + 4 => Ok(FalFluxPro11NumImages::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + _ => Ok(FalFluxPro11NumImages::Four), + }, + } +} + +impl FalFluxPro11NumImages { + pub fn to_fal(self) -> FluxPro11NumImages { + match self { + Self::One => FluxPro11NumImages::One, + Self::Two => FluxPro11NumImages::Two, + Self::Three => FluxPro11NumImages::Three, + Self::Four => FluxPro11NumImages::Four, + } + } + + pub fn as_u64(self) -> u64 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1_ultra.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1_ultra.rs new file mode 100644 index 0000000000..d8355de052 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_flux_pro_1p1_ultra.rs @@ -0,0 +1,126 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; +use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_to_image_webhook::{ + FluxPro11UltraAspectRatio, FluxPro11UltraNumImages, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FalFluxPro11UltraNumImages { + One, + Two, + Three, + Four, +} + +#[derive(Debug, Clone)] +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<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + if request.image_inputs.is_some() { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_inputs", + value: "Flux Pro 1.1 Ultra is text-to-image only".to_string(), + })); + } + + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let num_images = plan_num_images(request.image_batch_count, strategy)?; + + Ok(ImageGenerationPlan::FalFluxPro11Ultra(PlanFalFluxPro11Ultra { + prompt: request.prompt, + aspect_ratio, + num_images, + })) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + use FluxPro11UltraAspectRatio as Ar; + match aspect_ratio { + None => Ok(Ar::Square), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Ar::Square), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Ar::Square), + Some(CommonAspectRatio::WideFourByThree) => Ok(Ar::LandscapeFourByThree), + Some(CommonAspectRatio::WideThreeByTwo) => Ok(Ar::LandscapeThreeByTwo), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::LandscapeSixteenByNine), + Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Ar::LandscapeTwentyOneByNine), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Ar::PortraitThreeByFour), + Some(CommonAspectRatio::TallTwoByThree) => Ok(Ar::PortraitTwoByThree), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::PortraitNineBySixteen), + Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Ar::PortraitNineByTwentyOne), + + Some(CommonAspectRatio::WideFiveByFour) => Ok(Ar::LandscapeFourByThree), + Some(CommonAspectRatio::TallFourByFive) => Ok(Ar::PortraitThreeByFour), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Ar::Square), + }, + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(FalFluxPro11UltraNumImages::One), + 2 => Ok(FalFluxPro11UltraNumImages::Two), + 3 => Ok(FalFluxPro11UltraNumImages::Three), + 4 => Ok(FalFluxPro11UltraNumImages::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + _ => Ok(FalFluxPro11UltraNumImages::Four), + }, + } +} + +impl FalFluxPro11UltraNumImages { + pub fn to_fal(self) -> FluxPro11UltraNumImages { + match self { + Self::One => FluxPro11UltraNumImages::One, + Self::Two => FluxPro11UltraNumImages::Two, + Self::Three => FluxPro11UltraNumImages::Three, + Self::Four => FluxPro11UltraNumImages::Four, + } + } + + pub fn as_u64(self) -> u64 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_gpt_image_1p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_gpt_image_1p5.rs new file mode 100644 index 0000000000..663a396b38 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_gpt_image_1p5.rs @@ -0,0 +1,197 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::image_list_ref::ImageListRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; +use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{ + EnqueueGptImage1p5EditImageNumImages, EnqueueGptImage1p5EditImageQuality, + EnqueueGptImage1p5EditImageSize, +}; +use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{ + EnqueueGptImage1p5TextToImageNumImages, EnqueueGptImage1p5TextToImageQuality, + EnqueueGptImage1p5TextToImageSize, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FalGptImage1p5NumImages { + One, + Two, + Three, + Four, +} + +/// Quality is shared between t2i and edit (Low / Medium / High). +#[derive(Debug, Clone, Copy)] +pub enum FalGptImage1p5Quality { + Low, + Medium, + High, +} + +/// GPT Image 1.5 supports three image sizes: 1024x1024, 1536x1024, 1024x1536. +#[derive(Debug, Clone, Copy)] +pub enum FalGptImage1p5ImageSize { + Square, + Wide, + Tall, +} + +#[derive(Debug, Clone)] +pub struct PlanFalGptImage1p5<'a> { + pub prompt: Option<&'a str>, + pub image_urls: Vec, + pub image_size: Option, + pub quality: FalGptImage1p5Quality, + pub num_images: FalGptImage1p5NumImages, +} + +pub fn plan_generate_image_fal_gpt_image_1p5<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + let image_urls = resolve_image_list_ref(request.image_inputs)?; + let image_size = plan_image_size(request.aspect_ratio); + let num_images = plan_num_images(request.image_batch_count, strategy)?; + + Ok(ImageGenerationPlan::FalGptImage1p5(PlanFalGptImage1p5 { + prompt: request.prompt, + image_urls, + image_size, + quality: FalGptImage1p5Quality::Medium, + num_images, + })) +} + +fn resolve_image_list_ref( + image_list_ref: Option>, +) -> Result, ArtcraftRouterError> { + match image_list_ref { + None => Ok(vec![]), + Some(ImageListRef::Urls(urls)) => Ok(urls.clone()), + Some(ImageListRef::MediaFileTokens(_)) => { + Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls)) + } + } +} + +fn plan_image_size(aspect_ratio: Option) -> Option { + use FalGptImage1p5ImageSize as S; + match aspect_ratio { + None + | Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => None, + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Some(S::Square), + + Some(CommonAspectRatio::WideThreeByTwo) + | Some(CommonAspectRatio::WideFourByThree) + | Some(CommonAspectRatio::WideFiveByFour) + | Some(CommonAspectRatio::WideSixteenByNine) + | Some(CommonAspectRatio::WideTwentyOneByNine) + | Some(CommonAspectRatio::Wide) => Some(S::Wide), + + Some(CommonAspectRatio::TallTwoByThree) + | Some(CommonAspectRatio::TallThreeByFour) + | Some(CommonAspectRatio::TallFourByFive) + | Some(CommonAspectRatio::TallNineBySixteen) + | Some(CommonAspectRatio::TallNineByTwentyOne) + | Some(CommonAspectRatio::Tall) => Some(S::Tall), + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(FalGptImage1p5NumImages::One), + 2 => Ok(FalGptImage1p5NumImages::Two), + 3 => Ok(FalGptImage1p5NumImages::Three), + 4 => Ok(FalGptImage1p5NumImages::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + _ => Ok(FalGptImage1p5NumImages::Four), + }, + } +} + +impl FalGptImage1p5NumImages { + pub fn to_t2i(self) -> EnqueueGptImage1p5TextToImageNumImages { + use EnqueueGptImage1p5TextToImageNumImages as T; + match self { + Self::One => T::One, + Self::Two => T::Two, + Self::Three => T::Three, + Self::Four => T::Four, + } + } + + pub fn to_edit(self) -> EnqueueGptImage1p5EditImageNumImages { + use EnqueueGptImage1p5EditImageNumImages as E; + match self { + Self::One => E::One, + Self::Two => E::Two, + Self::Three => E::Three, + Self::Four => E::Four, + } + } + + pub fn as_u64(self) -> u64 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + } + } +} + +impl FalGptImage1p5Quality { + pub fn to_t2i(self) -> EnqueueGptImage1p5TextToImageQuality { + use EnqueueGptImage1p5TextToImageQuality as T; + match self { + Self::Low => T::Low, + Self::Medium => T::Medium, + Self::High => T::High, + } + } + + pub fn to_edit(self) -> EnqueueGptImage1p5EditImageQuality { + use EnqueueGptImage1p5EditImageQuality as E; + match self { + Self::Low => E::Low, + Self::Medium => E::Medium, + Self::High => E::High, + } + } +} + +impl FalGptImage1p5ImageSize { + pub fn to_t2i(self) -> EnqueueGptImage1p5TextToImageSize { + use EnqueueGptImage1p5TextToImageSize as T; + match self { + Self::Square => T::Square, + Self::Wide => T::Wide, + Self::Tall => T::Tall, + } + } + + pub fn to_edit(self) -> EnqueueGptImage1p5EditImageSize { + use EnqueueGptImage1p5EditImageSize as E; + match self { + Self::Square => E::Square, + Self::Wide => E::Wide, + Self::Tall => E::Tall, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4.rs new file mode 100644 index 0000000000..fd04881759 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4.rs @@ -0,0 +1,209 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::image_list_ref::ImageListRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; +use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{ + EnqueueBytedanceSeedreamV4EditImageNumImages, EnqueueBytedanceSeedreamV4EditImageSize, +}; +use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{ + EnqueueBytedanceSeedreamV4TextToImageNumImages, EnqueueBytedanceSeedreamV4TextToImageSize, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FalSeedream4NumImages { + One, + Two, + Three, + Four, +} + +/// Shared image-size shape between Seedream V4 t2i and edit (variants are identical). +#[derive(Debug, Clone, Copy)] +pub enum FalSeedream4ImageSize { + Square, + SquareHd, + PortraitFourThree, + PortraitSixteenNine, + LandscapeFourThree, + LandscapeSixteenNine, + Auto, + Auto2k, + Auto4k, +} + +#[derive(Debug, Clone)] +pub struct PlanFalSeedream4<'a> { + pub prompt: Option<&'a str>, + /// Image URLs for editing. Empty vec = text-to-image mode. + pub image_urls: Vec, + pub image_size: Option, + pub num_images: FalSeedream4NumImages, +} + +pub fn plan_generate_image_fal_seedream_4<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, 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)?; + 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, + image_urls, + image_size, + num_images, + })) +} + +fn resolve_image_list_ref( + image_list_ref: Option>, +) -> Result, ArtcraftRouterError> { + match image_list_ref { + None => Ok(vec![]), + Some(ImageListRef::Urls(urls)) => Ok(urls.clone()), + Some(ImageListRef::MediaFileTokens(_)) => { + Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls)) + } + } +} + +fn plan_image_size( + aspect_ratio: Option, + is_edit_mode: bool, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use FalSeedream4ImageSize as S; + match aspect_ratio { + None => Ok(None), + + Some(CommonAspectRatio::Auto) => { + if is_edit_mode { Ok(Some(S::Auto)) } else { Ok(Some(S::Square)) } + } + Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)), + Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto4k)), + + Some(CommonAspectRatio::Square) => Ok(Some(S::Square)), + Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)), + + Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)), + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)), + + Some(unsupported @ CommonAspectRatio::WideFiveByFour) + | Some(unsupported @ CommonAspectRatio::WideThreeByTwo) + | Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(S::LandscapeSixteenNine)), + }, + + Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)), + + Some(unsupported @ CommonAspectRatio::TallFourByFive) + | Some(unsupported @ CommonAspectRatio::TallTwoByThree) + | Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(S::PortraitSixteenNine)), + }, + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(FalSeedream4NumImages::One), + 2 => Ok(FalSeedream4NumImages::Two), + 3 => Ok(FalSeedream4NumImages::Three), + 4 => Ok(FalSeedream4NumImages::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + _ => Ok(FalSeedream4NumImages::Four), + }, + } +} + +impl FalSeedream4NumImages { + pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4TextToImageNumImages { + use EnqueueBytedanceSeedreamV4TextToImageNumImages as T; + match self { + Self::One => T::One, + Self::Two => T::Two, + Self::Three => T::Three, + Self::Four => T::Four, + } + } + + pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4EditImageNumImages { + use EnqueueBytedanceSeedreamV4EditImageNumImages as E; + match self { + Self::One => E::One, + Self::Two => E::Two, + Self::Three => E::Three, + Self::Four => E::Four, + } + } + + pub fn as_u64(self) -> u64 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + } + } +} + +impl FalSeedream4ImageSize { + pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4TextToImageSize { + use EnqueueBytedanceSeedreamV4TextToImageSize as T; + match self { + Self::Square => T::Square, + Self::SquareHd => T::SquareHd, + Self::PortraitFourThree => T::PortraitFourThree, + Self::PortraitSixteenNine => T::PortraitSixteenNine, + Self::LandscapeFourThree => T::LandscapeFourThree, + Self::LandscapeSixteenNine => T::LandscapeSixteenNine, + Self::Auto => T::Auto, + Self::Auto2k => T::Auto2k, + Self::Auto4k => T::Auto4k, + } + } + + pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4EditImageSize { + use EnqueueBytedanceSeedreamV4EditImageSize as E; + match self { + Self::Square => E::Square, + Self::SquareHd => E::SquareHd, + Self::PortraitFourThree => E::PortraitFourThree, + Self::PortraitSixteenNine => E::PortraitSixteenNine, + Self::LandscapeFourThree => E::LandscapeFourThree, + Self::LandscapeSixteenNine => E::LandscapeSixteenNine, + Self::Auto => E::Auto, + Self::Auto2k => E::Auto2k, + Self::Auto4k => E::Auto4k, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4p5.rs new file mode 100644 index 0000000000..ee91d43e6d --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_4p5.rs @@ -0,0 +1,201 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::image_list_ref::ImageListRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; +use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{ + EnqueueBytedanceSeedreamV4p5EditImageNumImages, EnqueueBytedanceSeedreamV4p5EditImageSize, +}; +use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{ + EnqueueBytedanceSeedreamV4p5TextToImageNumImages, EnqueueBytedanceSeedreamV4p5TextToImageSize, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FalSeedream4p5NumImages { + One, + Two, + Three, + Four, +} + +/// Note: Seedream V4.5 lacks the bare "Auto" image_size; only Auto2k/Auto4k are supported. +#[derive(Debug, Clone, Copy)] +pub enum FalSeedream4p5ImageSize { + Square, + SquareHd, + PortraitFourThree, + PortraitSixteenNine, + LandscapeFourThree, + LandscapeSixteenNine, + Auto2k, + Auto4k, +} + +#[derive(Debug, Clone)] +pub struct PlanFalSeedream4p5<'a> { + pub prompt: Option<&'a str>, + pub image_urls: Vec, + pub image_size: Option, + pub num_images: FalSeedream4p5NumImages, +} + +pub fn plan_generate_image_fal_seedream_4p5<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + 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, + image_urls, + image_size, + num_images, + })) +} + +fn resolve_image_list_ref( + image_list_ref: Option>, +) -> Result, ArtcraftRouterError> { + match image_list_ref { + None => Ok(vec![]), + Some(ImageListRef::Urls(urls)) => Ok(urls.clone()), + Some(ImageListRef::MediaFileTokens(_)) => { + Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls)) + } + } +} + +fn plan_image_size( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use FalSeedream4p5ImageSize as S; + match aspect_ratio { + None => Ok(None), + + // No bare Auto for v4.5 — fall back to Auto2k. + Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)), + Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto4k)), + + Some(CommonAspectRatio::Square) => Ok(Some(S::Square)), + Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)), + + Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)), + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)), + + Some(unsupported @ CommonAspectRatio::WideFiveByFour) + | Some(unsupported @ CommonAspectRatio::WideThreeByTwo) + | Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(S::LandscapeSixteenNine)), + }, + + Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)), + + Some(unsupported @ CommonAspectRatio::TallFourByFive) + | Some(unsupported @ CommonAspectRatio::TallTwoByThree) + | Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(S::PortraitSixteenNine)), + }, + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(FalSeedream4p5NumImages::One), + 2 => Ok(FalSeedream4p5NumImages::Two), + 3 => Ok(FalSeedream4p5NumImages::Three), + 4 => Ok(FalSeedream4p5NumImages::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + _ => Ok(FalSeedream4p5NumImages::Four), + }, + } +} + +impl FalSeedream4p5NumImages { + pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4p5TextToImageNumImages { + use EnqueueBytedanceSeedreamV4p5TextToImageNumImages as T; + match self { + Self::One => T::One, + Self::Two => T::Two, + Self::Three => T::Three, + Self::Four => T::Four, + } + } + + pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4p5EditImageNumImages { + use EnqueueBytedanceSeedreamV4p5EditImageNumImages as E; + match self { + Self::One => E::One, + Self::Two => E::Two, + Self::Three => E::Three, + Self::Four => E::Four, + } + } + + pub fn as_u64(self) -> u64 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + } + } +} + +impl FalSeedream4p5ImageSize { + pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4p5TextToImageSize { + use EnqueueBytedanceSeedreamV4p5TextToImageSize as T; + match self { + Self::Square => T::Square, + Self::SquareHd => T::SquareHd, + Self::PortraitFourThree => T::PortraitFourThree, + Self::PortraitSixteenNine => T::PortraitSixteenNine, + Self::LandscapeFourThree => T::LandscapeFourThree, + Self::LandscapeSixteenNine => T::LandscapeSixteenNine, + Self::Auto2k => T::Auto2k, + Self::Auto4k => T::Auto4k, + } + } + + pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4p5EditImageSize { + use EnqueueBytedanceSeedreamV4p5EditImageSize as E; + match self { + Self::Square => E::Square, + Self::SquareHd => E::SquareHd, + Self::PortraitFourThree => E::PortraitFourThree, + Self::PortraitSixteenNine => E::PortraitSixteenNine, + Self::LandscapeFourThree => E::LandscapeFourThree, + Self::LandscapeSixteenNine => E::LandscapeSixteenNine, + Self::Auto2k => E::Auto2k, + Self::Auto4k => E::Auto4k, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_5_lite.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_5_lite.rs new file mode 100644 index 0000000000..813d0bd57b --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_seedream_5_lite.rs @@ -0,0 +1,210 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::common_resolution::CommonResolution; +use crate::api::image_list_ref::ImageListRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; +use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{ + EnqueueBytedanceSeedreamV5LiteEditImageNumImages, EnqueueBytedanceSeedreamV5LiteEditImageSize, +}; +use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{ + EnqueueBytedanceSeedreamV5LiteTextToImageNumImages, EnqueueBytedanceSeedreamV5LiteTextToImageSize, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FalSeedream5LiteNumImages { + One, + Two, + Three, + Four, +} + +/// Note: Seedream V5 Lite supports Auto2k and Auto3k (no 4K, no bare Auto). +#[derive(Debug, Clone, Copy)] +pub enum FalSeedream5LiteImageSize { + Square, + SquareHd, + PortraitFourThree, + PortraitSixteenNine, + LandscapeFourThree, + LandscapeSixteenNine, + Auto2k, + Auto3k, +} + +#[derive(Debug, Clone)] +pub struct PlanFalSeedream5Lite<'a> { + pub prompt: Option<&'a str>, + pub image_urls: Vec, + pub image_size: Option, + pub num_images: FalSeedream5LiteNumImages, +} + +pub fn plan_generate_image_fal_seedream_5_lite<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + 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, + image_urls, + image_size, + num_images, + })) +} + +fn resolve_image_list_ref( + image_list_ref: Option>, +) -> Result, ArtcraftRouterError> { + match image_list_ref { + None => Ok(vec![]), + Some(ImageListRef::Urls(urls)) => Ok(urls.clone()), + Some(ImageListRef::MediaFileTokens(_)) => { + Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls)) + } + } +} + +fn plan_image_size( + aspect_ratio: Option, + resolution: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use FalSeedream5LiteImageSize as S; + + // Resolution-based auto sizes take precedence when an explicit Auto* aspect ratio + // isn't specified but a resolution is — Seedream v5 lite has no 4K, fall back to 3K. + match aspect_ratio { + None => match resolution { + Some(CommonResolution::TwoK) => Ok(Some(S::Auto2k)), + Some(CommonResolution::ThreeK) | Some(CommonResolution::FourK) => Ok(Some(S::Auto3k)), + _ => Ok(None), + }, + + Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)), + // No 4K — fall back to 3K. + Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto3k)), + + Some(CommonAspectRatio::Square) => Ok(Some(S::Square)), + Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)), + + Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)), + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)), + + Some(unsupported @ CommonAspectRatio::WideFiveByFour) + | Some(unsupported @ CommonAspectRatio::WideThreeByTwo) + | Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(S::LandscapeSixteenNine)), + }, + + Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)), + + Some(unsupported @ CommonAspectRatio::TallFourByFive) + | Some(unsupported @ CommonAspectRatio::TallTwoByThree) + | Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(S::PortraitSixteenNine)), + }, + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(FalSeedream5LiteNumImages::One), + 2 => Ok(FalSeedream5LiteNumImages::Two), + 3 => Ok(FalSeedream5LiteNumImages::Three), + 4 => Ok(FalSeedream5LiteNumImages::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + _ => Ok(FalSeedream5LiteNumImages::Four), + }, + } +} + +impl FalSeedream5LiteNumImages { + pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV5LiteTextToImageNumImages { + use EnqueueBytedanceSeedreamV5LiteTextToImageNumImages as T; + match self { + Self::One => T::One, + Self::Two => T::Two, + Self::Three => T::Three, + Self::Four => T::Four, + } + } + + pub fn to_edit(self) -> EnqueueBytedanceSeedreamV5LiteEditImageNumImages { + use EnqueueBytedanceSeedreamV5LiteEditImageNumImages as E; + match self { + Self::One => E::One, + Self::Two => E::Two, + Self::Three => E::Three, + Self::Four => E::Four, + } + } + + pub fn as_u64(self) -> u64 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + } + } +} + +impl FalSeedream5LiteImageSize { + pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV5LiteTextToImageSize { + use EnqueueBytedanceSeedreamV5LiteTextToImageSize as T; + match self { + Self::Square => T::Square, + Self::SquareHd => T::SquareHd, + Self::PortraitFourThree => T::PortraitFourThree, + Self::PortraitSixteenNine => T::PortraitSixteenNine, + Self::LandscapeFourThree => T::LandscapeFourThree, + Self::LandscapeSixteenNine => T::LandscapeSixteenNine, + Self::Auto2k => T::Auto2k, + Self::Auto3k => T::Auto3k, + } + } + + pub fn to_edit(self) -> EnqueueBytedanceSeedreamV5LiteEditImageSize { + use EnqueueBytedanceSeedreamV5LiteEditImageSize as E; + match self { + Self::Square => E::Square, + Self::SquareHd => E::SquareHd, + Self::PortraitFourThree => E::PortraitFourThree, + Self::PortraitSixteenNine => E::PortraitSixteenNine, + Self::LandscapeFourThree => E::LandscapeFourThree, + Self::LandscapeSixteenNine => E::LandscapeSixteenNine, + Self::Auto2k => E::Auto2k, + Self::Auto3k => E::Auto3k, + } + } +} diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_flux_pro_11_text_to_image_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_flux_pro_11_text_to_image_webhook.rs index 7dd3d34163..999addf0c4 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_flux_pro_11_text_to_image_webhook.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_flux_pro_11_text_to_image_webhook.rs @@ -37,8 +37,9 @@ pub enum FluxPro11NumImages { impl FalRequestCostCalculator for FluxPro11Args<'_, U> { fn calculate_cost_in_cents(&self) -> UsdCents { - // Your request will cost $0.04 per megapixel. -- NB: What? Not going to calculate that. - let base_cost = 6; + // Your request will cost $0.04 per megapixel, billed by rounding up to the nearest + // megapixel. Default image_size values are ~1MP, so 4 cents per image. + let base_cost = 4; let cost = match self.num_images { FluxPro11NumImages::One => base_cost, FluxPro11NumImages::Two => base_cost * 2, diff --git a/crates/service/web/storyteller_web/src/configs/omni_gen/image_models.rs b/crates/service/web/storyteller_web/src/configs/omni_gen/image_models.rs index 2d8f7bee8f..ae03bb95a2 100644 --- a/crates/service/web/storyteller_web/src/configs/omni_gen/image_models.rs +++ b/crates/service/web/storyteller_web/src/configs/omni_gen/image_models.rs @@ -56,7 +56,46 @@ fn build_omni_gen_image_models() -> Vec { ..Default::default() }); - // ... + models.push(OmniGenImageModelDetails { + model: CommonImageModel::FluxPro11, + full_name: Some("FLUX 1.1 [pro]".to_string()), + text_prompt_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::SquareHd, + CommonAspectRatio::Square, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::WideSixteenByNine, + ]), + aspect_ratio_default: Some(CommonAspectRatio::Square), + batch_size_min: Some(1), + batch_size_max: Some(4), + batch_size_default: Some(1), + ..Default::default() + }); + + models.push(OmniGenImageModelDetails { + model: CommonImageModel::FluxPro11Ultra, + full_name: Some("FLUX 1.1 [pro] ultra".to_string()), + text_prompt_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideTwentyOneByNine, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::WideThreeByTwo, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallTwoByThree, + CommonAspectRatio::TallNineBySixteen, + CommonAspectRatio::TallNineByTwentyOne, + ]), + aspect_ratio_default: Some(CommonAspectRatio::Square), + batch_size_min: Some(1), + batch_size_max: Some(4), + batch_size_default: Some(1), + ..Default::default() + }); models.push(OmniGenImageModelDetails { model: CommonImageModel::NanoBanana, // NB: currently Gemini25Flash in our system @@ -149,6 +188,92 @@ fn build_omni_gen_image_models() -> Vec { ..Default::default() }); + models.push(OmniGenImageModelDetails { + model: CommonImageModel::GptImage1p5, + full_name: Some("GPT Image 1.5".to_string()), + text_prompt_supported: Some(true), + image_refs_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideThreeByTwo, + CommonAspectRatio::TallTwoByThree, + ]), + aspect_ratio_default: Some(CommonAspectRatio::Square), + batch_size_min: Some(1), + batch_size_max: Some(4), + batch_size_default: Some(1), + ..Default::default() + }); + + models.push(OmniGenImageModelDetails { + model: CommonImageModel::Seedream4, + full_name: Some("Seedream 4".to_string()), + text_prompt_supported: Some(true), + image_refs_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Auto, + CommonAspectRatio::Auto2k, + CommonAspectRatio::Auto4k, + CommonAspectRatio::Square, + CommonAspectRatio::SquareHd, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::Square), + aspect_ratio_default_when_editing: Some(CommonAspectRatio::Auto), + batch_size_min: Some(1), + batch_size_max: Some(4), + batch_size_default: Some(1), + ..Default::default() + }); + + models.push(OmniGenImageModelDetails { + model: CommonImageModel::Seedream4p5, + full_name: Some("Seedream 4.5".to_string()), + text_prompt_supported: Some(true), + image_refs_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Auto2k, + CommonAspectRatio::Auto4k, + CommonAspectRatio::Square, + CommonAspectRatio::SquareHd, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::Square), + aspect_ratio_default_when_editing: Some(CommonAspectRatio::Auto2k), + batch_size_min: Some(1), + batch_size_max: Some(4), + batch_size_default: Some(1), + ..Default::default() + }); + + models.push(OmniGenImageModelDetails { + model: CommonImageModel::Seedream5Lite, + full_name: Some("Seedream 5 Lite".to_string()), + text_prompt_supported: Some(true), + image_refs_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Auto2k, + CommonAspectRatio::Square, + CommonAspectRatio::SquareHd, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::Square), + aspect_ratio_default_when_editing: Some(CommonAspectRatio::Auto2k), + batch_size_min: Some(1), + batch_size_max: Some(4), + batch_size_default: Some(1), + ..Default::default() + }); + models } @@ -166,6 +291,30 @@ fn build_omni_gen_image_model_providers() -> Vec