From 33a866a67d5fa3a87862165d1bed0ece61a94b17 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 10 May 2026 00:14:54 -0400 Subject: [PATCH] endpoint --- .../edit/flux_1_schnell_edit_image/api.rs | 117 ++++++++++++++++++ .../edit/flux_1_schnell_edit_image/cost.rs | 53 ++++++++ .../edit/flux_1_schnell_edit_image/mod.rs | 3 + .../flux_1_schnell_edit_image/raw_request.rs | 41 ++++++ .../src/requests/api/image/edit/mod.rs | 1 + 5 files changed, 215 insertions(+) create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/api.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/cost.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/mod.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/raw_request.rs diff --git a/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/api.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/api.rs new file mode 100644 index 0000000000..37eee91d2c --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/api.rs @@ -0,0 +1,117 @@ +use crate::error::fal_error_plus::FalErrorPlus; +use crate::requests::api::image::edit::flux_1_schnell_edit_image::raw_request::{ + Flux1SchnellEditImageInput, Flux1SchnellEditImageOutput, +}; +use crate::requests::traits::fal_endpoint_trait::FalEndpoint; + +#[derive(Clone, Debug)] +pub struct Flux1SchnellEditImageRequest { + /// URL of the source image to edit. + pub image_url: String, + + /// Number of images to generate. + pub num_images: Flux1SchnellEditImageNumImages, + + /// Output image size. + pub image_size: Option, +} + +#[derive(Copy, Clone, Debug)] +pub enum Flux1SchnellEditImageNumImages { + One, + Two, + Three, + Four, +} + +#[derive(Copy, Clone, Debug)] +pub enum Flux1SchnellEditImageSize { + Square, + SquareHd, + LandscapeFourByThree, + LandscapeSixteenByNine, + PortraitThreeByFour, + PortraitNineBySixteen, +} + +impl FalEndpoint for Flux1SchnellEditImageRequest { + const ENDPOINT: &str = "fal-ai/flux/schnell/redux"; + + type RawRequest = Flux1SchnellEditImageInput; + type RawResponse = Flux1SchnellEditImageOutput; + + fn to_raw_request(&self) -> Result { + let num_images = match self.num_images { + Flux1SchnellEditImageNumImages::One => 1, + Flux1SchnellEditImageNumImages::Two => 2, + Flux1SchnellEditImageNumImages::Three => 3, + Flux1SchnellEditImageNumImages::Four => 4, + }; + + let image_size = self.image_size.map(|s| match s { + Flux1SchnellEditImageSize::Square => "square", + Flux1SchnellEditImageSize::SquareHd => "square_hd", + Flux1SchnellEditImageSize::LandscapeFourByThree => "landscape_4_3", + Flux1SchnellEditImageSize::LandscapeSixteenByNine => "landscape_16_9", + Flux1SchnellEditImageSize::PortraitThreeByFour => "portrait_4_3", + Flux1SchnellEditImageSize::PortraitNineBySixteen => "portrait_16_9", + }.to_string()); + + Ok(Self::RawRequest { + image_url: self.image_url.clone(), + num_images: Some(num_images), + image_size, + enable_safety_checker: Some(false), + output_format: Some("png".to_string()), + ..Default::default() + }) + } +} + +#[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; + use test_data::web::image_urls::GHOST_IMAGE_URL; + + #[tokio::test] + #[ignore] // manually test — requires real API key, incurs costs + async fn test_single_image_no_size() -> AnyhowResult<()> { + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&secret); + + let request = Flux1SchnellEditImageRequest { + image_url: GHOST_IMAGE_URL.to_string(), + num_images: Flux1SchnellEditImageNumImages::One, + image_size: None, + }; + + let result = request.send_queue_request(&api_key).await?; + println!("Request ID: {}", result.request_id); + assert!(!result.request_id.is_empty()); + Ok(()) + } + + #[tokio::test] + #[ignore] // manually test — requires real API key, incurs costs + async fn test_with_landscape_size() -> AnyhowResult<()> { + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&secret); + + let request = Flux1SchnellEditImageRequest { + image_url: GHOST_IMAGE_URL.to_string(), + num_images: Flux1SchnellEditImageNumImages::Two, + image_size: Some(Flux1SchnellEditImageSize::LandscapeSixteenByNine), + }; + + let result = request.send_queue_request(&api_key).await?; + println!("Request ID: {}", result.request_id); + assert!(!result.request_id.is_empty()); + Ok(()) + } + + // NB: Pricing tests are in cost.rs +} diff --git a/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/cost.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/cost.rs new file mode 100644 index 0000000000..c2995ebe28 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/cost.rs @@ -0,0 +1,53 @@ +use crate::requests::api::image::edit::flux_1_schnell_edit_image::api::{ + Flux1SchnellEditImageNumImages, Flux1SchnellEditImageRequest, +}; +use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents}; + +impl FalRequestCostCalculator for Flux1SchnellEditImageRequest { + fn calculate_cost_in_cents(&self) -> UsdCents { + // Pricing: $0.003 per megapixel. + // For a 1024x1024 image (~1 MP), that's less than 1 cent per image. + // We round up to 1 cent per image. + let unit_cost = 1; + let cost = match self.num_images { + Flux1SchnellEditImageNumImages::One => unit_cost, + Flux1SchnellEditImageNumImages::Two => unit_cost * 2, + Flux1SchnellEditImageNumImages::Three => unit_cost * 3, + Flux1SchnellEditImageNumImages::Four => unit_cost * 4, + }; + cost as UsdCents + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_request(num_images: Flux1SchnellEditImageNumImages) -> Flux1SchnellEditImageRequest { + Flux1SchnellEditImageRequest { + image_url: "https://example.com/image.jpg".to_string(), + num_images, + image_size: None, + } + } + + #[test] + fn cost_one_image() { + assert_eq!(make_request(Flux1SchnellEditImageNumImages::One).calculate_cost_in_cents(), 1); + } + + #[test] + fn cost_two_images() { + assert_eq!(make_request(Flux1SchnellEditImageNumImages::Two).calculate_cost_in_cents(), 2); + } + + #[test] + fn cost_three_images() { + assert_eq!(make_request(Flux1SchnellEditImageNumImages::Three).calculate_cost_in_cents(), 3); + } + + #[test] + fn cost_four_images() { + assert_eq!(make_request(Flux1SchnellEditImageNumImages::Four).calculate_cost_in_cents(), 4); + } +} diff --git a/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/mod.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/mod.rs new file mode 100644 index 0000000000..a0a589f2cd --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_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/edit/flux_1_schnell_edit_image/raw_request.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/raw_request.rs new file mode 100644 index 0000000000..46b8826c35 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_schnell_edit_image/raw_request.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Flux1SchnellEditImageInput { + pub image_url: String, + + /// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9 + #[serde(skip_serializing_if = "Option::is_none")] + pub image_size: Option, + + /// 1 - 4 + /// Default: 1 + #[serde(skip_serializing_if = "Option::is_none")] + pub num_images: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub num_inference_steps: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_safety_checker: Option, + + /// Options: jpeg, png + #[serde(skip_serializing_if = "Option::is_none")] + pub output_format: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub sync_mode: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Flux1SchnellEditImageFile { + pub url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Flux1SchnellEditImageOutput { + pub images: Vec, +} diff --git a/crates/api_clients/fal_client/src/requests/api/image/edit/mod.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/mod.rs index 3dace933d0..e1efea699d 100644 --- a/crates/api_clients/fal_client/src/requests/api/image/edit/mod.rs +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/mod.rs @@ -1 +1,2 @@ pub mod flux_1_dev_edit_image; +pub mod flux_1_schnell_edit_image;