mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
new endpoint
This commit is contained in:
1 parent
9e2a77a7fe
commit
5d59aaeafe
6 files changed
+205
-1
No files matched your search
+100
@@ -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
|
||||
}
|
||||
+52
@@ -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;
|
||||
+47
@@ -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;
|
||||
Reference in new issue
Block a user