From 5d59aaeafe8eccf8e6079d974cf7fca8a34f271e Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 10 May 2026 00:02:59 -0400 Subject: [PATCH] new endpoint --- .../image/edit/flux_1_dev_edit_image/api.rs | 100 ++++++++++++++++++ .../image/edit/flux_1_dev_edit_image/cost.rs | 52 +++++++++ .../image/edit/flux_1_dev_edit_image/mod.rs | 3 + .../edit/flux_1_dev_edit_image/raw_request.rs | 47 ++++++++ .../src/requests/api/image/edit/mod.rs | 1 + .../fal_client/src/requests/api/image/mod.rs | 3 +- 6 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/api.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/cost.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/mod.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/raw_request.rs create mode 100644 crates/api_clients/fal_client/src/requests/api/image/edit/mod.rs diff --git a/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/api.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/api.rs new file mode 100644 index 0000000000..212353f707 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/api.rs @@ -0,0 +1,100 @@ +use crate::error::fal_error_plus::FalErrorPlus; +use crate::requests::api::image::edit::flux_1_dev_edit_image::raw_request::{ + Flux1DevEditImageInput, Flux1DevEditImageOutput, +}; +use crate::requests::traits::fal_endpoint_trait::FalEndpoint; + +#[derive(Clone, Debug)] +pub struct Flux1DevEditImageRequest { + /// Text prompt describing the desired edit. + pub prompt: String, + + /// URL of the source image to edit. + pub image_url: String, + + /// Number of images to generate. + pub num_images: Flux1DevEditImageNumImages, +} + +#[derive(Copy, Clone, Debug)] +pub enum Flux1DevEditImageNumImages { + One, + Two, + Three, + Four, +} + +impl FalEndpoint for Flux1DevEditImageRequest { + const ENDPOINT: &str = "fal-ai/flux/dev/image-to-image"; + + type RawRequest = Flux1DevEditImageInput; + type RawResponse = Flux1DevEditImageOutput; + + fn to_raw_request(&self) -> Result { + let num_images = match self.num_images { + Flux1DevEditImageNumImages::One => 1, + Flux1DevEditImageNumImages::Two => 2, + Flux1DevEditImageNumImages::Three => 3, + Flux1DevEditImageNumImages::Four => 4, + }; + + Ok(Self::RawRequest { + prompt: self.prompt.clone(), + image_url: self.image_url.clone(), + num_images: Some(num_images), + // Constants + enable_safety_checker: Some(false), + output_format: Some("png".to_string()), + // Defaults + ..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() -> AnyhowResult<()> { + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&secret); + + let request = Flux1DevEditImageRequest { + prompt: "make this image look like a watercolor painting".to_string(), + image_url: GHOST_IMAGE_URL.to_string(), + num_images: Flux1DevEditImageNumImages::One, + }; + + 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_batch_two() -> AnyhowResult<()> { + let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&secret); + + let request = Flux1DevEditImageRequest { + prompt: "turn this into a cyberpunk scene with neon lights".to_string(), + image_url: GHOST_IMAGE_URL.to_string(), + num_images: Flux1DevEditImageNumImages::Two, + }; + + 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_dev_edit_image/cost.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/cost.rs new file mode 100644 index 0000000000..7e92774b25 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/cost.rs @@ -0,0 +1,52 @@ +use crate::requests::api::image::edit::flux_1_dev_edit_image::api::{ + Flux1DevEditImageNumImages, Flux1DevEditImageRequest, +}; +use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents}; + +impl FalRequestCostCalculator for Flux1DevEditImageRequest { + fn calculate_cost_in_cents(&self) -> UsdCents { + // Pricing: $0.025 per megapixel. + // For a 1024x1024 image (~1 MP), that's ~3 cents per image. + let unit_cost = 3; + let cost = match self.num_images { + Flux1DevEditImageNumImages::One => unit_cost, + Flux1DevEditImageNumImages::Two => unit_cost * 2, + Flux1DevEditImageNumImages::Three => unit_cost * 3, + Flux1DevEditImageNumImages::Four => unit_cost * 4, + }; + cost as UsdCents + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_request(num_images: Flux1DevEditImageNumImages) -> Flux1DevEditImageRequest { + Flux1DevEditImageRequest { + prompt: "test".to_string(), + image_url: "https://example.com/image.jpg".to_string(), + num_images, + } + } + + #[test] + fn cost_one_image() { + assert_eq!(make_request(Flux1DevEditImageNumImages::One).calculate_cost_in_cents(), 3); + } + + #[test] + fn cost_two_images() { + assert_eq!(make_request(Flux1DevEditImageNumImages::Two).calculate_cost_in_cents(), 6); + } + + #[test] + fn cost_three_images() { + assert_eq!(make_request(Flux1DevEditImageNumImages::Three).calculate_cost_in_cents(), 9); + } + + #[test] + fn cost_four_images() { + assert_eq!(make_request(Flux1DevEditImageNumImages::Four).calculate_cost_in_cents(), 12); + } +} diff --git a/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/mod.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_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_dev_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_dev_edit_image/raw_request.rs b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/raw_request.rs new file mode 100644 index 0000000000..ef3af630d9 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/flux_1_dev_edit_image/raw_request.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Flux1DevEditImageInput { + pub prompt: String, + + pub image_url: String, + + /// 0.0 - 1.0 + /// Default: 0.95 + #[serde(skip_serializing_if = "Option::is_none")] + pub strength: 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 guidance_scale: 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 Flux1DevEditImageFile { + pub url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Flux1DevEditImageOutput { + 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 new file mode 100644 index 0000000000..3dace933d0 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/api/image/edit/mod.rs @@ -0,0 +1 @@ +pub mod flux_1_dev_edit_image; diff --git a/crates/api_clients/fal_client/src/requests/api/image/mod.rs b/crates/api_clients/fal_client/src/requests/api/image/mod.rs index af1ff64b86..ba830e736a 100644 --- a/crates/api_clients/fal_client/src/requests/api/image/mod.rs +++ b/crates/api_clients/fal_client/src/requests/api/image/mod.rs @@ -1,2 +1,3 @@ pub mod angle; -pub mod background; \ No newline at end of file +pub mod background; +pub mod edit; \ No newline at end of file