new endpoint

This commit is contained in:
Brandon Thomas committed 2026-05-10 00:02:59 -04:00
1 parent 9e2a77a7fe
commit 5d59aaeafe
6 files changed
+205 -1

No files matched your search

@@ -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<Self::RawRequest, FalErrorPlus> {
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
}
@@ -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);
}
}
@@ -0,0 +1,3 @@
pub mod api;
mod raw_request;
mod cost;
@@ -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<f64>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
/// Options: jpeg, png
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flux1DevEditImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flux1DevEditImageOutput {
pub images: Vec<Flux1DevEditImageFile>,
}
@@ -0,0 +1 @@
pub mod flux_1_dev_edit_image;
@@ -1,2 +1,3 @@
pub mod angle;
pub mod background;
pub mod background;
pub mod edit;