add fal wrapper nb integrations

This commit is contained in:
Brandon Thomas committed 2025-12-02 14:57:00 -05:00
1 parent bbb62bb1a8
commit 69cc2db080
7 files changed
+298 -6

No files matched your search

@@ -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<String>,
pub num_images: EnqueueNanoBananaProImageEditNumImages,
// Optional args
pub resolution: Option<EnqueueNanoBananaProImageEditResolution>,
pub aspect_ratio: Option<EnqueueNanoBananaProImageEditAspectRatio>,
// 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<R: IntoUrl>(
args: EnqueueNanoBananaProImageEditArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
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(())
}
}
@@ -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;
@@ -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;
@@ -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<EnqueueNanoBananaProTextToImageResolution>,
pub aspect_ratio: Option<EnqueueNanoBananaProTextToImageAspectRatio>,
// 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<R: IntoUrl>(
args: EnqueueNanoBananaProTextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
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(())
}
}
@@ -0,0 +1 @@
pub mod enqueue_nano_banana_pro_text_to_image_webhook;
@@ -7,10 +7,12 @@ pub struct NanoBananaProImageEditInput {
pub image_urls: Vec<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<String>,
/// Eg. "1K", "2K", "4K"
pub resolution: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
/// 1 - 4
#[serde(skip_serializing_if = "Option::is_none")]
@@ -46,7 +48,7 @@ pub struct NanoBananaProImageEditOutput {
pub images: Vec<ImageFile>,
}
pub fn gemini_25_flash_image_edit(
pub fn nano_banana_pro_image_edit(
params: NanoBananaProImageEditInput,
) -> FalRequest<NanoBananaProImageEditInput, NanoBananaProImageEditOutput> {
FalRequest::new("fal-ai/nano-banana-pro/edit", params)
@@ -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<String>,
/// Eg. "1K", "2K", "4K"
pub resolution: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
/// 1 - 4
#[serde(skip_serializing_if = "Option::is_none")]
@@ -30,7 +32,7 @@ pub struct NanoBananaProTextToImageOutput {
pub images: Vec<ImageFile>,
}
pub fn gemini_25_flash_image_edit(
pub fn nano_banana_pro_text_to_image(
params: NanoBananaProTextToImageInput,
) -> FalRequest<NanoBananaProTextToImageInput, NanoBananaProTextToImageOutput> {
FalRequest::new("fal-ai/nano-banana-pro", params)