From d78cca34f3f288f08be0023d702c853f4fe951de Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 10 May 2026 02:13:25 -0400 Subject: [PATCH] nano banana pro text to image --- .../src/requests/api/image/text/mod.rs | 1 + .../text/nano_banana_pro_text_to_image/api.rs | 123 ++++++++++++++++++ .../nano_banana_pro_text_to_image/cost.rs | 72 ++++++++++ .../text/nano_banana_pro_text_to_image/mod.rs | 3 + .../raw_request.rs | 36 +++++ 5 files changed, 235 insertions(+) create mode 100644 crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/api.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/cost.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/mod.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/raw_request.rs diff --git a/crates/api_clients/fal_client/src/requests/api/image/text/mod.rs b/crates/api_clients/fal_client/src/requests/api/image/text/mod.rs index 3ac3d48292..8a70cb773f 100644 --- a/crates/api_clients/fal_client/src/requests/api/image/text/mod.rs +++ b/crates/api_clients/fal_client/src/requests/api/image/text/mod.rs @@ -1,3 +1,4 @@ pub mod flux_1_dev_text_to_image; pub mod flux_1_schnell_text_to_image; pub mod nano_banana_2_text_to_image; +pub mod nano_banana_pro_text_to_image; diff --git a/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/api.rs b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/api.rs new file mode 100644 index 0000000000..5f97d64d0c --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/api.rs @@ -0,0 +1,123 @@ +use crate::error::fal_error_plus::FalErrorPlus; +use crate::requests::api::image::text::nano_banana_pro_text_to_image::raw_request::{ + NanoBananaProTextToImageInput, NanoBananaProTextToImageOutput, +}; +use crate::requests::traits::fal_endpoint_trait::FalEndpoint; + +#[derive(Clone, Debug)] +pub struct NanoBananaProTextToImageRequest { + /// Text prompt describing the image to generate. + pub prompt: String, + + /// Number of images to generate. + pub num_images: NanoBananaProTextToImageNumImages, + + /// Output resolution. + pub resolution: Option, + + /// Aspect ratio. + pub aspect_ratio: Option, +} + +#[derive(Copy, Clone, Debug)] +pub enum NanoBananaProTextToImageNumImages { + One, + Two, + Three, + Four, +} + +#[derive(Copy, Clone, Debug)] +pub enum NanoBananaProTextToImageResolution { + OneK, + TwoK, + FourK, +} + +/// 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16 +#[derive(Copy, Clone, Debug)] +pub enum NanoBananaProTextToImageAspectRatio { + OneByOne, + FiveByFour, + FourByThree, + ThreeByTwo, + SixteenByNine, + TwentyOneByNine, + FourByFive, + ThreeByFour, + TwoByThree, + NineBySixteen, +} + +impl FalEndpoint for NanoBananaProTextToImageRequest { + const ENDPOINT: &str = "fal-ai/nano-banana-pro"; + + type RawRequest = NanoBananaProTextToImageInput; + type RawResponse = NanoBananaProTextToImageOutput; + + fn to_raw_request(&self) -> Result { + let num_images = match self.num_images { + NanoBananaProTextToImageNumImages::One => 1, + NanoBananaProTextToImageNumImages::Two => 2, + NanoBananaProTextToImageNumImages::Three => 3, + NanoBananaProTextToImageNumImages::Four => 4, + }; + + let resolution = self.resolution.map(|r| match r { + NanoBananaProTextToImageResolution::OneK => "1K", + NanoBananaProTextToImageResolution::TwoK => "2K", + NanoBananaProTextToImageResolution::FourK => "4K", + }.to_string()); + + let aspect_ratio = self.aspect_ratio.map(|ar| match ar { + NanoBananaProTextToImageAspectRatio::OneByOne => "1:1", + NanoBananaProTextToImageAspectRatio::FiveByFour => "5:4", + NanoBananaProTextToImageAspectRatio::FourByThree => "4:3", + NanoBananaProTextToImageAspectRatio::ThreeByTwo => "3:2", + NanoBananaProTextToImageAspectRatio::SixteenByNine => "16:9", + NanoBananaProTextToImageAspectRatio::TwentyOneByNine => "21:9", + NanoBananaProTextToImageAspectRatio::FourByFive => "4:5", + NanoBananaProTextToImageAspectRatio::ThreeByFour => "3:4", + NanoBananaProTextToImageAspectRatio::TwoByThree => "2:3", + NanoBananaProTextToImageAspectRatio::NineBySixteen => "9:16", + }.to_string()); + + Ok(Self::RawRequest { + prompt: self.prompt.clone(), + num_images: Some(num_images), + aspect_ratio, + resolution, + output_format: Some("png".to_string()), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::creds::fal_api_key::FalApiKey; + use crate::requests::traits::fal_endpoint_trait::FalEndpoint; + use errors::AnyhowResult; + use std::fs::read_to_string; + + #[tokio::test] + #[ignore] // manually test — requires real API key, incurs costs + async fn test_text_to_image() -> AnyhowResult<()> { + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&secret); + + let request = NanoBananaProTextToImageRequest { + prompt: "an anime girl riding on the back of a t-rex".to_string(), + num_images: NanoBananaProTextToImageNumImages::One, + resolution: Some(NanoBananaProTextToImageResolution::TwoK), + aspect_ratio: Some(NanoBananaProTextToImageAspectRatio::SixteenByNine), + }; + + let result = request.send_webhook_request(&api_key, "https://example.com/test").await?; + println!("Request ID: {:?}", result.request_id); + assert!(result.request_id.is_some()); + Ok(()) + } + + // NB: Pricing tests are in cost.rs +} diff --git a/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/cost.rs b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/cost.rs new file mode 100644 index 0000000000..deb93c2aa7 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/cost.rs @@ -0,0 +1,72 @@ +use crate::requests::api::image::text::nano_banana_pro_text_to_image::api::{ + NanoBananaProTextToImageNumImages, NanoBananaProTextToImageRequest, + NanoBananaProTextToImageResolution, +}; +use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents}; + +impl FalRequestCostCalculator for NanoBananaProTextToImageRequest { + fn calculate_cost_in_cents(&self) -> UsdCents { + // Your request will cost $0.15 per image. + // 4K outputs will be charged at double the standard rate. + let base_cost = match self.resolution { + None => 15, + Some(NanoBananaProTextToImageResolution::OneK) => 15, + Some(NanoBananaProTextToImageResolution::TwoK) => 15, + Some(NanoBananaProTextToImageResolution::FourK) => 30, + }; + let cost = match self.num_images { + NanoBananaProTextToImageNumImages::One => base_cost, + NanoBananaProTextToImageNumImages::Two => base_cost * 2, + NanoBananaProTextToImageNumImages::Three => base_cost * 3, + NanoBananaProTextToImageNumImages::Four => base_cost * 4, + }; + cost as UsdCents + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_request( + num_images: NanoBananaProTextToImageNumImages, + resolution: Option, + ) -> NanoBananaProTextToImageRequest { + NanoBananaProTextToImageRequest { + prompt: "test".to_string(), + num_images, + resolution, + aspect_ratio: None, + } + } + + #[test] + fn cost_default_resolution_one_image() { + assert_eq!(make_request(NanoBananaProTextToImageNumImages::One, None).calculate_cost_in_cents(), 15); + } + + #[test] + fn cost_one_k_one_image() { + assert_eq!(make_request(NanoBananaProTextToImageNumImages::One, Some(NanoBananaProTextToImageResolution::OneK)).calculate_cost_in_cents(), 15); + } + + #[test] + fn cost_two_k_one_image() { + assert_eq!(make_request(NanoBananaProTextToImageNumImages::One, Some(NanoBananaProTextToImageResolution::TwoK)).calculate_cost_in_cents(), 15); + } + + #[test] + fn cost_four_k_one_image() { + assert_eq!(make_request(NanoBananaProTextToImageNumImages::One, Some(NanoBananaProTextToImageResolution::FourK)).calculate_cost_in_cents(), 30); + } + + #[test] + fn cost_one_k_four_images() { + assert_eq!(make_request(NanoBananaProTextToImageNumImages::Four, Some(NanoBananaProTextToImageResolution::OneK)).calculate_cost_in_cents(), 60); + } + + #[test] + fn cost_four_k_two_images() { + assert_eq!(make_request(NanoBananaProTextToImageNumImages::Two, Some(NanoBananaProTextToImageResolution::FourK)).calculate_cost_in_cents(), 60); + } +} diff --git a/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/mod.rs b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/mod.rs new file mode 100644 index 0000000000..a0a589f2cd --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/mod.rs @@ -0,0 +1,3 @@ +pub mod api; +mod raw_request; +mod cost; diff --git a/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/raw_request.rs b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/raw_request.rs new file mode 100644 index 0000000000..a85c715680 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/text/nano_banana_pro_text_to_image/raw_request.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct NanoBananaProTextToImageInput { + pub prompt: String, + + /// Options: auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16 + /// Default: "auto" + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + + /// "1K", "2K", "4K" + /// Default: "1K" + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution: Option, + + /// 1 - 4 + /// Default: 1 + #[serde(skip_serializing_if = "Option::is_none")] + pub num_images: Option, + + /// "jpeg", "png", "webp" + /// Default: "png" + #[serde(skip_serializing_if = "Option::is_none")] + pub output_format: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct NanoBananaProTextToImageFile { + pub url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct NanoBananaProTextToImageOutput { + pub images: Vec, +}