diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/edit/enqueue_nano_banana_pro_image_edit_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/image/edit/enqueue_nano_banana_pro_image_edit_webhook.rs new file mode 100644 index 0000000000..2469240add --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/image/edit/enqueue_nano_banana_pro_image_edit_webhook.rs @@ -0,0 +1,146 @@ +use crate::creds::fal_api_key::FalApiKey; +use crate::error::classify_fal_error::classify_fal_error; +use crate::error::fal_error_plus::FalErrorPlus; +use fal::endpoints::fal_ai::nano_banana_pro::nano_banana_pro_image_edit::{nano_banana_pro_image_edit, NanoBananaProImageEditInput}; +use fal::webhook::WebhookResponse; +use reqwest::IntoUrl; + +pub struct EnqueueNanoBananaProImageEditArgs<'a, R: IntoUrl> { + // Request required + pub prompt: &'a str, + pub image_urls: Vec, + pub num_images: EnqueueNanoBananaProImageEditNumImages, + + // Optional args + pub resolution: Option, + pub aspect_ratio: Option, + + // Fulfillment + pub webhook_url: R, + pub api_key: &'a FalApiKey, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueueNanoBananaProImageEditNumImages { + One, + Two, + Three, + Four, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueueNanoBananaProImageEditResolution { + OneK, + TwoK, + FourK, +} + +/// 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16 +/// Default is "1:1" +#[derive(Copy, Clone, Debug)] +pub enum EnqueueNanoBananaProImageEditAspectRatio { + // Square + OneByOne, + // Wide + FiveByFour, + FourByThree, + ThreeByTwo, + SixteenByNine, + TwentyOneByNine, + // Tall + FourByFive, + ThreeByFour, + TwoByThree, + NineBySixteen, // NB: No NineByTwentyOne ? +} + +pub async fn enqueue_nano_banana_pro_image_edit_webhook( + args: EnqueueNanoBananaProImageEditArgs<'_, R> +) -> Result { + + let num_images = match args.num_images { + EnqueueNanoBananaProImageEditNumImages::One => 1, + EnqueueNanoBananaProImageEditNumImages::Two => 2, + EnqueueNanoBananaProImageEditNumImages::Three => 3, + EnqueueNanoBananaProImageEditNumImages::Four => 4, + }; + + let resolution = args.resolution + .map(|resolution| match resolution { + EnqueueNanoBananaProImageEditResolution::OneK => "1K", + EnqueueNanoBananaProImageEditResolution::TwoK => "2K", + EnqueueNanoBananaProImageEditResolution::FourK => "4K", + }) + .map(|resolution| resolution.to_string()); + + let aspect_ratio = args.aspect_ratio + .map(|aspect_ratio| match aspect_ratio { + // Square + EnqueueNanoBananaProImageEditAspectRatio::OneByOne => "1:1", + // Wide + EnqueueNanoBananaProImageEditAspectRatio::FiveByFour => "5:4", + EnqueueNanoBananaProImageEditAspectRatio::FourByThree => "4:3", + EnqueueNanoBananaProImageEditAspectRatio::ThreeByTwo => "3:2", + EnqueueNanoBananaProImageEditAspectRatio::SixteenByNine => "16:9", + EnqueueNanoBananaProImageEditAspectRatio::TwentyOneByNine => "21:9", + // Tall + EnqueueNanoBananaProImageEditAspectRatio::FourByFive => "4:5", + EnqueueNanoBananaProImageEditAspectRatio::ThreeByFour => "3:4", + EnqueueNanoBananaProImageEditAspectRatio::TwoByThree => "2:3", + EnqueueNanoBananaProImageEditAspectRatio::NineBySixteen => "9:16", + }) + .map(|aspect_ratio| aspect_ratio.to_string()); + + let request = NanoBananaProImageEditInput { + prompt: args.prompt.to_string(), + image_urls: args.image_urls, + num_images: Some(num_images), + // Optionals + aspect_ratio, + resolution, + // Constants + output_format: Some("png".to_string()), + }; + + let result = nano_banana_pro_image_edit(request) + .with_api_key(&args.api_key.0) + .queue_webhook(args.webhook_url) + .await; + + result.map_err(|err| classify_fal_error(err)) +} + +#[cfg(test)] +mod tests { + use crate::creds::fal_api_key::FalApiKey; + use crate::requests::webhook::image::edit::enqueue_nano_banana_pro_image_edit_webhook::{enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProImageEditArgs, EnqueueNanoBananaProImageEditAspectRatio, EnqueueNanoBananaProImageEditNumImages, EnqueueNanoBananaProImageEditResolution}; + use errors::AnyhowResult; + use std::fs::read_to_string; + use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL}; + + #[tokio::test] + #[ignore] + async fn test() -> AnyhowResult<()> { + // XXX: Don't commit secrets! + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + + let api_key = FalApiKey::from_str(&secret); + + let args = EnqueueNanoBananaProImageEditArgs { + image_urls: vec![ + GHOST_IMAGE_URL.to_string(), + TREX_SKELETON_IMAGE_URL.to_string(), + ], + prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly", + num_images: EnqueueNanoBananaProImageEditNumImages::Two, + aspect_ratio: Some(EnqueueNanoBananaProImageEditAspectRatio::SixteenByNine), + resolution: Some(EnqueueNanoBananaProImageEditResolution::TwoK), + api_key: &api_key, + webhook_url: "https://example.com/webhook", + }; + + let result = enqueue_nano_banana_pro_image_edit_webhook(args).await?; + + Ok(()) + } +} diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/edit/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/image/edit/mod.rs index aaf27b4faf..622160c97b 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/image/edit/mod.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/image/edit/mod.rs @@ -1,5 +1,6 @@ pub mod enqueue_flux_pro_kontext_max_edit_webhook; pub mod enqueue_gemini_25_flash_edit_webhook; pub mod enqueue_gpt_image_1_edit_image_webhook; +pub mod enqueue_nano_banana_pro_image_edit_webhook; pub mod enqueue_qwen_image_edit_webhook; pub mod enqueue_seededit_v3_edit_webhook; \ No newline at end of file diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs index 3c32e1f5e7..d8126730bb 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs @@ -6,3 +6,4 @@ pub mod enqueue_flux_pro_11_ultra_text_to_image_webhook; pub mod enqueue_gpt_image_1_text_to_image_webhook; pub mod infill; pub mod remove_background_rembg_webhook; +pub mod text; diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_nano_banana_pro_text_to_image_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_nano_banana_pro_text_to_image_webhook.rs new file mode 100644 index 0000000000..dcaf1fc7b3 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/image/text/enqueue_nano_banana_pro_text_to_image_webhook.rs @@ -0,0 +1,139 @@ +use crate::creds::fal_api_key::FalApiKey; +use crate::error::classify_fal_error::classify_fal_error; +use crate::error::fal_error_plus::FalErrorPlus; +use fal::endpoints::fal_ai::nano_banana_pro::nano_banana_pro_text_to_image::{nano_banana_pro_text_to_image, NanoBananaProTextToImageInput}; +use fal::webhook::WebhookResponse; +use reqwest::IntoUrl; + +pub struct EnqueueNanoBananaProTextToImageArgs<'a, R: IntoUrl> { + // Request required + pub prompt: &'a str, + pub num_images: EnqueueNanoBananaProTextToImageNumImages, + + // Optional args + pub resolution: Option, + pub aspect_ratio: Option, + + // Fulfillment + pub webhook_url: R, + pub api_key: &'a FalApiKey, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueueNanoBananaProTextToImageNumImages { + One, + Two, + Three, + Four, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueueNanoBananaProTextToImageResolution { + OneK, + TwoK, + FourK, +} + +/// 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16 +/// Default is "1:1" +#[derive(Copy, Clone, Debug)] +pub enum EnqueueNanoBananaProTextToImageAspectRatio { + // Square + OneByOne, + // Wide + FiveByFour, + FourByThree, + ThreeByTwo, + SixteenByNine, + TwentyOneByNine, + // Tall + FourByFive, + ThreeByFour, + TwoByThree, + NineBySixteen, // NB: No NineByTwentyOne ? +} + +pub async fn enqueue_nano_banana_pro_text_to_image_webhook( + args: EnqueueNanoBananaProTextToImageArgs<'_, R> +) -> Result { + + let num_images = match args.num_images { + EnqueueNanoBananaProTextToImageNumImages::One => 1, + EnqueueNanoBananaProTextToImageNumImages::Two => 2, + EnqueueNanoBananaProTextToImageNumImages::Three => 3, + EnqueueNanoBananaProTextToImageNumImages::Four => 4, + }; + + let resolution = args.resolution + .map(|resolution| match resolution { + EnqueueNanoBananaProTextToImageResolution::OneK => "1K", + EnqueueNanoBananaProTextToImageResolution::TwoK => "2K", + EnqueueNanoBananaProTextToImageResolution::FourK => "4K", + }) + .map(|resolution| resolution.to_string()); + + let aspect_ratio = args.aspect_ratio + .map(|aspect_ratio| match aspect_ratio { + // Square + EnqueueNanoBananaProTextToImageAspectRatio::OneByOne => "1:1", + // Wide + EnqueueNanoBananaProTextToImageAspectRatio::FiveByFour => "5:4", + EnqueueNanoBananaProTextToImageAspectRatio::FourByThree => "4:3", + EnqueueNanoBananaProTextToImageAspectRatio::ThreeByTwo => "3:2", + EnqueueNanoBananaProTextToImageAspectRatio::SixteenByNine => "16:9", + EnqueueNanoBananaProTextToImageAspectRatio::TwentyOneByNine => "21:9", + // Tall + EnqueueNanoBananaProTextToImageAspectRatio::FourByFive => "4:5", + EnqueueNanoBananaProTextToImageAspectRatio::ThreeByFour => "3:4", + EnqueueNanoBananaProTextToImageAspectRatio::TwoByThree => "2:3", + EnqueueNanoBananaProTextToImageAspectRatio::NineBySixteen => "9:16", + }) + .map(|aspect_ratio| aspect_ratio.to_string()); + + let request = NanoBananaProTextToImageInput { + prompt: args.prompt.to_string(), + num_images: Some(num_images), + // Optionals + aspect_ratio, + resolution, + // Constants + output_format: Some("png".to_string()), + }; + + let result = nano_banana_pro_text_to_image(request) + .with_api_key(&args.api_key.0) + .queue_webhook(args.webhook_url) + .await; + + result.map_err(|err| classify_fal_error(err)) +} + +#[cfg(test)] +mod tests { + use crate::creds::fal_api_key::FalApiKey; + use crate::requests::webhook::image::text::enqueue_nano_banana_pro_text_to_image_webhook::{enqueue_nano_banana_pro_text_to_image_webhook, EnqueueNanoBananaProTextToImageArgs, EnqueueNanoBananaProTextToImageAspectRatio, EnqueueNanoBananaProTextToImageNumImages, EnqueueNanoBananaProTextToImageResolution}; + use errors::AnyhowResult; + use std::fs::read_to_string; + + #[tokio::test] + #[ignore] + async fn test() -> AnyhowResult<()> { + // XXX: Don't commit secrets! + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + + let api_key = FalApiKey::from_str(&secret); + + let args = EnqueueNanoBananaProTextToImageArgs { + prompt: "an anime girl riding on the back of a t-rex", + num_images: EnqueueNanoBananaProTextToImageNumImages::One, + aspect_ratio: Some(EnqueueNanoBananaProTextToImageAspectRatio::SixteenByNine), + resolution: Some(EnqueueNanoBananaProTextToImageResolution::TwoK), + api_key: &api_key, + webhook_url: "https://example.com/webhook", + }; + + let result = enqueue_nano_banana_pro_text_to_image_webhook(args).await?; + + Ok(()) + } +} diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/text/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/image/text/mod.rs new file mode 100644 index 0000000000..579a991a18 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/image/text/mod.rs @@ -0,0 +1 @@ +pub mod enqueue_nano_banana_pro_text_to_image_webhook; \ No newline at end of file diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs index bdfde2f3a1..9526de616a 100644 --- a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs @@ -7,10 +7,12 @@ pub struct NanoBananaProImageEditInput { pub image_urls: Vec, /// Eg. "16:9", "1:1", and a ton of other options. So many options. - pub aspect_ratio: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, /// Eg. "1K", "2K", "4K" - pub resolution: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution: Option, /// 1 - 4 #[serde(skip_serializing_if = "Option::is_none")] @@ -46,7 +48,7 @@ pub struct NanoBananaProImageEditOutput { pub images: Vec, } -pub fn gemini_25_flash_image_edit( +pub fn nano_banana_pro_image_edit( params: NanoBananaProImageEditInput, ) -> FalRequest { FalRequest::new("fal-ai/nano-banana-pro/edit", params) diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs index e188679962..b35cab3dfa 100644 --- a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs @@ -5,10 +5,12 @@ pub struct NanoBananaProTextToImageInput { pub prompt: String, /// Eg. "16:9", "1:1", and a ton of other options. So many options. - pub aspect_ratio: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, /// Eg. "1K", "2K", "4K" - pub resolution: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution: Option, /// 1 - 4 #[serde(skip_serializing_if = "Option::is_none")] @@ -30,7 +32,7 @@ pub struct NanoBananaProTextToImageOutput { pub images: Vec, } -pub fn gemini_25_flash_image_edit( +pub fn nano_banana_pro_text_to_image( params: NanoBananaProTextToImageInput, ) -> FalRequest { FalRequest::new("fal-ai/nano-banana-pro", params)