Compare commits

..
Author SHA1 Message Date
Brandon Thomas 4ade8f9923 codegen 2026-05-04 02:56:53 -04:00
Brandon Thomas b20f73c094 change type 2026-05-04 02:54:05 -04:00
Brandon Thomas 82892c1aae new mod endpoint 2026-05-04 02:51:31 -04:00
Brandon Thomas 25ef5c085c alter endpoint, codegen 2026-05-04 02:39:41 -04:00
Brandon Thomas 3178df86e3 debug endpoint 2026-05-04 02:33:06 -04:00
861 changed files with 23343 additions and 22697 deletions
@@ -1,135 +0,0 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n\n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "status: enums::common::job_status_plus::JobStatusPlus",
"type_info": {
"type": "String",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
"char_set": 224,
"max_size": 76
}
},
{
"ordinal": 2,
"name": "external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "external_third_party_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 256
}
},
{
"ordinal": 4,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "creator_ip_address",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 160
}
},
{
"ordinal": 7,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 8,
"name": "maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 10,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
true,
true,
true,
true,
false,
true,
true,
false,
false
]
},
"hash": "31d85cc0013f29e6db2d272ee6e709cca79b46496a2c222960f1a1419175f00b"
}
Generated
-17
View File
@@ -1404,22 +1404,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "beeble_client"
version = "0.0.1"
dependencies = [
"anyhow",
"errors",
"log",
"reqwest 0.12.23",
"serde",
"serde_derive",
"serde_json",
"test_data",
"tokio",
"workspace-build-acceleration",
]
[[package]]
name = "bigdecimal"
version = "0.3.1"
@@ -11250,7 +11234,6 @@ dependencies = [
"async-stripe 0.26.0",
"async-trait",
"base64 0.22.1",
"beeble_client",
"billing_artcraft_component",
"billing_component",
"bootstrap",
-2
View File
@@ -16,7 +16,6 @@ members = [
"crates/api_clients/grok_client",
"crates/api_clients/midjourney_client",
"crates/api_clients/muapi_client",
"crates/api_clients/beeble_client",
"crates/api_clients/openai_sora_client",
"crates/api_clients/openart_client",
"crates/api_clients/rootly_client",
@@ -245,7 +244,6 @@ fal_client = { path = "crates/api_clients/fal_client" }
ffmpeg_utils = { path = "crates/lib/ffmpeg_utils" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
beeble_client = { path = "crates/api_clients/beeble_client" }
grok_client = { path = "crates/api_clients/grok_client" }
rootly_client = { path = "crates/api_clients/rootly_client" }
rootly_config = { path = "crates/schema/config/rootly_config" }
@@ -1,3 +0,0 @@
$kind: collection
name: edit
order: 312
@@ -1,23 +0,0 @@
$kind: http-request
name: Beeble SwitchX Edit Video
url: "{{api_host}}/v1/generate/video/edit/beeble_switchx"
method: POST
headers:
- key: Accept
value: application/json
- key: Session
value: ""
disabled: true
body:
type: json
content: >-
{
"uuid_idempotency_token": "{{$randomUUID}}",
//"source_video_media_token": "m_6vve5gffn2r4rajyvy8z2380x15kqc", // two corgis (dev)
"source_video_media_token": "m_rkgdk7qw0a7gz4mrzfv0q78ax57kbx", // corgi cowboy (dev)
//"source_video_media_token": "m_1k19k6dgdny253zag8g3jqf3bgemss", // corgi watermellon (prod)
"reference_image_media_token": "m_pf310vdhc4v98n7r8mz70bz9geest3" // tornado (dev)
//"reference_image_media_token": "m_z4y097t9pdmbyatf70z0qb8zvfphvm" // cactus (prod)
//"prompt": "Stormy skies, there's a rotating tornado in the background"
}
order: 1778262498846
@@ -9,7 +9,7 @@ body:
// hanashi : password (dev)
//"username_or_email": "hanashi",
//"password": "hanashi"
"username_or_email": "newuser4",
"password": "newuser4"
"username_or_email": "newuser2",
"password": "newuser2"
}
order: 1000
@@ -1,20 +0,0 @@
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
pub const BEEBLE_SWITCHX_EDIT_VIDEO_PATH: &str = "/v1/generate/video/edit/beeble_switchx";
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct BeebleSwitchXEditVideoRequest {
pub uuid_idempotency_token: String,
pub source_video_media_token: Option<MediaFileToken>,
pub reference_image_media_token: Option<MediaFileToken>,
pub prompt: Option<String>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct BeebleSwitchXEditVideoResponse {
pub success: bool,
pub inference_job_token: InferenceJobToken,
}
@@ -1 +0,0 @@
pub mod beeble_switchx_edit_video;
@@ -1,4 +1,3 @@
pub mod edit;
pub mod generate_kling_1_6_pro_image_to_video;
pub mod generate_kling_2_1_master_image_to_video;
pub mod generate_kling_2_1_pro_image_to_video;
@@ -1,20 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::edit::beeble_switchx_edit_video::{
BeebleSwitchXEditVideoRequest, BeebleSwitchXEditVideoResponse, BEEBLE_SWITCHX_EDIT_VIDEO_PATH,
};
pub async fn beeble_switchx_edit_video(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: BeebleSwitchXEditVideoRequest,
) -> Result<BeebleSwitchXEditVideoResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
BEEBLE_SWITCHX_EDIT_VIDEO_PATH,
maybe_creds,
request,
).await?)
}
@@ -1 +0,0 @@
pub mod beeble_switchx_edit_video;
@@ -1,4 +1,3 @@
pub mod edit;
pub mod generate_kling_16_pro_image_to_video;
pub mod generate_kling_21_master_image_to_video;
pub mod generate_kling_21_pro_image_to_video;
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,55 +6,44 @@ use crate::generate::generate_image::generate_image_response::{
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev;
use fal_client::requests::webhook::image::edit::enqueue_flux_1_dev_edit_image_webhook::{
enqueue_flux_1_dev_edit_image_webhook, Flux1DevEditImageArgs, Flux1DevEditImageRequest,
enqueue_flux_1_dev_edit_image_webhook, Flux1DevEditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image_webhook::{
enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs, Flux1DevRequest,
enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs,
};
pub async fn execute_fal_flux_1_dev(
plan: &PlanFalFlux1Dev,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if let Some(image_url) = &plan.maybe_image_url {
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
// Image-to-image mode
let request = Flux1DevEditImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = Flux1DevEditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_url: image_url.clone(),
num_images: plan.num_images.to_edit(),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Flux1DevEditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_flux_1_dev_edit_image_webhook(args)
enqueue_flux_1_dev_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Text-to-image mode
let request = Flux1DevRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = Flux1DevArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_t2i(),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Flux1DevArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_flux_1_dev_text_to_image_webhook(args)
enqueue_flux_1_dev_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,55 +6,44 @@ use crate::generate::generate_image::generate_image_response::{
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell;
use fal_client::requests::webhook::image::edit::enqueue_flux_1_schnell_edit_image_webhook::{
enqueue_flux_1_schnell_edit_image_webhook, Flux1SchnellEditImageArgs, Flux1SchnellEditImageRequest,
enqueue_flux_1_schnell_edit_image_webhook, Flux1SchnellEditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_image_webhook::{
enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs, Flux1SchnellRequest,
enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs,
};
pub async fn execute_fal_flux_1_schnell(
plan: &PlanFalFlux1Schnell,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if let Some(image_url) = &plan.maybe_image_url {
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
// Image-to-image (redux) mode
let request = Flux1SchnellEditImageRequest {
let args = Flux1SchnellEditImageArgs {
image_url: image_url.clone(),
num_images: plan.num_images.to_edit(),
image_size: plan.edit_image_size,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Flux1SchnellEditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_flux_1_schnell_edit_image_webhook(args)
enqueue_flux_1_schnell_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Text-to-image mode
let request = Flux1SchnellRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = Flux1SchnellArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
aspect_ratio: plan.t2i_aspect_ratio,
num_images: plan.num_images.to_t2i(),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Flux1SchnellArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_flux_1_schnell_text_to_image_webhook(args)
enqueue_flux_1_schnell_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,21 +6,17 @@ use crate::generate::generate_image::generate_image_response::{
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11;
use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{
enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args, FluxPro11Request,
enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args,
};
pub async fn execute_fal_flux_pro_1p1(
plan: &PlanFalFluxPro11,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = FluxPro11Request {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = FluxPro11Args {
prompt: plan.prompt.as_deref().unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_fal(),
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = FluxPro11Args {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
@@ -35,6 +28,5 @@ pub async fn execute_fal_flux_pro_1p1(
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,21 +6,17 @@ use crate::generate::generate_image::generate_image_response::{
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra;
use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_to_image_webhook::{
enqueue_flux_pro_11_ultra_text_to_image_webhook, FluxPro11UltraArgs, FluxPro11UltraRequest,
enqueue_flux_pro_11_ultra_text_to_image_webhook, FluxPro11UltraArgs,
};
pub async fn execute_fal_flux_pro_1p1_ultra(
plan: &PlanFalFluxPro11Ultra,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let request = FluxPro11UltraRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = FluxPro11UltraArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_fal(),
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = FluxPro11UltraArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
@@ -35,6 +28,5 @@ pub async fn execute_fal_flux_pro_1p1_ultra(
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -10,39 +7,32 @@ use crate::generate::generate_image::generate_image_response::{
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1::PlanFalGptImage1;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::{
enqueue_gpt_image_1_edit_image_webhook, EnqueueGptImage1EditImageArgs,
EnqueueGptImage1EditImageRequest,
};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_1_text_to_image_webhook::{
enqueue_gpt_image_1_text_to_image_webhook, EnqueueGptImage1TextToImageArgs,
EnqueueGptImage1TextToImageRequest,
};
pub async fn execute_fal_gpt_image_1(
plan: &PlanFalGptImage1,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let request = EnqueueGptImage1TextToImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueGptImage1TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: plan.num_images.to_t2i(),
image_size: plan.image_size.map(|s| s.to_t2i()),
quality: Some(plan.quality.to_t2i()),
background: None,
output_format: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueGptImage1TextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gpt_image_1_text_to_image_webhook(args)
enqueue_gpt_image_1_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let request = EnqueueGptImage1EditImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueGptImage1EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: plan.num_images.to_edit(),
mask_image_url: None,
@@ -51,23 +41,17 @@ pub async fn execute_fal_gpt_image_1(
input_fidelity: None,
background: None,
output_format: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueGptImage1EditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gpt_image_1_edit_image_webhook(args)
enqueue_gpt_image_1_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -10,39 +7,32 @@ use crate::generate::generate_image::generate_image_response::{
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::PlanFalGptImage1p5;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{
enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs,
EnqueueGptImage1p5EditImageRequest,
};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{
enqueue_gpt_image_1p5_text_to_image_webhook, EnqueueGptImage1p5TextToImageArgs,
EnqueueGptImage1p5TextToImageRequest,
};
pub async fn execute_fal_gpt_image_1p5(
plan: &PlanFalGptImage1p5,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let request = EnqueueGptImage1p5TextToImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueGptImage1p5TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: plan.num_images.to_t2i(),
image_size: plan.image_size.map(|s| s.to_t2i()),
background: None,
quality: Some(plan.quality.to_t2i()),
output_format: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueGptImage1p5TextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gpt_image_1p5_text_to_image_webhook(args)
enqueue_gpt_image_1p5_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let request = EnqueueGptImage1p5EditImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueGptImage1p5EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: plan.num_images.to_edit(),
mask_image_url: None,
@@ -51,22 +41,16 @@ pub async fn execute_fal_gpt_image_1p5(
quality: Some(plan.quality.to_edit()),
input_fidelity: None,
output_format: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueGptImage1p5EditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gpt_image_1p5_image_edit_webhook(args)
enqueue_gpt_image_1p5_image_edit_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -10,61 +7,48 @@ use crate::generate::generate_image::generate_image_response::{
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_2::PlanFalGptImage2;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_2_edit_image_webhook::{
enqueue_gpt_image_2_edit_image_webhook, EnqueueGptImage2EditImageArgs,
EnqueueGptImage2EditImageRequest,
};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_2_text_to_image_webhook::{
enqueue_gpt_image_2_text_to_image_webhook, EnqueueGptImage2TextToImageArgs,
EnqueueGptImage2TextToImageRequest,
};
pub async fn execute_fal_gpt_image_2(
plan: &PlanFalGptImage2,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let request = EnqueueGptImage2TextToImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueGptImage2TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: plan.num_images.to_t2i(),
image_size: plan.image_size.map(|s| s.to_t2i()),
quality: Some(plan.quality.to_t2i()),
output_format: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueGptImage2TextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gpt_image_2_text_to_image_webhook(args)
enqueue_gpt_image_2_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let request = EnqueueGptImage2EditImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueGptImage2EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: plan.num_images.to_edit(),
mask_url: None,
image_size: plan.image_size.map(|s| s.to_edit()),
quality: Some(plan.quality.to_edit()),
output_format: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueGptImage2EditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gpt_image_2_edit_image_webhook(args)
enqueue_gpt_image_2_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -12,58 +9,47 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
};
use fal_client::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{
enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs,
Gemini25FlashEditNumImages, Gemini25FlashEditRequest,
Gemini25FlashEditNumImages,
};
use fal_client::requests::webhook::image::text::enqueue_gemini_25_flash_text_to_image_webhook::{
enqueue_gemini_25_flash_text_to_image_webhook, Gemini25FlashTextToImageArgs,
Gemini25FlashTextToImageNumImages, Gemini25FlashTextToImageRequest,
Gemini25FlashTextToImageNumImages,
};
pub async fn execute_fal_nano_banana(
plan: &PlanFalNanoBanana,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let request = Gemini25FlashTextToImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = Gemini25FlashTextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
aspect_ratio: plan.t2i_aspect_ratio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Gemini25FlashTextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gemini_25_flash_text_to_image_webhook(args)
enqueue_gemini_25_flash_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Image-edit mode
let request = Gemini25FlashEditRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = Gemini25FlashEditArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
aspect_ratio: plan.edit_aspect_ratio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Gemini25FlashEditArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_gemini_25_flash_edit_webhook(args)
enqueue_gemini_25_flash_edit_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -12,62 +9,49 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
};
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::{
enqueue_nano_banana_2_edit_image_webhook, EnqueueNanoBanana2EditImageArgs,
EnqueueNanoBanana2EditImageNumImages, EnqueueNanoBanana2EditImageRequest,
EnqueueNanoBanana2EditImageResolution,
EnqueueNanoBanana2EditImageNumImages, EnqueueNanoBanana2EditImageResolution,
};
use fal_client::requests::webhook::image::text::enqueue_nano_banana_2_text_to_image_webhook::{
enqueue_nano_banana_2_text_to_image_webhook, EnqueueNanoBanana2TextToImageArgs,
EnqueueNanoBanana2TextToImageNumImages, EnqueueNanoBanana2TextToImageRequest,
EnqueueNanoBanana2TextToImageResolution,
EnqueueNanoBanana2TextToImageNumImages, EnqueueNanoBanana2TextToImageResolution,
};
pub async fn execute_fal_nano_banana_2(
plan: &PlanFalNanaBanana2,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let request = EnqueueNanoBanana2TextToImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueNanoBanana2TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
resolution: plan.resolution.map(to_t2i_resolution),
aspect_ratio: plan.t2i_aspect_ratio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueNanoBanana2TextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_nano_banana_2_text_to_image_webhook(args)
enqueue_nano_banana_2_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Image-edit mode
let request = EnqueueNanoBanana2EditImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueNanoBanana2EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
resolution: plan.resolution.map(to_edit_resolution),
aspect_ratio: plan.edit_aspect_ratio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueNanoBanana2EditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_nano_banana_2_edit_image_webhook(args)
enqueue_nano_banana_2_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -12,61 +9,48 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
};
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::{
enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProEditImageArgs,
EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageRequest,
EnqueueNanoBananaProEditImageResolution,
EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageResolution,
};
use fal_client::requests::webhook::image::text::enqueue_nano_banana_pro_text_to_image_webhook::{
enqueue_nano_banana_pro_text_to_image_webhook, EnqueueNanoBananaProTextToImageArgs,
EnqueueNanoBananaProTextToImageNumImages, EnqueueNanoBananaProTextToImageRequest,
EnqueueNanoBananaProTextToImageResolution,
EnqueueNanoBananaProTextToImageNumImages, EnqueueNanoBananaProTextToImageResolution,
};
pub async fn execute_fal_nano_banana_pro(
plan: &PlanFalNanaBananaPro,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let request = EnqueueNanoBananaProTextToImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueNanoBananaProTextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
resolution: plan.resolution.map(to_t2i_resolution),
aspect_ratio: plan.t2i_aspect_ratio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueNanoBananaProTextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_nano_banana_pro_text_to_image_webhook(args)
enqueue_nano_banana_pro_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Image-edit mode
let request = EnqueueNanoBananaProEditImageRequest {
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
let args = EnqueueNanoBananaProEditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
resolution: plan.resolution.map(to_edit_resolution),
aspect_ratio: plan.edit_aspect_ratio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueNanoBananaProEditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_nano_banana_pro_image_edit_webhook(args)
enqueue_nano_banana_pro_image_edit_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -10,57 +7,44 @@ use crate::generate::generate_image::generate_image_response::{
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{
enqueue_bytedance_seedream_v4_edit_image_webhook, EnqueueBytedanceSeedreamV4EditImageArgs,
EnqueueBytedanceSeedreamV4EditImageRequest,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{
enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs,
EnqueueBytedanceSeedreamV4TextToImageRequest,
};
pub async fn execute_fal_seedream_4(
plan: &PlanFalSeedream4,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let request = EnqueueBytedanceSeedreamV4TextToImageRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueBytedanceSeedreamV4TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueBytedanceSeedreamV4TextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_bytedance_seedream_v4_text_to_image_webhook(args)
enqueue_bytedance_seedream_v4_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let request = EnqueueBytedanceSeedreamV4EditImageRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = EnqueueBytedanceSeedreamV4EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_edit()),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueBytedanceSeedreamV4EditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_bytedance_seedream_v4_edit_image_webhook(args)
enqueue_bytedance_seedream_v4_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -10,57 +7,44 @@ use crate::generate::generate_image::generate_image_response::{
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{
enqueue_bytedance_seedream_v4p5_edit_image_webhook, EnqueueBytedanceSeedreamV4p5EditImageArgs,
EnqueueBytedanceSeedreamV4p5EditImageRequest,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{
enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs,
EnqueueBytedanceSeedreamV4p5TextToImageRequest,
};
pub async fn execute_fal_seedream_4p5(
plan: &PlanFalSeedream4p5,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let request = EnqueueBytedanceSeedreamV4p5TextToImageRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueBytedanceSeedreamV4p5TextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueBytedanceSeedreamV4p5TextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args)
enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let request = EnqueueBytedanceSeedreamV4p5EditImageRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = EnqueueBytedanceSeedreamV4p5EditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_edit()),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueBytedanceSeedreamV4p5EditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_bytedance_seedream_v4p5_edit_image_webhook(args)
enqueue_bytedance_seedream_v4p5_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -10,57 +7,44 @@ use crate::generate::generate_image::generate_image_response::{
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{
enqueue_bytedance_seedream_v5_lite_edit_image_webhook, EnqueueBytedanceSeedreamV5LiteEditImageArgs,
EnqueueBytedanceSeedreamV5LiteEditImageRequest,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{
enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs,
EnqueueBytedanceSeedreamV5LiteTextToImageRequest,
};
pub async fn execute_fal_seedream_5_lite(
plan: &PlanFalSeedream5Lite,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
let request = EnqueueBytedanceSeedreamV5LiteTextToImageRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let webhook_response = if plan.image_urls.is_empty() {
let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args)
enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let request = EnqueueBytedanceSeedreamV5LiteEditImageRequest {
prompt: plan.prompt.clone().unwrap_or_default(),
let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
prompt: plan.prompt.as_deref().unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_edit()),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let resp = enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args)
enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
#[derive(Clone, Debug)]
@@ -12,11 +9,6 @@ pub struct ArtcraftImageResponsePayload {
pub struct FalImageResponsePayload {
pub request_id: Option<String>,
pub gateway_request_id: Option<String>,
/// The outbound request that was sent to Fal.
/// Stored as a trait object so any Request type can be captured.
/// Use `format!("{:?}", ...)` or `format!("{:#?}", ...)` to print.
pub maybe_outbound_request: Option<Arc<dyn Debug + Send + Sync>>,
}
#[derive(Clone, Debug)]
@@ -1,7 +1,7 @@
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::Kling3p0ProMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_pro_text_to_video_webhook::{
EnqueueKling3p0ProTextToVideoRequest,
EnqueueKling3p0ProTextToVideoArgs,
EnqueueKling3p0ProTextToVideoDuration,
};
@@ -29,16 +29,19 @@ pub(crate) fn estimate_video_cost_artcraft_kling3p0_pro(
});
// Use text-to-video cost calculator (same pricing for both modes)
let req = EnqueueKling3p0ProTextToVideoRequest {
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
let args = EnqueueKling3p0ProTextToVideoArgs {
prompt: String::new(),
generate_audio: plan.generate_audio,
negative_prompt: None,
duration,
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
@@ -1,7 +1,7 @@
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::Kling3p0StandardMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_standard_text_to_video_webhook::{
EnqueueKling3p0StandardTextToVideoRequest,
EnqueueKling3p0StandardTextToVideoArgs,
EnqueueKling3p0StandardTextToVideoDuration,
};
@@ -29,16 +29,19 @@ pub(crate) fn estimate_video_cost_artcraft_kling3p0_standard(
});
// Use text-to-video cost calculator (same pricing for both modes)
let req = EnqueueKling3p0StandardTextToVideoRequest {
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
let args = EnqueueKling3p0StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: plan.generate_audio,
negative_prompt: None,
duration,
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
@@ -1,7 +1,7 @@
use artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::Seedance1p5ProMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
EnqueueSeedance1p5ProTextToVideoRequest,
EnqueueSeedance1p5ProTextToVideoArgs,
EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoResolution,
};
@@ -33,15 +33,18 @@ pub(crate) fn estimate_video_cost_artcraft_seedance1p5_pro(
});
// Use text-to-video cost calculator (same pricing for both modes)
let req = EnqueueSeedance1p5ProTextToVideoRequest {
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
let args = EnqueueSeedance1p5ProTextToVideoArgs {
prompt: String::new(),
duration,
resolution,
aspect_ratio: None,
generate_audio: plan.generate_audio,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
@@ -1,7 +1,8 @@
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteResolution;
use fal_client::creds::fal_api_key::FalApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{
Seedance1LiteDuration, Seedance1LiteRequest, Seedance1LiteResolution,
Seedance1LiteArgs, Seedance1LiteDuration, Seedance1LiteResolution,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance_1_0_lite::PlanArtcraftSeedance10Lite;
@@ -12,6 +13,7 @@ pub(crate) fn estimate_video_cost_artcraft_seedance_1_0_lite(
) -> VideoGenerationCostEstimate {
// Legacy handler defaults: 5 seconds, 720p. Delegate to the Fal client's
// cost calculator to guarantee parity with what we actually charge.
let api_key = FalApiKey::from_str("");
let duration = if plan.duration_seconds_for_cost() == 10 {
Seedance1LiteDuration::TenSeconds
} else {
@@ -22,18 +24,21 @@ pub(crate) fn estimate_video_cost_artcraft_seedance_1_0_lite(
GenerateSeedance10LiteResolution::SevenTwentyP => Seedance1LiteResolution::SevenTwentyP,
};
let req = Seedance1LiteRequest {
image_url: String::new(),
end_frame_image_url: None,
prompt: String::new(),
let end_frame: Option<&str> = None;
let args = Seedance1LiteArgs {
image_url: "",
end_frame_image_url: end_frame,
prompt: "",
duration,
resolution,
aspect_ratio: None,
camera_fixed: false,
seed: None,
api_key: &api_key,
webhook_url: "https://example.com",
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
@@ -1,5 +1,6 @@
use fal_client::creds::fal_api_key::FalApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::Seedance1LiteRequest;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::Seedance1LiteArgs;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1_0_lite::PlanFalSeedance10Lite;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
@@ -8,18 +9,22 @@ pub(crate) fn estimate_video_cost_fal_seedance_1_0_lite(
plan: &PlanFalSeedance10Lite,
) -> VideoGenerationCostEstimate {
// Delegate to the Fal client's cost calculator to guarantee parity with billing.
let req = Seedance1LiteRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
let api_key = FalApiKey::from_str("");
let end_frame: Option<&str> = plan.end_image_url.as_deref();
let args = Seedance1LiteArgs {
image_url: plan.image_url.as_str(),
end_frame_image_url: end_frame,
prompt: plan.prompt.as_str(),
duration: plan.duration,
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
camera_fixed: false,
seed: None,
api_key: &api_key,
webhook_url: "https://example.com",
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
@@ -1,6 +1,7 @@
use fal_client::creds::fal_api_key::FalApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
EnqueueSeedance1p5ProTextToVideoRequest, EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoArgs, EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoResolution,
};
@@ -14,15 +15,18 @@ pub(crate) fn estimate_video_cost_fal_seedance_1p5_pro(
) -> VideoGenerationCostEstimate {
// Seedance 1.5 Pro t2v and i2v have identical pricing; delegate to the t2v
// calculator to guarantee parity with billing.
let req = EnqueueSeedance1p5ProTextToVideoRequest {
let api_key = FalApiKey::from_str("");
let args = EnqueueSeedance1p5ProTextToVideoArgs {
prompt: String::new(),
resolution: plan.resolution.map(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: None,
generate_audio: plan.generate_audio,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,28 +6,26 @@ use crate::generate::generate_video::generate_video_response::{
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::PlanFalKling16Pro;
use fal_client::requests::webhook::video::image::enqueue_kling_v1p6_pro_image_to_video_webhook::{
enqueue_kling_v1p6_pro_image_to_video_webhook, Kling1p6ProArgs, Kling1p6ProRequest,
enqueue_kling_v1p6_pro_image_to_video_webhook, Kling1p6ProArgs,
};
pub async fn execute_fal_kling_1_6_pro(
plan: &PlanFalKling16Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling1p6ProRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
// The Fal arg type is generic over `Option<T: IntoUrl>` for the end frame URL —
// explicitly bind T to String here.
let end_frame_image_url: Option<String> = plan.end_image_url.clone();
let args = Kling1p6ProArgs {
image_url: plan.image_url.as_str(),
end_frame_image_url,
webhook_url: fal_client.webhook_url.as_str(),
prompt: plan.prompt.as_str(),
api_key: &fal_client.api_key,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Kling1p6ProArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let webhook_response = enqueue_kling_v1p6_pro_image_to_video_webhook(args)
.await
@@ -39,6 +34,5 @@ pub async fn execute_fal_kling_1_6_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,27 +6,21 @@ use crate::generate::generate_video::generate_video_response::{
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_master::PlanFalKling21Master;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_master_image_to_video_webhook::{
enqueue_kling_v2p1_master_image_to_video_webhook, Kling2p1MasterArgs, Kling2p1MasterRequest,
enqueue_kling_v2p1_master_image_to_video_webhook, Kling2p1MasterArgs,
};
pub async fn execute_fal_kling_2_1_master(
plan: &PlanFalKling21Master,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling2p1MasterRequest {
image_url: plan.image_url.clone(),
prompt: plan.prompt.clone(),
let args = Kling2p1MasterArgs {
image_url: plan.image_url.as_str(),
webhook_url: fal_client.webhook_url.as_str(),
prompt: plan.prompt.as_str(),
api_key: &fal_client.api_key,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Kling2p1MasterArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let webhook_response = enqueue_kling_v2p1_master_image_to_video_webhook(args)
.await
@@ -38,6 +29,5 @@ pub async fn execute_fal_kling_2_1_master(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,27 +6,23 @@ use crate::generate::generate_video::generate_video_response::{
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_pro::PlanFalKling21Pro;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::{
enqueue_kling_v2p1_pro_image_to_video_webhook, Kling2p1ProArgs, Kling2p1ProRequest,
enqueue_kling_v2p1_pro_image_to_video_webhook, Kling2p1ProArgs,
};
pub async fn execute_fal_kling_2_1_pro(
plan: &PlanFalKling21Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling2p1ProRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let end_frame_image_url: Option<String> = plan.end_image_url.clone();
let args = Kling2p1ProArgs {
request,
image_url: plan.image_url.as_str(),
end_frame_image_url,
webhook_url: fal_client.webhook_url.as_str(),
prompt: plan.prompt.as_str(),
api_key: &fal_client.api_key,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let webhook_response = enqueue_kling_v2p1_pro_image_to_video_webhook(args)
@@ -39,6 +32,5 @@ pub async fn execute_fal_kling_2_1_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -13,49 +10,40 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_
};
use fal_client::requests::webhook::video::image::enqueue_kling_v2p5_turbo_pro_image_to_video_webhook::{
enqueue_kling_v2p5_turbo_pro_image_to_video_webhook, EnqueueKlingV2p5TurboProImageToVideoArgs,
EnqueueKlingV2p5TurboProImageToVideoRequest, EnqueueKlingV2p5TurboProImageToVideoDurationSeconds,
EnqueueKlingV2p5TurboProImageToVideoDurationSeconds,
};
use fal_client::requests::webhook::video::text::enqueue_kling_v2p5_turbo_pro_text_to_video_webhook::{
enqueue_kling_v2p5_turbo_pro_text_to_video_webhook, EnqueueKlingV2p5TurboProTextToVideoArgs,
EnqueueKlingV2p5TurboProTextToVideoRequest, EnqueueKlingV2p5TurboProTextToVideoAspectRatio,
EnqueueKlingV2p5TurboProTextToVideoDurationSeconds,
EnqueueKlingV2p5TurboProTextToVideoAspectRatio, EnqueueKlingV2p5TurboProTextToVideoDurationSeconds,
};
pub async fn execute_fal_kling_2_5_turbo_pro(
plan: &PlanFalKling2p5TurboPro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalKling2p5TurboProMode::TextToVideo => {
let request = EnqueueKlingV2p5TurboProTextToVideoRequest {
let args = EnqueueKlingV2p5TurboProTextToVideoArgs {
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p5TurboProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_v2p5_turbo_pro_text_to_video_webhook(args).await, outbound)
enqueue_kling_v2p5_turbo_pro_text_to_video_webhook(args).await
}
FalKling2p5TurboProMode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueKlingV2p5TurboProImageToVideoRequest {
let args = EnqueueKlingV2p5TurboProImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
tail_image_url: end_image_url.clone(),
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration.map(to_i2v_duration),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p5TurboProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_v2p5_turbo_pro_image_to_video_webhook(args).await, outbound)
enqueue_kling_v2p5_turbo_pro_image_to_video_webhook(args).await
}
};
@@ -65,7 +53,6 @@ pub async fn execute_fal_kling_2_5_turbo_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -12,50 +9,41 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_
};
use fal_client::requests::webhook::video::image::enqueue_kling_v2p6_pro_image_to_video_webhook::{
enqueue_kling_v2p6_pro_image_to_video_webhook, EnqueueKlingV2p6ProImageToVideoArgs,
EnqueueKlingV2p6ProImageToVideoRequest, EnqueueKlingV2p6ProImageToVideoDurationSeconds,
EnqueueKlingV2p6ProImageToVideoDurationSeconds,
};
use fal_client::requests::webhook::video::text::enqueue_kling_v2p6_pro_text_to_video_webhook::{
enqueue_kling_v2p6_pro_text_to_video_webhook, EnqueueKlingV2p6ProTextToVideoArgs,
EnqueueKlingV2p6ProTextToVideoRequest, EnqueueKlingV2p6ProTextToVideoAspectRatio,
EnqueueKlingV2p6ProTextToVideoDurationSeconds,
EnqueueKlingV2p6ProTextToVideoAspectRatio, EnqueueKlingV2p6ProTextToVideoDurationSeconds,
};
pub async fn execute_fal_kling_2_6_pro(
plan: &PlanFalKling2p6Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalKling2p6ProMode::TextToVideo => {
let request = EnqueueKlingV2p6ProTextToVideoRequest {
let args = EnqueueKlingV2p6ProTextToVideoArgs {
prompt: plan.prompt.clone(),
generate_audio: plan.generate_audio,
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p6ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_v2p6_pro_text_to_video_webhook(args).await, outbound)
enqueue_kling_v2p6_pro_text_to_video_webhook(args).await
}
FalKling2p6ProMode::ImageToVideo { image_url } => {
let request = EnqueueKlingV2p6ProImageToVideoRequest {
let args = EnqueueKlingV2p6ProImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
generate_audio: plan.generate_audio,
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration.map(to_i2v_duration),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p6ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_v2p6_pro_image_to_video_webhook(args).await, outbound)
enqueue_kling_v2p6_pro_image_to_video_webhook(args).await
}
};
@@ -65,7 +53,6 @@ pub async fn execute_fal_kling_2_6_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -12,39 +9,33 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p
};
use fal_client::requests::webhook::video::image::enqueue_kling_3p0_pro_image_to_video_webhook::{
enqueue_kling_3p0_pro_image_to_video_webhook, EnqueueKling3p0ProImageToVideoArgs,
EnqueueKling3p0ProImageToVideoRequest, EnqueueKling3p0ProImageToVideoAspectRatio,
EnqueueKling3p0ProImageToVideoDuration,
EnqueueKling3p0ProImageToVideoAspectRatio, EnqueueKling3p0ProImageToVideoDuration,
};
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_pro_text_to_video_webhook::{
enqueue_kling_3p0_pro_text_to_video_webhook, EnqueueKling3p0ProTextToVideoArgs,
EnqueueKling3p0ProTextToVideoRequest, EnqueueKling3p0ProTextToVideoAspectRatio,
EnqueueKling3p0ProTextToVideoDuration,
EnqueueKling3p0ProTextToVideoAspectRatio, EnqueueKling3p0ProTextToVideoDuration,
};
pub async fn execute_fal_kling_3p0_pro(
plan: &PlanFalKling3p0Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalKling3p0Mode::TextToVideo => {
let request = EnqueueKling3p0ProTextToVideoRequest {
let args = EnqueueKling3p0ProTextToVideoArgs {
prompt: plan.prompt.clone(),
generate_audio: plan.generate_audio,
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
shot_type: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_pro_text_to_video_webhook(args).await, outbound)
enqueue_kling_3p0_pro_text_to_video_webhook(args).await
}
FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueKling3p0ProImageToVideoRequest {
let args = EnqueueKling3p0ProImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
end_image_url: end_image_url.clone(),
@@ -53,14 +44,10 @@ pub async fn execute_fal_kling_3p0_pro(
duration: plan.duration.map(to_i2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
shot_type: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_pro_image_to_video_webhook(args).await, outbound)
enqueue_kling_3p0_pro_image_to_video_webhook(args).await
}
};
@@ -70,7 +57,6 @@ pub async fn execute_fal_kling_3p0_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -13,39 +10,33 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_standard::PlanFalKling3p0Standard;
use fal_client::requests::webhook::video::image::enqueue_kling_3p0_standard_image_to_video_webhook::{
enqueue_kling_3p0_standard_image_to_video_webhook, EnqueueKling3p0StandardImageToVideoArgs,
EnqueueKling3p0StandardImageToVideoRequest, EnqueueKling3p0StandardImageToVideoAspectRatio,
EnqueueKling3p0StandardImageToVideoDuration,
EnqueueKling3p0StandardImageToVideoAspectRatio, EnqueueKling3p0StandardImageToVideoDuration,
};
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_standard_text_to_video_webhook::{
enqueue_kling_3p0_standard_text_to_video_webhook, EnqueueKling3p0StandardTextToVideoArgs,
EnqueueKling3p0StandardTextToVideoRequest, EnqueueKling3p0StandardTextToVideoAspectRatio,
EnqueueKling3p0StandardTextToVideoDuration,
EnqueueKling3p0StandardTextToVideoAspectRatio, EnqueueKling3p0StandardTextToVideoDuration,
};
pub async fn execute_fal_kling_3p0_standard(
plan: &PlanFalKling3p0Standard,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalKling3p0Mode::TextToVideo => {
let request = EnqueueKling3p0StandardTextToVideoRequest {
let args = EnqueueKling3p0StandardTextToVideoArgs {
prompt: plan.prompt.clone(),
generate_audio: plan.generate_audio,
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
shot_type: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0StandardTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_standard_text_to_video_webhook(args).await, outbound)
enqueue_kling_3p0_standard_text_to_video_webhook(args).await
}
FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueKling3p0StandardImageToVideoRequest {
let args = EnqueueKling3p0StandardImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
end_image_url: end_image_url.clone(),
@@ -54,14 +45,10 @@ pub async fn execute_fal_kling_3p0_standard(
duration: plan.duration.map(to_i2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
shot_type: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0StandardImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_standard_image_to_video_webhook(args).await, outbound)
enqueue_kling_3p0_standard_image_to_video_webhook(args).await
}
};
@@ -71,7 +58,6 @@ pub async fn execute_fal_kling_3p0_standard(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,28 +6,24 @@ use crate::generate::generate_video::generate_video_response::{
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1_0_lite::PlanFalSeedance10Lite;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{
enqueue_seedance_1_lite_image_to_video_webhook, Seedance1LiteArgs, Seedance1LiteRequest,
enqueue_seedance_1_lite_image_to_video_webhook, Seedance1LiteArgs,
};
pub async fn execute_fal_seedance_1_0_lite(
plan: &PlanFalSeedance10Lite,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Seedance1LiteRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
let end_frame: Option<&str> = plan.end_image_url.as_deref();
let args = Seedance1LiteArgs {
image_url: plan.image_url.as_str(),
end_frame_image_url: end_frame,
prompt: plan.prompt.as_str(),
duration: plan.duration,
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
camera_fixed: false,
seed: None,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Seedance1LiteArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
@@ -42,6 +35,5 @@ pub async fn execute_fal_seedance_1_0_lite(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -14,37 +11,33 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance
use fal_client::requests::webhook::video::image::enqueue_seedance_1p5_pro_image_to_video_webhook::{
enqueue_seedance_1p5_pro_image_to_video_webhook, EnqueueSeedance1p5ProImageToVideoArgs,
EnqueueSeedance1p5ProImageToVideoAspectRatio, EnqueueSeedance1p5ProImageToVideoDuration,
EnqueueSeedance1p5ProImageToVideoRequest, EnqueueSeedance1p5ProImageToVideoResolution,
EnqueueSeedance1p5ProImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
enqueue_seedance_1p5_pro_text_to_video_webhook, EnqueueSeedance1p5ProTextToVideoArgs,
EnqueueSeedance1p5ProTextToVideoRequest, EnqueueSeedance1p5ProTextToVideoAspectRatio,
EnqueueSeedance1p5ProTextToVideoDuration, EnqueueSeedance1p5ProTextToVideoResolution,
EnqueueSeedance1p5ProTextToVideoAspectRatio, EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoResolution,
};
pub async fn execute_fal_seedance_1p5_pro(
plan: &PlanFalSeedance1p5Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalSeedance1p5ProMode::TextToVideo => {
let request = EnqueueSeedance1p5ProTextToVideoRequest {
let args = EnqueueSeedance1p5ProTextToVideoArgs {
prompt: plan.prompt.clone(),
resolution: plan.resolution.map(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSeedance1p5ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_seedance_1p5_pro_text_to_video_webhook(args).await, outbound)
enqueue_seedance_1p5_pro_text_to_video_webhook(args).await
}
FalSeedance1p5ProMode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueSeedance1p5ProImageToVideoRequest {
let args = EnqueueSeedance1p5ProImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
end_image_url: end_image_url.clone(),
@@ -52,14 +45,10 @@ pub async fn execute_fal_seedance_1p5_pro(
duration: plan.duration.map(to_i2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSeedance1p5ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_seedance_1p5_pro_image_to_video_webhook(args).await, outbound)
enqueue_seedance_1p5_pro_image_to_video_webhook(args).await
}
};
@@ -69,7 +58,6 @@ pub async fn execute_fal_seedance_1p5_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -13,50 +10,42 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2::
use fal_client::requests::webhook::video::image::enqueue_sora_2_image_to_video_webhook::{
enqueue_sora_2_image_to_video_webhook, EnqueueSora2ImageToVideoArgs,
EnqueueSora2ImageToVideoAspectRatio, EnqueueSora2ImageToVideoDurationSeconds,
EnqueueSora2ImageToVideoRequest, EnqueueSora2ImageToVideoResolution,
EnqueueSora2ImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_sora_2_text_to_video_webhook::{
enqueue_sora_2_text_to_video_webhook, EnqueueSora2TextToVideoArgs,
EnqueueSora2TextToVideoRequest, EnqueueSora2TextToVideoAspectRatio,
EnqueueSora2TextToVideoDurationSeconds, EnqueueSora2TextToVideoResolution,
EnqueueSora2TextToVideoAspectRatio, EnqueueSora2TextToVideoDurationSeconds,
EnqueueSora2TextToVideoResolution,
};
pub async fn execute_fal_sora_2(
plan: &PlanFalSora2,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalSora2Mode::TextToVideo => {
// Text-to-video does not support Auto aspect ratio or Auto resolution.
let request = EnqueueSora2TextToVideoRequest {
let args = EnqueueSora2TextToVideoArgs {
prompt: plan.prompt.clone(),
resolution: plan.resolution.and_then(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.and_then(to_t2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2TextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_text_to_video_webhook(args).await, outbound)
enqueue_sora_2_text_to_video_webhook(args).await
}
FalSora2Mode::ImageToVideo { image_url } => {
let request = EnqueueSora2ImageToVideoRequest {
let args = EnqueueSora2ImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
duration: plan.duration.map(to_i2v_duration),
resolution: plan.resolution.map(to_i2v_resolution),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2ImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_image_to_video_webhook(args).await, outbound)
enqueue_sora_2_image_to_video_webhook(args).await
}
};
@@ -66,7 +55,6 @@ pub async fn execute_fal_sora_2(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -14,50 +11,42 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2_p
use fal_client::requests::webhook::video::image::enqueue_sora_2_pro_image_to_video_webhook::{
enqueue_sora_2_pro_image_to_video_webhook, EnqueueSora2ProImageToVideoArgs,
EnqueueSora2ProImageToVideoAspectRatio, EnqueueSora2ProImageToVideoDurationSeconds,
EnqueueSora2ProImageToVideoRequest, EnqueueSora2ProImageToVideoResolution,
EnqueueSora2ProImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{
enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs,
EnqueueSora2ProTextToVideoRequest, EnqueueSora2ProTextToVideoAspectRatio,
EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution,
EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds,
EnqueueSora2ProTextToVideoResolution,
};
pub async fn execute_fal_sora_2_pro(
plan: &PlanFalSora2Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalSora2ProMode::TextToVideo => {
// Text-to-video does not support Auto aspect ratio or Auto resolution.
let request = EnqueueSora2ProTextToVideoRequest {
let args = EnqueueSora2ProTextToVideoArgs {
prompt: plan.prompt.clone(),
resolution: plan.resolution.and_then(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.and_then(to_t2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_pro_text_to_video_webhook(args).await, outbound)
enqueue_sora_2_pro_text_to_video_webhook(args).await
}
FalSora2ProMode::ImageToVideo { image_url } => {
let request = EnqueueSora2ProImageToVideoRequest {
let args = EnqueueSora2ProImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
duration: plan.duration.map(to_i2v_duration),
resolution: plan.resolution.map(to_i2v_resolution),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_pro_image_to_video_webhook(args).await, outbound)
enqueue_sora_2_pro_image_to_video_webhook(args).await
}
};
@@ -67,7 +56,6 @@ pub async fn execute_fal_sora_2_pro(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -11,59 +8,48 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_2::{
FalVeo2Mode, PlanFalVeo2,
};
use fal_client::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::{
enqueue_veo_2_image_to_video_webhook, Veo2Args, Veo2AspectRatio, Veo2Request,
enqueue_veo_2_image_to_video_webhook, Veo2Args, Veo2AspectRatio,
};
use fal_client::requests::webhook::video::text::enqueue_veo_2_text_to_video_webhook::{
enqueue_veo_2_text_to_video_webhook, Veo2TextToVideoArgs, Veo2TextToVideoRequest,
enqueue_veo_2_text_to_video_webhook, Veo2TextToVideoArgs,
};
pub async fn execute_fal_veo_2(
plan: &PlanFalVeo2,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalVeo2Mode::TextToVideo => {
let request = Veo2TextToVideoRequest {
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
let args = Veo2TextToVideoArgs {
prompt: plan.prompt.as_str(),
negative_prompt: plan.negative_prompt.as_deref(),
api_key: &fal_client.api_key,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio.unwrap_or(Veo2AspectRatio::Auto),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo2TextToVideoArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_2_text_to_video_webhook(args)
enqueue_veo_2_text_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
}
FalVeo2Mode::ImageToVideo { image_url } => {
// Image-to-video does not support aspect_ratio — the output
// inherits the source image's aspect ratio.
let request = Veo2Request {
image_url: image_url.to_string(),
prompt: plan.prompt.clone(),
duration: plan.duration,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo2Args {
request,
image_url: image_url.as_str(),
prompt: plan.prompt.as_str(),
api_key: &fal_client.api_key,
duration: plan.duration,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_2_image_to_video_webhook(args)
enqueue_veo_2_image_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
}
};
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -13,67 +10,13 @@ use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3::{
};
use fal_client::requests::webhook::video::image::enqueue_veo_3_image_to_video_webhook::{
enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3I2vAspectRatio, Veo3I2vDuration,
Veo3I2vResolution, Veo3Request,
Veo3I2vResolution,
};
use fal_client::requests::webhook::video::text::enqueue_veo_3_text_to_video_webhook::{
enqueue_veo_3_text_to_video_webhook, Veo3T2vAspectRatio, Veo3T2vDuration, Veo3T2vResolution,
Veo3TextToVideoArgs, Veo3TextToVideoRequest,
Veo3TextToVideoArgs,
};
pub async fn execute_fal_veo_3(
plan: &PlanFalVeo3,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalVeo3Mode::TextToVideo => {
let request = Veo3TextToVideoRequest {
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
duration: to_t2v_duration(plan.duration),
aspect_ratio: plan.t2v_aspect_ratio.map(to_t2v_aspect_ratio).unwrap_or(Veo3T2vAspectRatio::Default),
resolution: to_t2v_resolution(plan.resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo3TextToVideoArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_3_text_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
}
FalVeo3Mode::ImageToVideo { image_url } => {
let request = Veo3Request {
image_url: image_url.to_string(),
prompt: plan.prompt.clone(),
duration: to_i2v_duration(plan.duration),
aspect_ratio: plan.i2v_aspect_ratio.map(to_i2v_aspect_ratio).unwrap_or(Veo3I2vAspectRatio::Auto),
resolution: to_i2v_resolution(plan.resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo3Args {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_3_image_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
}
};
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_i2v_duration(d: FalVeo3Duration) -> Veo3I2vDuration {
match d {
FalVeo3Duration::Default => Veo3I2vDuration::Default,
@@ -123,3 +66,46 @@ fn to_i2v_aspect_ratio(ar: FalVeo3I2vAspectRatio) -> Veo3I2vAspectRatio {
FalVeo3I2vAspectRatio::TallNineSixteen => Veo3I2vAspectRatio::TallNineSixteen,
}
}
pub async fn execute_fal_veo_3(
plan: &PlanFalVeo3,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let webhook_response = match &plan.mode {
FalVeo3Mode::TextToVideo => {
let args = Veo3TextToVideoArgs {
prompt: plan.prompt.as_str(),
negative_prompt: plan.negative_prompt.as_deref(),
api_key: &fal_client.api_key,
duration: to_t2v_duration(plan.duration),
aspect_ratio: plan.t2v_aspect_ratio.map(to_t2v_aspect_ratio).unwrap_or(Veo3T2vAspectRatio::Default),
resolution: to_t2v_resolution(plan.resolution),
generate_audio: plan.generate_audio,
webhook_url: fal_client.webhook_url.as_str(),
};
enqueue_veo_3_text_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
}
FalVeo3Mode::ImageToVideo { image_url } => {
let args = Veo3Args {
image_url: image_url.as_str(),
prompt: plan.prompt.as_str(),
duration: to_i2v_duration(plan.duration),
aspect_ratio: plan.i2v_aspect_ratio.map(to_i2v_aspect_ratio).unwrap_or(Veo3I2vAspectRatio::Auto),
resolution: to_i2v_resolution(plan.resolution),
generate_audio: plan.generate_audio,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
enqueue_veo_3_image_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
}
};
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -9,27 +6,21 @@ use crate::generate::generate_video::generate_video_response::{
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3_fast::PlanFalVeo3Fast;
use fal_client::requests::webhook::video::image::enqueue_veo_3_fast_image_to_video_webhook::{
enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs, Veo3FastRequest,
enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs,
};
pub async fn execute_fal_veo_3_fast(
plan: &PlanFalVeo3Fast,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Veo3FastRequest {
prompt: plan.prompt.clone(),
image_url: plan.start_frame_url.clone(),
let args = Veo3FastArgs {
prompt: plan.prompt.as_str(),
image_url: plan.start_frame_url.as_str(),
aspect_ratio: plan.aspect_ratio,
duration: plan.duration,
api_key: &fal_client.api_key,
resolution: plan.resolution,
generate_audio: plan.generate_audio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo3FastArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
@@ -40,6 +31,5 @@ pub async fn execute_fal_veo_3_fast(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -15,27 +12,26 @@ use fal_client::requests::webhook::video::image::enqueue_veo_3p1_first_last_fram
EnqueueVeo3p1FirstLastFrameImageToVideoArgs,
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio,
EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds,
EnqueueVeo3p1FirstLastFrameImageToVideoRequest,
EnqueueVeo3p1FirstLastFrameImageToVideoResolution,
};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_image_to_video_webhook::{
enqueue_veo_3p1_image_to_video_webhook, EnqueueVeo3p1ImageToVideoArgs,
EnqueueVeo3p1ImageToVideoAspectRatio, EnqueueVeo3p1ImageToVideoDurationSeconds,
EnqueueVeo3p1ImageToVideoRequest, EnqueueVeo3p1ImageToVideoResolution,
EnqueueVeo3p1ImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_veo_3p1_text_to_video_webhook::{
enqueue_veo_3p1_text_to_video_webhook, EnqueueVeo3p1TextToVideoArgs,
EnqueueVeo3p1TextToVideoRequest, EnqueueVeo3p1TextToVideoAspectRatio,
EnqueueVeo3p1TextToVideoDurationSeconds, EnqueueVeo3p1TextToVideoResolution,
EnqueueVeo3p1TextToVideoAspectRatio, EnqueueVeo3p1TextToVideoDurationSeconds,
EnqueueVeo3p1TextToVideoResolution,
};
pub async fn execute_fal_veo_3p1(
plan: &PlanFalVeo3p1,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
let webhook_response = match &plan.mode {
FalVeo3p1Mode::TextToVideo => {
let request = EnqueueVeo3p1TextToVideoRequest {
let args = EnqueueVeo3p1TextToVideoArgs {
prompt: plan.prompt.clone(),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
@@ -45,34 +41,26 @@ pub async fn execute_fal_veo_3p1(
negative_prompt: plan.negative_prompt.clone(),
seed: None,
auto_fix: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1TextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_text_to_video_webhook(args).await, outbound)
enqueue_veo_3p1_text_to_video_webhook(args).await
}
FalVeo3p1Mode::ImageToVideo { start_frame_url } => {
let request = EnqueueVeo3p1ImageToVideoRequest {
let args = EnqueueVeo3p1ImageToVideoArgs {
prompt: plan.prompt.clone(),
image_url: start_frame_url.clone(),
duration: plan.duration.map(to_i2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
resolution: plan.resolution.map(to_i2v_resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1ImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_image_to_video_webhook(args).await, outbound)
enqueue_veo_3p1_image_to_video_webhook(args).await
}
FalVeo3p1Mode::FirstLastFrame { first_frame_url, last_frame_url } => {
let request = EnqueueVeo3p1FirstLastFrameImageToVideoRequest {
let args = EnqueueVeo3p1FirstLastFrameImageToVideoArgs {
prompt: plan.prompt.clone(),
first_frame_url: first_frame_url.clone(),
last_frame_url: last_frame_url.clone(),
@@ -80,14 +68,10 @@ pub async fn execute_fal_veo_3p1(
aspect_ratio: plan.aspect_ratio.map(to_flf_aspect_ratio),
resolution: plan.resolution.map(to_flf_resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FirstLastFrameImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_first_last_frame_image_to_video_webhook(args).await, outbound)
enqueue_veo_3p1_first_last_frame_image_to_video_webhook(args).await
}
};
@@ -97,7 +81,6 @@ pub async fn execute_fal_veo_3p1(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
@@ -16,18 +13,17 @@ use fal_client::requests::webhook::video::image::enqueue_veo_3p1_fast_first_last
EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs,
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio,
EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds,
EnqueueVeo3p1FastFirstLastFrameImageToVideoRequest,
EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution,
};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_fast_image_to_video_webhook::{
enqueue_veo_3p1_fast_image_to_video_webhook, EnqueueVeo3p1FastImageToVideoArgs,
EnqueueVeo3p1FastImageToVideoAspectRatio, EnqueueVeo3p1FastImageToVideoDurationSeconds,
EnqueueVeo3p1FastImageToVideoRequest, EnqueueVeo3p1FastImageToVideoResolution,
EnqueueVeo3p1FastImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_veo_3p1_fast_text_to_video_webhook::{
enqueue_veo_3p1_fast_text_to_video_webhook, EnqueueVeo3p1FastTextToVideoArgs,
EnqueueVeo3p1FastTextToVideoRequest, EnqueueVeo3p1FastTextToVideoAspectRatio,
EnqueueVeo3p1FastTextToVideoDurationSeconds, EnqueueVeo3p1FastTextToVideoResolution,
EnqueueVeo3p1FastTextToVideoAspectRatio, EnqueueVeo3p1FastTextToVideoDurationSeconds,
EnqueueVeo3p1FastTextToVideoResolution,
};
pub async fn execute_fal_veo_3p1_fast(
@@ -36,9 +32,9 @@ pub async fn execute_fal_veo_3p1_fast(
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let inner = &plan.inner;
let (webhook_response, outbound_request) = match &inner.mode {
let webhook_response = match &inner.mode {
FalVeo3p1Mode::TextToVideo => {
let request = EnqueueVeo3p1FastTextToVideoRequest {
let args = EnqueueVeo3p1FastTextToVideoArgs {
prompt: inner.prompt.clone(),
duration: inner.duration.map(to_t2v_duration),
aspect_ratio: inner.aspect_ratio.map(to_t2v_aspect_ratio),
@@ -48,34 +44,26 @@ pub async fn execute_fal_veo_3p1_fast(
negative_prompt: inner.negative_prompt.clone(),
seed: None,
auto_fix: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FastTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_fast_text_to_video_webhook(args).await, outbound)
enqueue_veo_3p1_fast_text_to_video_webhook(args).await
}
FalVeo3p1Mode::ImageToVideo { start_frame_url } => {
let request = EnqueueVeo3p1FastImageToVideoRequest {
let args = EnqueueVeo3p1FastImageToVideoArgs {
prompt: inner.prompt.clone(),
image_url: start_frame_url.clone(),
duration: inner.duration.map(to_i2v_duration),
aspect_ratio: inner.aspect_ratio.map(to_i2v_aspect_ratio),
resolution: inner.resolution.map(to_i2v_resolution),
generate_audio: inner.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FastImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_fast_image_to_video_webhook(args).await, outbound)
enqueue_veo_3p1_fast_image_to_video_webhook(args).await
}
FalVeo3p1Mode::FirstLastFrame { first_frame_url, last_frame_url } => {
let request = EnqueueVeo3p1FastFirstLastFrameImageToVideoRequest {
let args = EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs {
prompt: inner.prompt.clone(),
first_frame_url: first_frame_url.clone(),
last_frame_url: last_frame_url.clone(),
@@ -83,14 +71,10 @@ pub async fn execute_fal_veo_3p1_fast(
aspect_ratio: inner.aspect_ratio.map(to_flf_aspect_ratio),
resolution: inner.resolution.map(to_flf_resolution),
generate_audio: inner.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook(args).await, outbound)
enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook(args).await
}
};
@@ -100,7 +84,6 @@ pub async fn execute_fal_veo_3p1_fast(
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,6 +1,3 @@
use std::fmt::Debug;
use std::sync::Arc;
use muapi_client::api_types::request_id::RequestId as MuapiRequestId;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
@@ -27,11 +24,6 @@ pub struct Seedance2proVideoResponsePayload {
pub struct FalVideoResponsePayload {
pub request_id: Option<String>,
pub gateway_request_id: Option<String>,
/// The outbound request that was sent to Fal.
/// Stored as a trait object so any Request type can be captured.
/// Use `format!("{:?}", ...)` or `format!("{:#?}", ...)` to print.
pub maybe_outbound_request: Option<Arc<dyn Debug + Send + Sync>>,
}
#[derive(Clone, Debug)]
@@ -1,24 +0,0 @@
# beeble_client
HTTP client for the Beeble SwitchX compositing API.
## API Reference
- Docs: https://developer.beeble.ai/docs
- OpenAPI: https://api.beeble.ai/developer-api-docs/openapi.json
- Auth: `x-api-key` header
## Endpoints
- `create_upload_url` — POST /v1/uploads — get a presigned PUT URL for uploading media
- `start_generation` — POST /v1/switchx/generations — start an image or video compositing job
- `get_job_status` — GET /v1/switchx/generations/{job_id} — poll job status until completed/failed
## Testing
```bash
cargo test -p beeble_client # run non-ignored tests (API shape only)
cargo test -p beeble_client -- --ignored # run live API tests (requires key, incurs costs)
```
Live tests require: `/Users/bt/Artcraft/credentials/beeble_api_key.txt`
@@ -1,31 +0,0 @@
[package]
name = "beeble_client"
edition = "2024"
version = "0.0.1"
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "beeble_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
errors.workspace = true
# External
log.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
[dev-dependencies]
anyhow.workspace = true
test_data.workspace = true
tokio = { version = "1", features = ["full"] }
@@ -1,24 +0,0 @@
use std::fmt;
/// Holds the API key for authenticating with the Beeble API.
/// Sent as the `x-api-key` header.
#[derive(Clone)]
pub struct BeebleApiKey {
pub(crate) api_key: String,
}
impl BeebleApiKey {
pub fn new(api_key: String) -> Self {
Self { api_key }
}
}
impl fmt::Debug for BeebleApiKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.api_key.len() > 0 {
write!(f, "BeebleApiKey(***)")
} else {
write!(f, "BeebleApiKey(EMPTY)")
}
}
}
@@ -1 +0,0 @@
pub mod beeble_api_key;
@@ -1,26 +0,0 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub enum BeebleClientError {
/// No API key is present.
NoApiKeyPresent,
/// An error was encountered in building the reqwest client.
ReqwestClientError(reqwest::Error),
/// A serialization error with the request.
SerializationError(serde_json::Error),
}
impl Error for BeebleClientError {}
impl Display for BeebleClientError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoApiKeyPresent => write!(f, "No API key present."),
Self::ReqwestClientError(err) => write!(f, "Reqwest client error: {}", err),
Self::SerializationError(err) => write!(f, "Serialization error: {}", err),
}
}
}
@@ -1,41 +0,0 @@
use crate::error::beeble_client_error::BeebleClientError;
use crate::error::beeble_generic_api_error::BeebleGenericApiError;
use crate::error::beeble_specific_api_error::BeebleSpecificApiError;
use std::error::Error;
#[derive(Debug)]
pub enum BeebleError {
Client(BeebleClientError),
ApiSpecific(BeebleSpecificApiError),
ApiGeneric(BeebleGenericApiError),
}
impl Error for BeebleError {}
impl std::fmt::Display for BeebleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Client(e) => write!(f, "BeebleClientError: {:?}", e),
Self::ApiSpecific(e) => write!(f, "BeebleSpecificApiError: {:?}", e),
Self::ApiGeneric(e) => write!(f, "BeebleGenericApiError: {:?}", e),
}
}
}
impl From<BeebleClientError> for BeebleError {
fn from(error: BeebleClientError) -> Self {
Self::Client(error)
}
}
impl From<BeebleSpecificApiError> for BeebleError {
fn from(error: BeebleSpecificApiError) -> Self {
Self::ApiSpecific(error)
}
}
impl From<BeebleGenericApiError> for BeebleError {
fn from(error: BeebleGenericApiError) -> Self {
Self::ApiGeneric(error)
}
}
@@ -1,34 +0,0 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
/// Catch-all for API errors that don't fit a specific variant.
#[derive(Debug)]
pub enum BeebleGenericApiError {
/// An HTTP transport error from reqwest.
ReqwestError(reqwest::Error),
/// Failed to parse the response body as JSON.
SerdeResponseParseError(serde_json::Error, String),
/// The API returned a non-success status code.
UncategorizedBadResponseWithStatusAndBody {
status_code: reqwest::StatusCode,
body: String,
},
}
impl Error for BeebleGenericApiError {}
impl Display for BeebleGenericApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::ReqwestError(err) => write!(f, "Beeble reqwest error: {}", err),
Self::SerdeResponseParseError(err, body) => {
write!(f, "Beeble response parse error: {} (body: {})", err, body)
}
Self::UncategorizedBadResponseWithStatusAndBody { status_code, body } => {
write!(f, "Beeble API error: status={}, body={}", status_code, body)
}
}
}
}
@@ -1,54 +0,0 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
/// Well-known API errors from the Beeble service.
#[derive(Debug)]
pub enum BeebleSpecificApiError {
/// 401 Unauthorized — invalid or missing API key.
Unauthorized,
/// 402 Payment Required — insufficient credits.
InsufficientCredits,
/// 409 Conflict — idempotency key reused with different parameters.
IdempotencyConflict,
/// 429 Too Many Requests — rate limit exceeded.
RateLimited,
/// The generation job failed with a reported error message.
GenerationFailed(String),
/// 400 Bad Request — the callback_url is not a valid publicly-reachable HTTPS URL.
BadWebhookUrl {
message: String,
webhook_url: String,
},
/// 400 Bad Request — the video has too many frames.
VideoHasTooManyFrames {
max_frames: u64,
detected_frames: u64,
message: String,
},
}
impl Error for BeebleSpecificApiError {}
impl Display for BeebleSpecificApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unauthorized => write!(f, "Beeble API: Unauthorized (invalid or missing API key)"),
Self::InsufficientCredits => write!(f, "Beeble API: Insufficient credits"),
Self::IdempotencyConflict => write!(f, "Beeble API: Idempotency conflict"),
Self::RateLimited => write!(f, "Beeble API: Rate limited"),
Self::GenerationFailed(msg) => write!(f, "Beeble API: Generation failed: {}", msg),
Self::BadWebhookUrl { message, webhook_url } => {
write!(f, "Beeble API: Bad webhook URL '{}': {}", webhook_url, message)
}
Self::VideoHasTooManyFrames { max_frames, detected_frames, message } => {
write!(f, "Beeble API: Video has too many frames ({} detected, max {}): {}", detected_frames, max_frames, message)
}
}
}
}
@@ -1,4 +0,0 @@
pub mod beeble_client_error;
pub mod beeble_error;
pub mod beeble_generic_api_error;
pub mod beeble_specific_api_error;
@@ -1,8 +0,0 @@
#[cfg(test)]
pub(crate) mod test_utils;
// Public API
pub mod creds;
pub mod error;
pub mod requests;
pub mod webhook_api;
@@ -1,126 +0,0 @@
use log::info;
use crate::creds::beeble_api_key::BeebleApiKey;
use crate::error::beeble_client_error::BeebleClientError;
use crate::error::beeble_error::BeebleError;
use crate::error::beeble_generic_api_error::BeebleGenericApiError;
use crate::error::beeble_specific_api_error::BeebleSpecificApiError;
use crate::requests::create_upload_url::request_types::*;
const BEEBLE_API_BASE_URL: &str = "https://api.beeble.ai/v1";
// ── Public args ──
pub struct CreateUploadUrlArgs {
pub api_key: BeebleApiKey,
pub filename: String,
}
// ── Public response ──
#[derive(Debug, Clone)]
pub struct CreateUploadUrlSuccess {
/// Upload ID (upload_...).
pub id: String,
/// Presigned PUT URL for uploading. Expires after 1 hour.
pub upload_url: String,
/// beeble:// URI to reference this upload in generation calls.
pub beeble_uri: String,
}
// ── Implementation ──
/// Create a presigned upload URL for a media file.
///
/// After receiving the URL, PUT your file bytes directly to `upload_url`.
/// Then use `beeble_uri` as the source_uri, reference_image_uri, or alpha_uri
/// in generation calls.
pub async fn create_upload_url(args: CreateUploadUrlArgs) -> Result<CreateUploadUrlSuccess, BeebleError> {
let url = format!("{}/uploads", BEEBLE_API_BASE_URL);
info!("Creating Beeble upload URL for filename: {}", args.filename);
let request_body = CreateUploadUrlRequestBody {
filename: args.filename,
};
let client = reqwest::Client::builder()
.build()
.map_err(|err| BeebleClientError::ReqwestClientError(err))?;
let response = client.post(&url)
.header("x-api-key", &args.api_key.api_key)
.header("Content-Type", "application/json")
.json(&request_body)
.send()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
let status = response.status();
let response_body = response.text()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
info!("Beeble create upload URL response: status={}", status);
if status == reqwest::StatusCode::UNAUTHORIZED {
return Err(BeebleSpecificApiError::Unauthorized.into());
}
if !status.is_success() {
return Err(BeebleGenericApiError::UncategorizedBadResponseWithStatusAndBody {
status_code: status,
body: response_body,
}.into());
}
let parsed: CreateUploadUrlResponseBody = serde_json::from_str(&response_body)
.map_err(|err| BeebleGenericApiError::SerdeResponseParseError(err, response_body.clone()))?;
Ok(CreateUploadUrlSuccess {
id: parsed.id,
upload_url: parsed.upload_url,
beeble_uri: parsed.beeble_uri,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::get_test_api_key::get_test_api_key;
use errors::AnyhowResult;
#[test]
fn request_body_serializes() {
let body = CreateUploadUrlRequestBody {
filename: "test_video.mp4".to_string(),
};
let json = serde_json::to_string(&body).unwrap();
assert!(json.contains("test_video.mp4"));
}
#[test]
fn response_body_deserializes() {
let json = r#"{"id":"upload_abc123","upload_url":"https://example.com/put","beeble_uri":"beeble://upload_abc123/video.mp4"}"#;
let parsed: CreateUploadUrlResponseBody = serde_json::from_str(json).unwrap();
assert_eq!(parsed.id, "upload_abc123");
assert_eq!(parsed.beeble_uri, "beeble://upload_abc123/video.mp4");
}
#[tokio::test]
#[ignore] // manually test — requires real API key
async fn test_create_upload_url() -> AnyhowResult<()> {
let api_key = get_test_api_key()?;
let result = create_upload_url(CreateUploadUrlArgs {
api_key,
filename: "test_image.png".to_string(),
}).await?;
println!("Upload ID: {}", result.id);
println!("Upload URL: {}", result.upload_url);
println!("Beeble URI: {}", result.beeble_uri);
assert!(result.id.starts_with("upload_"));
assert!(result.beeble_uri.starts_with("beeble://"));
Ok(())
}
}
@@ -1,3 +0,0 @@
pub mod create_upload_url;
mod request_types;
pub mod upload_bytes;
@@ -1,13 +0,0 @@
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct CreateUploadUrlRequestBody {
pub filename: String,
}
#[derive(Deserialize, Debug)]
pub struct CreateUploadUrlResponseBody {
pub id: String,
pub upload_url: String,
pub beeble_uri: String,
}
@@ -1,50 +0,0 @@
use log::info;
use crate::creds::beeble_api_key::BeebleApiKey;
use crate::error::beeble_client_error::BeebleClientError;
use crate::error::beeble_error::BeebleError;
use crate::error::beeble_generic_api_error::BeebleGenericApiError;
use crate::requests::create_upload_url::create_upload_url::{
create_upload_url, CreateUploadUrlArgs, CreateUploadUrlSuccess,
};
/// Upload bytes to Beeble in one call (create presigned URL + PUT bytes).
///
/// Returns the upload success with the `beeble_uri` to use in generation calls.
pub async fn upload_bytes_to_beeble(
api_key: &BeebleApiKey,
filename: &str,
content_type: &str,
bytes: Vec<u8>,
) -> Result<CreateUploadUrlSuccess, BeebleError> {
info!("Uploading {} bytes to Beeble as {}", bytes.len(), filename);
// 1. Get presigned URL.
let upload = create_upload_url(CreateUploadUrlArgs {
api_key: api_key.clone(),
filename: filename.to_string(),
}).await?;
info!("Beeble upload URL obtained: {}", upload.beeble_uri);
// 2. PUT bytes to presigned URL.
let client = reqwest::Client::new();
let put_response = client.put(&upload.upload_url)
.header("Content-Type", content_type)
.body(bytes)
.send()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
let status = put_response.status();
if !status.is_success() {
let body = put_response.text().await.unwrap_or_default();
return Err(BeebleGenericApiError::UncategorizedBadResponseWithStatusAndBody {
status_code: status,
body,
}.into());
}
info!("Beeble upload PUT succeeded: {}", upload.beeble_uri);
Ok(upload)
}
@@ -1,104 +0,0 @@
use log::info;
use crate::creds::beeble_api_key::BeebleApiKey;
use crate::error::beeble_client_error::BeebleClientError;
use crate::error::beeble_error::BeebleError;
use crate::error::beeble_generic_api_error::BeebleGenericApiError;
use crate::error::beeble_specific_api_error::BeebleSpecificApiError;
use crate::requests::start_generation::request_types::GenerationJobResponseBody;
use crate::requests::start_generation::start_generation::{map_job_response, StartGenerationSuccess};
const BEEBLE_API_BASE_URL: &str = "https://api.beeble.ai/v1";
// ── Public args ──
pub struct GetJobStatusArgs {
pub api_key: BeebleApiKey,
/// The job ID (swx_...) to poll.
pub job_id: String,
}
// ── Implementation ──
/// Poll the status of a SwitchX generation job.
///
/// Returns the same response shape as start_generation.
/// Poll until `status` is "completed" or "failed".
pub async fn get_job_status(args: GetJobStatusArgs) -> Result<StartGenerationSuccess, BeebleError> {
let url = format!("{}/switchx/generations/{}", BEEBLE_API_BASE_URL, args.job_id);
info!("Polling Beeble job status: job_id={}", args.job_id);
let client = reqwest::Client::builder()
.build()
.map_err(|err| BeebleClientError::ReqwestClientError(err))?;
let response = client.get(&url)
.header("x-api-key", &args.api_key.api_key)
.send()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
let status = response.status();
let response_body = response.text()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
info!("Beeble get job status response: status={}", status);
match status.as_u16() {
401 => return Err(BeebleSpecificApiError::Unauthorized.into()),
429 => return Err(BeebleSpecificApiError::RateLimited.into()),
_ if !status.is_success() => {
return Err(BeebleGenericApiError::UncategorizedBadResponseWithStatusAndBody {
status_code: status,
body: response_body,
}.into());
}
_ => {}
}
let parsed: GenerationJobResponseBody = serde_json::from_str(&response_body)
.map_err(|err| BeebleGenericApiError::SerdeResponseParseError(err, response_body.clone()))?;
Ok(map_job_response(parsed))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::get_test_api_key::get_test_api_key;
//const TEST_JOB_ID: &str = "swx_uZAcwMjRZpDesqfp3pKkF"; // Production
//const TEST_JOB_ID: &str = "swx_Ontb6KfbfdY24Cz8DZ9c8"; // Dev
//const TEST_JOB_ID: &str = "swx_k5SmgdFcesuE05BE8VqFb"; // Dev
//const TEST_JOB_ID: &str = "swx_snkg7Et8EOvWaQ0QgoZlZ"; // Dev
const TEST_JOB_ID: &str = "swx_a1GBhEHwIfhzXfWEkEbcb"; // Dev
//const TEST_JOB_ID: &str = "swx_9Cbr7coiW9ZOayIrKGe0v"; // Prod
#[tokio::test]
#[ignore] // manually test — requires real API key
async fn test_poll_known_job() -> errors::AnyhowResult<()> {
let api_key = get_test_api_key()?;
let result = get_job_status(GetJobStatusArgs {
api_key,
job_id: TEST_JOB_ID.to_string(),
}).await?;
println!("Job ID: {}", result.id);
println!("Status: {}", result.status);
println!("Progress: {:?}", result.progress);
println!("Generation type: {:?}", result.generation_type);
println!("Alpha mode: {:?}", result.alpha_mode);
println!("Error: {:?}", result.error);
println!("Created at: {:?}", result.created_at);
if let Some(output) = &result.output {
println!("Render URL: {:?}", output.render);
println!("Source URL: {:?}", output.source);
println!("Alpha URL: {:?}", output.alpha);
}
assert_eq!(result.id, TEST_JOB_ID);
Ok(())
}
}
@@ -1 +0,0 @@
pub mod get_job_status;
@@ -1,3 +0,0 @@
pub mod create_upload_url;
pub mod get_job_status;
pub mod start_generation;
@@ -1,72 +0,0 @@
use reqwest::StatusCode;
use crate::error::beeble_error::BeebleError;
use crate::error::beeble_generic_api_error::BeebleGenericApiError;
use crate::error::beeble_specific_api_error::BeebleSpecificApiError;
/// Additional context for error handling that depends on the original request.
pub struct ErrorContext <'a> {
pub maybe_callback_url: Option<&'a str>,
}
/// Classify a non-success Beeble API response into the appropriate error.
///
/// Only call this when `!status.is_success()`. Always returns `Err`.
pub fn handle_error_response<T>(
status: StatusCode,
response_body: &str,
context: &ErrorContext,
) -> Result<T, BeebleError> {
match status.as_u16() {
401 => Err(BeebleSpecificApiError::Unauthorized.into()),
402 => Err(BeebleSpecificApiError::InsufficientCredits.into()),
400 if response_body.contains("INVALID_CALLBACK_URL") => {
let message = extract_error_message(response_body)
.unwrap_or_else(|| "callback_url must be a valid, publicly-reachable HTTPS URL".to_string());
Err(BeebleSpecificApiError::BadWebhookUrl {
message,
webhook_url: context.maybe_callback_url.unwrap_or_default().to_string(),
}.into())
}
400 if response_body.contains("VIDEO_TOO_MANY_FRAMES") => {
let message = extract_error_message(response_body)
.unwrap_or_else(|| "Video has too many frames".to_string());
let (detected, max) = extract_frame_counts(&message);
Err(BeebleSpecificApiError::VideoHasTooManyFrames {
max_frames: max,
detected_frames: detected,
message,
}.into())
}
409 => Err(BeebleSpecificApiError::IdempotencyConflict.into()),
429 => Err(BeebleSpecificApiError::RateLimited.into()),
_ => {
Err(BeebleGenericApiError::UncategorizedBadResponseWithStatusAndBody {
status_code: status,
body: response_body.to_string(),
}.into())
}
}
}
/// Try to extract the "message" field from a Beeble error response body.
/// Beeble errors look like: `{"error":{"message":"...","code":"..."}}`
fn extract_error_message(body: &str) -> Option<String> {
let parsed: serde_json::Value = serde_json::from_str(body).ok()?;
parsed.get("error")?.get("message")?.as_str().map(|s| s.to_string())
}
/// Parse frame counts from a message like "Video has 419 frames, which exceeds the maximum of 240."
fn extract_frame_counts(message: &str) -> (u64, u64) {
let numbers: Vec<u64> = message
.split(|c: char| !c.is_ascii_digit())
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse().ok())
.collect();
match numbers.as_slice() {
[detected, max, ..] => (*detected, *max),
[detected] => (*detected, 0),
_ => (0, 0),
}
}
@@ -1,3 +0,0 @@
pub(crate) mod handle_error_response;
pub(crate) mod request_types;
pub mod start_generation;
@@ -1,55 +0,0 @@
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct StartGenerationRequestBody {
pub generation_type: String,
pub source_uri: String,
pub alpha_mode: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reference_image_uri: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alpha_uri: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_resolution: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub callback_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub idempotency_key: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GenerationJobResponseBody {
pub id: String,
pub status: String,
pub progress: Option<u8>,
pub generation_type: Option<String>,
pub alpha_mode: Option<String>,
pub output: Option<GenerationOutput>,
pub error: Option<String>,
pub created_at: Option<String>,
pub modified_at: Option<String>,
pub completed_at: Option<String>,
pub webhook: Option<WebhookStatus>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GenerationOutput {
pub render: Option<String>,
pub source: Option<String>,
pub alpha: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct WebhookStatus {
pub status: Option<String>,
pub attempts: Option<u32>,
pub last_error: Option<String>,
}
@@ -1,395 +0,0 @@
use log::info;
use crate::creds::beeble_api_key::BeebleApiKey;
use crate::error::beeble_client_error::BeebleClientError;
use crate::error::beeble_error::BeebleError;
use crate::error::beeble_generic_api_error::BeebleGenericApiError;
use crate::requests::start_generation::handle_error_response::{handle_error_response, ErrorContext};
use crate::requests::start_generation::request_types::*;
const BEEBLE_API_BASE_URL: &str = "https://api.beeble.ai/v1";
// ── Public types ──
#[derive(Clone, Debug)]
pub struct StartGenerationArgs {
pub api_key: BeebleApiKey,
pub request: StartGenerationRequest,
}
#[derive(Clone, Debug)]
pub struct StartGenerationRequest {
/// "image" or "video".
pub generation_type: BeebleGenerationType,
/// Source image/video URI. Supports beeble://, https://, data: base64.
pub source_uri: String,
/// Alpha matte mode.
pub alpha_mode: BeebleAlphaMode,
/// Text description of desired output (max 2000 chars).
pub prompt: Option<String>,
/// URI for style transfer reference image.
pub reference_image_uri: Option<String>,
/// Custom alpha matte URI. Required for Custom and Select modes.
pub alpha_uri: Option<String>,
/// Maximum output resolution: 720 or 1080. Default 1080.
pub max_resolution: Option<u16>,
/// HTTPS webhook URL for completion notification.
pub callback_url: Option<String>,
/// Safe retry key preventing duplicate jobs (max 256 chars).
pub idempotency_key: Option<String>,
}
#[derive(Copy, Clone, Debug)]
pub enum BeebleGenerationType {
Image,
Video,
}
#[derive(Copy, Clone, Debug)]
pub enum BeebleAlphaMode {
/// Automatic alpha detection.
Auto,
/// Fill the entire frame.
Fill,
/// Use a custom alpha matte (requires alpha_uri).
Custom,
/// Select regions from the source (requires alpha_uri).
Select,
}
// ── Public response ──
#[derive(Debug, Clone)]
pub struct StartGenerationSuccess {
/// Job identifier (swx_...).
pub id: String,
/// in_queue, processing, completed, or failed.
pub status: String,
/// 0-100 progress percentage.
pub progress: Option<u8>,
pub generation_type: Option<String>,
pub alpha_mode: Option<String>,
pub output: Option<GenerationOutputResult>,
pub error: Option<String>,
pub created_at: Option<String>,
}
#[derive(Debug, Clone)]
pub struct GenerationOutputResult {
/// URL to the rendered output. Expires after 72 hours.
pub render: Option<String>,
/// URL to the source. Expires after 72 hours.
pub source: Option<String>,
/// URL to the alpha matte. Expires after 72 hours.
pub alpha: Option<String>,
}
// ── Implementation ──
/// Start a SwitchX compositing generation job.
pub async fn start_generation(args: StartGenerationArgs) -> Result<StartGenerationSuccess, BeebleError> {
let url = format!("{}/switchx/generations", BEEBLE_API_BASE_URL);
let req = args.request;
info!("Starting Beeble generation: type={:?}, alpha_mode={:?}", req.generation_type, req.alpha_mode);
let request_body = StartGenerationRequestBody {
generation_type: match req.generation_type {
BeebleGenerationType::Image => "image".to_string(),
BeebleGenerationType::Video => "video".to_string(),
},
source_uri: req.source_uri,
alpha_mode: match req.alpha_mode {
BeebleAlphaMode::Auto => "auto".to_string(),
BeebleAlphaMode::Fill => "fill".to_string(),
BeebleAlphaMode::Custom => "custom".to_string(),
BeebleAlphaMode::Select => "select".to_string(),
},
prompt: req.prompt,
reference_image_uri: req.reference_image_uri,
alpha_uri: req.alpha_uri,
max_resolution: req.max_resolution,
callback_url: req.callback_url.clone(),
idempotency_key: req.idempotency_key,
};
let client = reqwest::Client::builder()
.build()
.map_err(|err| BeebleClientError::ReqwestClientError(err))?;
let response = client.post(&url)
.header("x-api-key", &args.api_key.api_key)
.header("Content-Type", "application/json")
.json(&request_body)
.send()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
let status = response.status();
let response_body = response.text()
.await
.map_err(|err| BeebleGenericApiError::ReqwestError(err))?;
info!("Beeble start generation response: status={}", status);
if !status.is_success() {
return handle_error_response(status, &response_body, &ErrorContext {
maybe_callback_url: req.callback_url.as_deref(),
});
}
let parsed: GenerationJobResponseBody = serde_json::from_str(&response_body)
.map_err(|err| BeebleGenericApiError::SerdeResponseParseError(err, response_body.clone()))?;
Ok(map_job_response(parsed))
}
pub(crate) fn map_job_response(parsed: GenerationJobResponseBody) -> StartGenerationSuccess {
StartGenerationSuccess {
id: parsed.id,
status: parsed.status,
progress: parsed.progress,
generation_type: parsed.generation_type,
alpha_mode: parsed.alpha_mode,
output: parsed.output.map(|o| GenerationOutputResult {
render: o.render,
source: o.source,
alpha: o.alpha,
}),
error: parsed.error,
created_at: parsed.created_at,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::requests::start_generation::request_types::GenerationJobResponseBody;
#[test]
fn request_body_serializes_minimal() {
let body = StartGenerationRequestBody {
generation_type: "image".to_string(),
source_uri: "beeble://upload_abc/photo.jpg".to_string(),
alpha_mode: "auto".to_string(),
prompt: None,
reference_image_uri: None,
alpha_uri: None,
max_resolution: None,
callback_url: None,
idempotency_key: None,
};
let json = serde_json::to_string(&body).unwrap();
assert!(json.contains("\"generation_type\":\"image\""));
assert!(json.contains("\"alpha_mode\":\"auto\""));
// Optional fields should not be present
assert!(!json.contains("prompt"));
assert!(!json.contains("reference_image_uri"));
}
#[test]
fn request_body_serializes_full() {
let body = StartGenerationRequestBody {
generation_type: "video".to_string(),
source_uri: "https://example.com/video.mp4".to_string(),
alpha_mode: "custom".to_string(),
prompt: Some("A sunny beach".to_string()),
reference_image_uri: Some("beeble://upload_ref/ref.jpg".to_string()),
alpha_uri: Some("beeble://upload_alpha/mask.png".to_string()),
max_resolution: Some(1080),
callback_url: Some("https://example.com/webhook".to_string()),
idempotency_key: Some("my-key-123".to_string()),
};
let json = serde_json::to_string(&body).unwrap();
assert!(json.contains("\"prompt\":\"A sunny beach\""));
assert!(json.contains("\"max_resolution\":1080"));
assert!(json.contains("\"idempotency_key\":\"my-key-123\""));
}
#[test]
fn response_body_deserializes_queued() {
let json = r#"{
"id": "swx_abc123",
"status": "in_queue",
"progress": null,
"generation_type": "image",
"alpha_mode": "auto",
"output": null,
"error": null,
"created_at": "2026-05-08T00:00:00Z",
"modified_at": "2026-05-08T00:00:00Z",
"completed_at": null,
"webhook": null
}"#;
let parsed: GenerationJobResponseBody = serde_json::from_str(json).unwrap();
assert_eq!(parsed.id, "swx_abc123");
assert_eq!(parsed.status, "in_queue");
assert!(parsed.output.is_none());
}
#[test]
fn response_body_deserializes_completed() {
let json = r#"{
"id": "swx_abc123",
"status": "completed",
"progress": 100,
"generation_type": "image",
"alpha_mode": "auto",
"output": {
"render": "https://cdn.beeble.ai/render.png",
"source": "https://cdn.beeble.ai/source.png",
"alpha": "https://cdn.beeble.ai/alpha.png"
},
"error": null,
"created_at": "2026-05-08T00:00:00Z",
"modified_at": "2026-05-08T00:00:01Z",
"completed_at": "2026-05-08T00:00:01Z",
"webhook": null
}"#;
let parsed: GenerationJobResponseBody = serde_json::from_str(json).unwrap();
assert_eq!(parsed.status, "completed");
assert_eq!(parsed.progress, Some(100));
let output = parsed.output.unwrap();
assert!(output.render.unwrap().contains("render.png"));
}
#[tokio::test]
#[ignore] // manually test — requires real API key and incurs costs
async fn test_start_image_generation() -> errors::AnyhowResult<()> {
use crate::test_utils::get_test_api_key::get_test_api_key;
let api_key = get_test_api_key()?;
let result = start_generation(StartGenerationArgs {
api_key,
request: StartGenerationRequest {
generation_type: BeebleGenerationType::Image,
source_uri: "https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0?w=800".to_string(),
alpha_mode: BeebleAlphaMode::Auto,
prompt: Some("A tropical beach at sunset".to_string()),
reference_image_uri: None,
alpha_uri: None,
max_resolution: Some(720),
callback_url: None,
idempotency_key: None,
},
}).await?;
println!("Job ID: {}", result.id);
println!("Status: {}", result.status);
println!("Progress: {:?}", result.progress);
assert!(result.id.starts_with("swx_"));
Ok(())
}
// ── Helpers for end-to-end tests ──
async fn download_bytes(url: &str) -> errors::AnyhowResult<Vec<u8>> {
let response = reqwest::get(url).await?;
let bytes = response.bytes().await?;
Ok(bytes.to_vec())
}
fn guess_content_type(filename: &str) -> &'static str {
if filename.ends_with(".mp4") { "video/mp4" }
else if filename.ends_with(".mov") { "video/quicktime" }
else if filename.ends_with(".png") { "image/png" }
else if filename.ends_with(".jpg") || filename.ends_with(".jpeg") { "image/jpeg" }
else if filename.ends_with(".webp") { "image/webp" }
else { "application/octet-stream" }
}
async fn upload_asset_bytes(
api_key: &crate::creds::beeble_api_key::BeebleApiKey,
filename: &str,
bytes: Vec<u8>,
) -> errors::AnyhowResult<crate::requests::create_upload_url::create_upload_url::CreateUploadUrlSuccess> {
use crate::requests::create_upload_url::create_upload_url::{
create_upload_url, CreateUploadUrlArgs,
};
// 1. Get presigned upload URL.
let upload = create_upload_url(CreateUploadUrlArgs {
api_key: api_key.clone(),
filename: filename.to_string(),
}).await.map_err(|e| anyhow::anyhow!("create_upload_url failed: {:?}", e))?;
println!("Upload ID: {}", upload.id);
println!("Upload URL: {}", upload.upload_url);
println!("Beeble URI: {}", upload.beeble_uri);
// 2. PUT file bytes to the presigned URL with correct Content-Type.
let content_type = guess_content_type(filename);
println!("Uploading {} bytes with Content-Type: {}", bytes.len(), content_type);
let client = reqwest::Client::new();
let put_response = client.put(&upload.upload_url)
.header("Content-Type", content_type)
.body(bytes)
.send()
.await?;
let put_status = put_response.status();
let put_body = put_response.text().await.unwrap_or_default();
println!("PUT upload status: {} body: {}", put_status, put_body);
assert!(put_status.is_success(), "PUT upload failed: {} body: {}", put_status, put_body);
Ok(upload)
}
#[tokio::test]
#[ignore] // manually test — requires real API key, downloads files, incurs costs
async fn test_end_to_end_video_generation() -> errors::AnyhowResult<()> {
use crate::test_utils::get_test_api_key::get_test_api_key;
use test_data::web::image_urls::FOREST_BACKDROP_IMAGE_URL;
use test_data::web::video_urls::ANGRY_SHIBA_VIDEO_URL;
let api_key = get_test_api_key()?;
// 1. Download the source video.
println!("Downloading video...");
let video_bytes = download_bytes(ANGRY_SHIBA_VIDEO_URL).await?;
println!("Downloaded video: {} bytes", video_bytes.len());
// 2. Download the reference image.
println!("Downloading image...");
let image_bytes = download_bytes(FOREST_BACKDROP_IMAGE_URL).await?;
println!("Downloaded image: {} bytes", image_bytes.len());
// 3. Upload the video.
println!("Uploading video...");
let video_upload = upload_asset_bytes(&api_key, "shiba.mp4", video_bytes).await?;
// 4. Upload the reference image.
println!("Uploading image...");
let image_upload = upload_asset_bytes(&api_key, "forest.jpg", image_bytes).await?;
// 5. Start generation.
println!("Starting generation...");
let result = start_generation(StartGenerationArgs {
api_key,
request: StartGenerationRequest {
generation_type: BeebleGenerationType::Video,
source_uri: video_upload.beeble_uri,
alpha_mode: BeebleAlphaMode::Auto,
prompt: Some("Place the dog in a forest setting".to_string()),
reference_image_uri: Some(image_upload.beeble_uri),
alpha_uri: None,
max_resolution: Some(720),
callback_url: None,
idempotency_key: None,
},
}).await.map_err(|e| anyhow::anyhow!("start_generation failed: {:?}", e))?;
println!("Job ID: {}", result.id);
println!("Status: {}", result.status);
println!("Progress: {:?}", result.progress);
assert!(result.id.starts_with("swx_"));
// NB: We don't poll to completion here — that would take too long for a test.
// Use get_job_status to poll manually if needed.
Ok(())
}
}
@@ -1,11 +0,0 @@
use errors::AnyhowResult;
use std::fs::read_to_string;
use crate::creds::beeble_api_key::BeebleApiKey;
#[cfg(test)]
pub fn get_test_api_key() -> AnyhowResult<BeebleApiKey> {
let api_key = read_to_string("/Users/bt/Artcraft/credentials/beeble_api_key.txt")?;
let api_key = api_key.trim().to_string();
Ok(BeebleApiKey::new(api_key))
}
@@ -1 +0,0 @@
pub mod get_test_api_key;
@@ -1,115 +0,0 @@
use serde_derive::Deserialize;
/// Raw deserialized webhook payload from Beeble.
#[derive(Deserialize, Debug, Clone)]
pub struct BeebleWebhookPayload {
pub id: String,
pub status: BeebleWebhookStatus,
pub output: Option<BeebleWebhookOutput>,
pub error: Option<String>,
pub completed_at: Option<String>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BeebleWebhookStatus {
Completed,
Failed,
/// Catch unknown variants gracefully.
#[serde(other)]
Unknown,
}
#[derive(Deserialize, Debug, Clone)]
pub struct BeebleWebhookOutput {
pub render: Option<String>,
pub source: Option<String>,
pub alpha: Option<String>,
}
pub fn parse_beeble_webhook(raw_body: &str) -> Result<BeebleWebhookPayload, serde_json::Error> {
serde_json::from_str(raw_body)
}
#[cfg(test)]
mod tests {
use super::*;
const SUCCESS_PAYLOAD: &str = r#"{
"id": "swx_abc123",
"status": "completed",
"output": {
"render": "https://cdn.beeble.ai/render.mp4",
"source": "https://cdn.beeble.ai/source.mp4",
"alpha": "https://cdn.beeble.ai/alpha.mp4"
},
"completed_at": "2026-02-23T10:05:00Z"
}"#;
const FAILURE_PAYLOAD: &str = r#"{
"id": "swx_abc123",
"status": "failed",
"completed_at": "2026-02-23T10:05:00Z",
"error": "Processing failed"
}"#;
const UNKNOWN_STATUS_PAYLOAD: &str = r#"{
"id": "swx_xyz789",
"status": "processing",
"completed_at": null
}"#;
const NULL_OUTPUT_PAYLOAD: &str = r#"{
"id": "swx_abc123",
"status": "completed",
"output": null,
"completed_at": "2026-02-23T10:05:00Z"
}"#;
mod parse_tests {
use super::*;
#[test]
fn parse_success_payload() {
let payload = parse_beeble_webhook(SUCCESS_PAYLOAD).unwrap();
assert_eq!(payload.id, "swx_abc123");
assert_eq!(payload.status, BeebleWebhookStatus::Completed);
assert!(payload.error.is_none());
let output = payload.output.unwrap();
assert_eq!(output.render.as_deref(), Some("https://cdn.beeble.ai/render.mp4"));
assert_eq!(output.source.as_deref(), Some("https://cdn.beeble.ai/source.mp4"));
assert_eq!(output.alpha.as_deref(), Some("https://cdn.beeble.ai/alpha.mp4"));
}
#[test]
fn parse_failure_payload() {
let payload = parse_beeble_webhook(FAILURE_PAYLOAD).unwrap();
assert_eq!(payload.id, "swx_abc123");
assert_eq!(payload.status, BeebleWebhookStatus::Failed);
assert_eq!(payload.error.as_deref(), Some("Processing failed"));
assert!(payload.output.is_none());
}
#[test]
fn parse_unknown_status() {
let payload = parse_beeble_webhook(UNKNOWN_STATUS_PAYLOAD).unwrap();
assert_eq!(payload.id, "swx_xyz789");
assert_eq!(payload.status, BeebleWebhookStatus::Unknown);
}
#[test]
fn parse_null_output() {
let payload = parse_beeble_webhook(NULL_OUTPUT_PAYLOAD).unwrap();
assert_eq!(payload.id, "swx_abc123");
assert_eq!(payload.status, BeebleWebhookStatus::Completed);
assert!(payload.output.is_none());
}
#[test]
fn parse_invalid_json() {
let result = parse_beeble_webhook("not valid json");
assert!(result.is_err());
}
}
}
@@ -1 +0,0 @@
pub mod beeble_webhook_payload;
@@ -7,13 +7,6 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueFlux2LoraEditImageAngleArgs<'a, R: IntoUrl> {
pub request: EnqueueFlux2LoraEditImageAngleRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueFlux2LoraEditImageAngleRequest {
// Request required
pub image_urls: Vec<String>,
@@ -28,6 +21,10 @@ pub struct EnqueueFlux2LoraEditImageAngleRequest {
pub lora_scale: Option<f64>,
pub guidance_scale: Option<f64>,
pub num_inference_steps: Option<u32>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -48,7 +45,7 @@ pub enum EnqueueFlux2LoraAngleImageSize {
LandscapeSixteenNine,
}
impl FalRequestCostCalculator for EnqueueFlux2LoraEditImageAngleRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueFlux2LoraEditImageAngleArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Pricing: $0.021 per megapixel.
// For a 1024x1024 image (~1 MP), that's ~2 cents per image.
@@ -68,9 +65,7 @@ pub async fn enqueue_flux_2_lora_edit_image_angle_webhook<R: IntoUrl>(
args: EnqueueFlux2LoraEditImageAngleArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|n| match n {
EnqueueFlux2LoraAngleNumImages::One => 1,
EnqueueFlux2LoraAngleNumImages::Two => 2,
@@ -78,7 +73,7 @@ pub async fn enqueue_flux_2_lora_edit_image_angle_webhook<R: IntoUrl>(
EnqueueFlux2LoraAngleNumImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|s| match s {
EnqueueFlux2LoraAngleImageSize::Square => "square",
EnqueueFlux2LoraAngleImageSize::SquareHd => "square_hd",
@@ -90,14 +85,14 @@ pub async fn enqueue_flux_2_lora_edit_image_angle_webhook<R: IntoUrl>(
.map(|s| s.to_string());
let request = Flux2LoraEditImageAngleInput {
image_urls: req.image_urls,
horizontal_angle: req.horizontal_angle,
vertical_angle: req.vertical_angle,
zoom: req.zoom,
lora_scale: req.lora_scale,
image_urls: args.image_urls,
horizontal_angle: args.horizontal_angle,
vertical_angle: args.vertical_angle,
zoom: args.zoom,
lora_scale: args.lora_scale,
image_size,
guidance_scale: req.guidance_scale,
num_inference_steps: req.num_inference_steps,
guidance_scale: args.guidance_scale,
num_inference_steps: args.num_inference_steps,
num_images,
// Constants
enable_safety_checker: Some(false),
@@ -117,7 +112,7 @@ pub async fn enqueue_flux_2_lora_edit_image_angle_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::angle::enqueue_flux_2_lora_edit_image_angle_webhook::{enqueue_flux_2_lora_edit_image_angle_webhook, EnqueueFlux2LoraEditImageAngleArgs, EnqueueFlux2LoraEditImageAngleRequest, EnqueueFlux2LoraAngleNumImages, EnqueueFlux2LoraAngleImageSize};
use crate::requests::webhook::image::angle::enqueue_flux_2_lora_edit_image_angle_webhook::{enqueue_flux_2_lora_edit_image_angle_webhook, EnqueueFlux2LoraEditImageAngleArgs, EnqueueFlux2LoraAngleNumImages, EnqueueFlux2LoraAngleImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
@@ -129,17 +124,15 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueFlux2LoraEditImageAngleArgs {
request: EnqueueFlux2LoraEditImageAngleRequest {
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(45.0),
vertical_angle: Some(15.0),
zoom: Some(5.0),
num_images: Some(EnqueueFlux2LoraAngleNumImages::One),
image_size: Some(EnqueueFlux2LoraAngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
},
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(45.0),
vertical_angle: Some(15.0),
zoom: Some(5.0),
num_images: Some(EnqueueFlux2LoraAngleNumImages::One),
image_size: Some(EnqueueFlux2LoraAngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -148,7 +141,7 @@ mod tests {
Ok(())
}
/// Enqueues 27 angle combinations: 3 horizontal x 3 vertical x 3 zoom levels.
/// Enqueues 27 angle combinations: 3 horizontal × 3 vertical × 3 zoom levels.
#[tokio::test]
#[ignore]
async fn test_angle_grid() -> AnyhowResult<()> {
@@ -163,17 +156,15 @@ mod tests {
for &v in &vertical_angles {
for &z in &zoom_levels {
let args = EnqueueFlux2LoraEditImageAngleArgs {
request: EnqueueFlux2LoraEditImageAngleRequest {
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(h),
vertical_angle: Some(v),
zoom: Some(z),
num_images: Some(EnqueueFlux2LoraAngleNumImages::One),
image_size: Some(EnqueueFlux2LoraAngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
},
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(h),
vertical_angle: Some(v),
zoom: Some(z),
num_images: Some(EnqueueFlux2LoraAngleNumImages::One),
image_size: Some(EnqueueFlux2LoraAngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -7,13 +7,6 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueQwenEdit2511EditImageAngleArgs<'a, R: IntoUrl> {
pub request: EnqueueQwenEdit2511EditImageAngleRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueQwenEdit2511EditImageAngleRequest {
// Request required
pub image_urls: Vec<String>,
@@ -23,12 +16,16 @@ pub struct EnqueueQwenEdit2511EditImageAngleRequest {
pub zoom: Option<f64>,
// Optional args
pub additional_prompt: Option<String>,
pub additional_prompt: Option<&'a str>,
pub num_images: Option<EnqueueQwenEdit2511AngleNumImages>,
pub image_size: Option<EnqueueQwenEdit2511AngleImageSize>,
pub lora_scale: Option<f64>,
pub guidance_scale: Option<f64>,
pub num_inference_steps: Option<u32>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -49,7 +46,7 @@ pub enum EnqueueQwenEdit2511AngleImageSize {
LandscapeSixteenNine,
}
impl FalRequestCostCalculator for EnqueueQwenEdit2511EditImageAngleRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueQwenEdit2511EditImageAngleArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Pricing: $0.035 per megapixel.
// For a 1024x1024 image (~1 MP), that's ~4 cents per image.
@@ -69,9 +66,7 @@ pub async fn enqueue_qwen_edit_2511_edit_image_angle_webhook<R: IntoUrl>(
args: EnqueueQwenEdit2511EditImageAngleArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|n| match n {
EnqueueQwenEdit2511AngleNumImages::One => 1,
EnqueueQwenEdit2511AngleNumImages::Two => 2,
@@ -79,7 +74,7 @@ pub async fn enqueue_qwen_edit_2511_edit_image_angle_webhook<R: IntoUrl>(
EnqueueQwenEdit2511AngleNumImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|s| match s {
EnqueueQwenEdit2511AngleImageSize::Square => "square",
EnqueueQwenEdit2511AngleImageSize::SquareHd => "square_hd",
@@ -91,15 +86,15 @@ pub async fn enqueue_qwen_edit_2511_edit_image_angle_webhook<R: IntoUrl>(
.map(|s| s.to_string());
let request = QwenEdit2511EditImageAngleInput {
image_urls: req.image_urls,
horizontal_angle: req.horizontal_angle,
vertical_angle: req.vertical_angle,
zoom: req.zoom,
additional_prompt: req.additional_prompt,
lora_scale: req.lora_scale,
image_urls: args.image_urls,
horizontal_angle: args.horizontal_angle,
vertical_angle: args.vertical_angle,
zoom: args.zoom,
additional_prompt: args.additional_prompt.map(|s| s.to_string()),
lora_scale: args.lora_scale,
image_size,
guidance_scale: req.guidance_scale,
num_inference_steps: req.num_inference_steps,
guidance_scale: args.guidance_scale,
num_inference_steps: args.num_inference_steps,
num_images,
// Constants
enable_safety_checker: Some(false),
@@ -120,7 +115,7 @@ pub async fn enqueue_qwen_edit_2511_edit_image_angle_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::angle::enqueue_qwen_edit_2511_edit_image_angle_webhook::{enqueue_qwen_edit_2511_edit_image_angle_webhook, EnqueueQwenEdit2511EditImageAngleArgs, EnqueueQwenEdit2511EditImageAngleRequest, EnqueueQwenEdit2511AngleNumImages, EnqueueQwenEdit2511AngleImageSize};
use crate::requests::webhook::image::angle::enqueue_qwen_edit_2511_edit_image_angle_webhook::{enqueue_qwen_edit_2511_edit_image_angle_webhook, EnqueueQwenEdit2511EditImageAngleArgs, EnqueueQwenEdit2511AngleNumImages, EnqueueQwenEdit2511AngleImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
@@ -133,18 +128,16 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueQwenEdit2511EditImageAngleArgs {
request: EnqueueQwenEdit2511EditImageAngleRequest {
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(45.0),
vertical_angle: Some(15.0),
zoom: Some(5.0),
additional_prompt: Some("cinematic lighting".to_string()),
num_images: Some(EnqueueQwenEdit2511AngleNumImages::One),
image_size: Some(EnqueueQwenEdit2511AngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
},
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(45.0),
vertical_angle: Some(15.0),
zoom: Some(5.0),
additional_prompt: Some("cinematic lighting"),
num_images: Some(EnqueueQwenEdit2511AngleNumImages::One),
image_size: Some(EnqueueQwenEdit2511AngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -153,7 +146,7 @@ mod tests {
Ok(())
}
/// Enqueues 9 angle combinations: 3 horizontal x 3 vertical x 3 zoom levels.
/// Enqueues 9 angle combinations: 3 horizontal × 3 vertical × 3 zoom levels.
/// Each combination generates one image, for a total of 27 enqueued requests.
#[tokio::test]
#[ignore]
@@ -169,18 +162,16 @@ mod tests {
for &v in &vertical_angles {
for &z in &zoom_levels {
let args = EnqueueQwenEdit2511EditImageAngleArgs {
request: EnqueueQwenEdit2511EditImageAngleRequest {
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(h),
vertical_angle: Some(v),
zoom: Some(z),
additional_prompt: None,
num_images: Some(EnqueueQwenEdit2511AngleNumImages::One),
image_size: Some(EnqueueQwenEdit2511AngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
},
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
horizontal_angle: Some(h),
vertical_angle: Some(v),
zoom: Some(z),
additional_prompt: None,
num_images: Some(EnqueueQwenEdit2511AngleNumImages::One),
image_size: Some(EnqueueQwenEdit2511AngleImageSize::SquareHd),
lora_scale: None,
guidance_scale: None,
num_inference_steps: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -5,25 +5,20 @@ use crate::requests::http::image::background::http_rembg_remove_background::{rem
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct RemoveBackgroundRembgWebhookArgs<'a, V: IntoUrl> {
pub request: RemoveBackgroundRembgWebhookRequest,
pub struct RemoveBackgroundRembgWebhookArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub api_key: &'a FalApiKey
}
#[derive(Clone, Debug)]
pub struct RemoveBackgroundRembgWebhookRequest {
pub image_url: String,
}
pub async fn remove_background_rembg_webhook<V: IntoUrl>(
args: RemoveBackgroundRembgWebhookArgs<'_, V>
pub async fn remove_background_rembg_webhook<U: IntoUrl, V: IntoUrl>(
args: RemoveBackgroundRembgWebhookArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let image_url = args.image_url.into_url()?;
let request = RembgRemoveBackgroundInput {
image_url: req.image_url,
image_url: image_url.to_string(),
crop_to_bbox: None,
sync_mode: None
};
@@ -40,7 +35,7 @@ pub async fn remove_background_rembg_webhook<V: IntoUrl>(
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::background::remove_background_rembg_webhook::remove_background_rembg_webhook;
use crate::requests::webhook::image::background::remove_background_rembg_webhook::{RemoveBackgroundRembgWebhookArgs, RemoveBackgroundRembgWebhookRequest};
use crate::requests::webhook::image::background::remove_background_rembg_webhook::RemoveBackgroundRembgWebhookArgs;
use std::fs::read_to_string;
use test_data::web::image_urls::ERNEST_SCARED_STUPID_IMAGE_URL;
@@ -50,15 +45,13 @@ mod tests {
// XXX: Don't commit secrets!
let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&api_key);
let args = RemoveBackgroundRembgWebhookArgs {
request: RemoveBackgroundRembgWebhookRequest {
image_url: ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
},
image_url: ERNEST_SCARED_STUPID_IMAGE_URL,
webhook_url: "https://api.storyteller.ai/webhook",
api_key: &api_key,
};
let response = remove_background_rembg_webhook(args).await?;
println!("{:?}", response);
@@ -7,18 +7,18 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueBytedanceSeedreamV4EditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueBytedanceSeedreamV4EditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueBytedanceSeedreamV4EditImageRequest {
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
// Optional args
pub num_images: Option<EnqueueBytedanceSeedreamV4EditImageNumImages>,
pub max_images: Option<EnqueueBytedanceSeedreamV4EditImageMaxImages>,
pub image_size: Option<EnqueueBytedanceSeedreamV4EditImageSize>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -39,18 +39,23 @@ pub enum EnqueueBytedanceSeedreamV4EditImageMaxImages {
#[derive(Copy, Clone, Debug)]
pub enum EnqueueBytedanceSeedreamV4EditImageSize {
// Square
Square,
SquareHd,
// Tall
PortraitFourThree,
PortraitSixteenNine,
// Wide
LandscapeFourThree,
LandscapeSixteenNine,
// Auto
Auto,
Auto2k,
Auto4k,
}
impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV4EditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.03 per image.
let unit_cost = 3;
@@ -65,29 +70,29 @@ impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV4EditImageRequest {
}
}
pub async fn enqueue_bytedance_seedream_v4_edit_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
.map(|n| match n {
let num_images = args.num_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4EditImageNumImages::One => 1,
EnqueueBytedanceSeedreamV4EditImageNumImages::Two => 2,
EnqueueBytedanceSeedreamV4EditImageNumImages::Three => 3,
EnqueueBytedanceSeedreamV4EditImageNumImages::Four => 4,
});
let max_images = req.max_images
.map(|n| match n {
let max_images = args.max_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4EditImageMaxImages::One => 1,
EnqueueBytedanceSeedreamV4EditImageMaxImages::Two => 2,
EnqueueBytedanceSeedreamV4EditImageMaxImages::Three => 3,
EnqueueBytedanceSeedreamV4EditImageMaxImages::Four => 4,
});
let image_size = req.image_size
.map(|s| match s {
let image_size = args.image_size
.map(|image_size| match image_size {
EnqueueBytedanceSeedreamV4EditImageSize::Square => "square",
EnqueueBytedanceSeedreamV4EditImageSize::SquareHd => "square_hd",
EnqueueBytedanceSeedreamV4EditImageSize::PortraitFourThree => "portrait_4_3",
@@ -98,29 +103,32 @@ pub async fn enqueue_bytedance_seedream_v4_edit_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV4EditImageSize::Auto2k => "auto_2K",
EnqueueBytedanceSeedreamV4EditImageSize::Auto4k => "auto_4K",
})
.map(|s| s.to_string());
.map(|resolution| resolution.to_string());
let input = SeedreamV4EditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
let request = SeedreamV4EditImageInput {
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
// Optionals
num_images,
max_images,
image_size,
// Constants
enhance_prompt_mode: Some("standard".to_string()),
enable_safety_checker: Some(false),
};
seedream_4_edit_image(input)
let result = seedream_4_edit_image(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await
.map_err(|err| classify_fal_error(err))
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{enqueue_bytedance_seedream_v4_edit_image_webhook, EnqueueBytedanceSeedreamV4EditImageArgs, EnqueueBytedanceSeedreamV4EditImageMaxImages, EnqueueBytedanceSeedreamV4EditImageNumImages, EnqueueBytedanceSeedreamV4EditImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -128,22 +136,26 @@ mod tests {
#[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 = EnqueueBytedanceSeedreamV4EditImageArgs {
request: EnqueueBytedanceSeedreamV4EditImageRequest {
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
image_urls: vec![GHOST_IMAGE_URL.to_string(), TREX_SKELETON_IMAGE_URL.to_string()],
num_images: Some(EnqueueBytedanceSeedreamV4EditImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4EditImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4EditImageSize::Auto2k),
},
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: Some(EnqueueBytedanceSeedreamV4EditImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4EditImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4EditImageSize::Auto2k),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let _result = enqueue_bytedance_seedream_v4_edit_image_webhook(args).await?;
let result = enqueue_bytedance_seedream_v4_edit_image_webhook(args).await?;
Ok(())
}
}
@@ -7,18 +7,18 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueBytedanceSeedreamV4p5EditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueBytedanceSeedreamV4p5EditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueBytedanceSeedreamV4p5EditImageRequest {
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
// Optional args
pub num_images: Option<EnqueueBytedanceSeedreamV4p5EditImageNumImages>,
pub max_images: Option<EnqueueBytedanceSeedreamV4p5EditImageMaxImages>,
pub image_size: Option<EnqueueBytedanceSeedreamV4p5EditImageSize>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -39,17 +39,22 @@ pub enum EnqueueBytedanceSeedreamV4p5EditImageMaxImages {
#[derive(Copy, Clone, Debug)]
pub enum EnqueueBytedanceSeedreamV4p5EditImageSize {
// Square
Square,
SquareHd,
// Tall
PortraitFourThree,
PortraitSixteenNine,
// Wide
LandscapeFourThree,
LandscapeSixteenNine,
// Auto
Auto2k,
Auto4k,
}
impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5EditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.04 per image.
let unit_cost = 4;
@@ -64,29 +69,29 @@ impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5EditImageRequest {
}
}
pub async fn enqueue_bytedance_seedream_v4p5_edit_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4p5EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
.map(|n| match n {
let num_images = args.num_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4p5EditImageNumImages::One => 1,
EnqueueBytedanceSeedreamV4p5EditImageNumImages::Two => 2,
EnqueueBytedanceSeedreamV4p5EditImageNumImages::Three => 3,
EnqueueBytedanceSeedreamV4p5EditImageNumImages::Four => 4,
});
let max_images = req.max_images
.map(|n| match n {
let max_images = args.max_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::One => 1,
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Two => 2,
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Three => 3,
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Four => 4,
});
let image_size = req.image_size
.map(|s| match s {
let image_size = args.image_size
.map(|image_size| match image_size {
EnqueueBytedanceSeedreamV4p5EditImageSize::Square => "square",
EnqueueBytedanceSeedreamV4p5EditImageSize::SquareHd => "square_hd",
EnqueueBytedanceSeedreamV4p5EditImageSize::PortraitFourThree => "portrait_4_3",
@@ -96,29 +101,32 @@ pub async fn enqueue_bytedance_seedream_v4p5_edit_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV4p5EditImageSize::Auto2k => "auto_2K",
EnqueueBytedanceSeedreamV4p5EditImageSize::Auto4k => "auto_4K",
})
.map(|s| s.to_string());
.map(|resolution| resolution.to_string());
let input = SeedreamV4p5EditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
let request = SeedreamV4p5EditImageInput {
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
// Optionals
num_images,
max_images,
image_size,
// Constants
enable_safety_checker: Some(false),
seed: None,
};
seedream_4p5_edit_image(input)
let result = seedream_4p5_edit_image(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await
.map_err(|err| classify_fal_error(err))
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{enqueue_bytedance_seedream_v4p5_edit_image_webhook, EnqueueBytedanceSeedreamV4p5EditImageArgs, EnqueueBytedanceSeedreamV4p5EditImageMaxImages, EnqueueBytedanceSeedreamV4p5EditImageNumImages, EnqueueBytedanceSeedreamV4p5EditImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -126,22 +134,27 @@ mod tests {
#[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 secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueBytedanceSeedreamV4p5EditImageArgs {
request: EnqueueBytedanceSeedreamV4p5EditImageRequest {
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
image_urls: vec![GHOST_IMAGE_URL.to_string(), TREX_SKELETON_IMAGE_URL.to_string()],
num_images: Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4p5EditImageSize::Auto2k),
},
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: Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4p5EditImageSize::Auto2k),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let _result = enqueue_bytedance_seedream_v4p5_edit_image_webhook(args).await?;
let result = enqueue_bytedance_seedream_v4p5_edit_image_webhook(args).await?;
Ok(())
}
}
@@ -7,18 +7,18 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueBytedanceSeedreamV5LiteEditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueBytedanceSeedreamV5LiteEditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueBytedanceSeedreamV5LiteEditImageRequest {
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
// Optional args
pub num_images: Option<EnqueueBytedanceSeedreamV5LiteEditImageNumImages>,
pub max_images: Option<EnqueueBytedanceSeedreamV5LiteEditImageMaxImages>,
pub image_size: Option<EnqueueBytedanceSeedreamV5LiteEditImageSize>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -39,17 +39,22 @@ pub enum EnqueueBytedanceSeedreamV5LiteEditImageMaxImages {
#[derive(Copy, Clone, Debug)]
pub enum EnqueueBytedanceSeedreamV5LiteEditImageSize {
// Square
Square,
SquareHd,
// Tall
PortraitFourThree,
PortraitSixteenNine,
// Wide
LandscapeFourThree,
LandscapeSixteenNine,
// Auto
Auto2k,
Auto3k, // NB: v5 uses auto_3K instead of v4's auto_4K
}
impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteEditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteEditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// TODO(bt): Verify actual pricing for Seedream v5 Lite on fal.ai.
let unit_cost = 4;
@@ -64,12 +69,12 @@ impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteEditImageRequest
}
}
pub async fn enqueue_bytedance_seedream_v5_lite_edit_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV5LiteEditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|n| match n {
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::One => 1,
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two => 2,
@@ -77,7 +82,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_edit_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Four => 4,
});
let max_images = req.max_images
let max_images = args.max_images
.map(|n| match n {
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::One => 1,
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Two => 2,
@@ -85,7 +90,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_edit_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|s| match s {
EnqueueBytedanceSeedreamV5LiteEditImageSize::Square => "square",
EnqueueBytedanceSeedreamV5LiteEditImageSize::SquareHd => "square_hd",
@@ -98,26 +103,29 @@ pub async fn enqueue_bytedance_seedream_v5_lite_edit_image_webhook<R: IntoUrl>(
})
.map(|s| s.to_string());
let input = SeedreamV5LiteEditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
let request = SeedreamV5LiteEditImageInput {
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
// Optionals
num_images,
max_images,
image_size,
// Constants
enable_safety_checker: Some(false),
};
http_seedream_5_edit_image(input)
let result = http_seedream_5_edit_image(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await
.map_err(|err| classify_fal_error(err))
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{enqueue_bytedance_seedream_v5_lite_edit_image_webhook, EnqueueBytedanceSeedreamV5LiteEditImageArgs, EnqueueBytedanceSeedreamV5LiteEditImageMaxImages, EnqueueBytedanceSeedreamV5LiteEditImageNumImages, EnqueueBytedanceSeedreamV5LiteEditImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -125,22 +133,26 @@ mod tests {
#[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 = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
request: EnqueueBytedanceSeedreamV5LiteEditImageRequest {
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
image_urls: vec![GHOST_IMAGE_URL.to_string(), TREX_SKELETON_IMAGE_URL.to_string()],
num_images: Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto2k),
},
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: Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto2k),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let _result = enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args).await?;
let result = enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args).await?;
Ok(())
}
}
@@ -6,16 +6,13 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct Flux1DevEditImageArgs<'a, U: IntoUrl> {
pub request: Flux1DevEditImageRequest,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct Flux1DevEditImageRequest {
pub prompt: String,
pub prompt: &'a str,
pub image_url: String,
pub num_images: Flux1DevEditImageNumImages,
// Fulfillment
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -29,9 +26,7 @@ pub enum Flux1DevEditImageNumImages {
pub async fn enqueue_flux_1_dev_edit_image_webhook<U: IntoUrl>(
args: Flux1DevEditImageArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
Flux1DevEditImageNumImages::One => 1,
Flux1DevEditImageNumImages::Two => 2,
Flux1DevEditImageNumImages::Three => 3,
@@ -39,8 +34,8 @@ pub async fn enqueue_flux_1_dev_edit_image_webhook<U: IntoUrl>(
};
let request = Flux1DevEditImageInput {
prompt: req.prompt,
image_url: req.image_url,
prompt: args.prompt.to_string(),
image_url: args.image_url,
num_images: Some(num_images),
enable_safety_checker: Some(false),
output_format: Some("png".to_string()),
@@ -60,7 +55,6 @@ mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_flux_1_dev_edit_image_webhook::{
enqueue_flux_1_dev_edit_image_webhook, Flux1DevEditImageArgs, Flux1DevEditImageNumImages,
Flux1DevEditImageRequest,
};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -73,11 +67,9 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Flux1DevEditImageArgs {
request: Flux1DevEditImageRequest {
prompt: "make this image look like a watercolor painting".to_string(),
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1DevEditImageNumImages::One,
},
prompt: "make this image look like a watercolor painting",
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1DevEditImageNumImages::One,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -94,11 +86,9 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Flux1DevEditImageArgs {
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,
},
prompt: "turn this into a cyberpunk scene with neon lights",
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1DevEditImageNumImages::Two,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -6,16 +6,13 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct Flux1SchnellEditImageArgs<'a, U: IntoUrl> {
pub request: Flux1SchnellEditImageRequest,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct Flux1SchnellEditImageRequest {
pub image_url: String,
pub num_images: Flux1SchnellEditImageNumImages,
pub image_size: Option<Flux1SchnellEditImageSize>,
// Fulfillment
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -39,16 +36,14 @@ pub enum Flux1SchnellEditImageSize {
pub async fn enqueue_flux_1_schnell_edit_image_webhook<U: IntoUrl>(
args: Flux1SchnellEditImageArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
Flux1SchnellEditImageNumImages::One => 1,
Flux1SchnellEditImageNumImages::Two => 2,
Flux1SchnellEditImageNumImages::Three => 3,
Flux1SchnellEditImageNumImages::Four => 4,
};
let image_size = req.image_size.map(|s| match s {
let image_size = args.image_size.map(|s| match s {
Flux1SchnellEditImageSize::Square => "square",
Flux1SchnellEditImageSize::SquareHd => "square_hd",
Flux1SchnellEditImageSize::LandscapeFourByThree => "landscape_4_3",
@@ -58,7 +53,7 @@ pub async fn enqueue_flux_1_schnell_edit_image_webhook<U: IntoUrl>(
}.to_string());
let request = Flux1SchnellEditImageInput {
image_url: req.image_url,
image_url: args.image_url,
num_images: Some(num_images),
image_size,
enable_safety_checker: Some(false),
@@ -79,7 +74,7 @@ mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_flux_1_schnell_edit_image_webhook::{
enqueue_flux_1_schnell_edit_image_webhook, Flux1SchnellEditImageArgs,
Flux1SchnellEditImageNumImages, Flux1SchnellEditImageRequest, Flux1SchnellEditImageSize,
Flux1SchnellEditImageNumImages, Flux1SchnellEditImageSize,
};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -92,11 +87,9 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Flux1SchnellEditImageArgs {
request: Flux1SchnellEditImageRequest {
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1SchnellEditImageNumImages::One,
image_size: None,
},
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1SchnellEditImageNumImages::One,
image_size: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -113,11 +106,9 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Flux1SchnellEditImageArgs {
request: Flux1SchnellEditImageRequest {
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1SchnellEditImageNumImages::Two,
image_size: Some(Flux1SchnellEditImageSize::LandscapeSixteenByNine),
},
image_url: GHOST_IMAGE_URL.to_string(),
num_images: Flux1SchnellEditImageNumImages::Two,
image_size: Some(Flux1SchnellEditImageSize::LandscapeSixteenByNine),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -5,19 +5,17 @@ use crate::requests::http::image::edit::http_flux_pro_kontext_max_edit::{flux_pr
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct FluxProKontextMaxArgs<'a, R: IntoUrl> {
pub request: FluxProKontextMaxRequest,
pub struct FluxProKontextMaxArgs<'a, U: IntoUrl, R: IntoUrl> {
// Request
pub prompt: &'a str,
pub image_url: U,
pub num_images: FluxProKontextMaxNumImages,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct FluxProKontextMaxRequest {
pub prompt: String,
pub image_url: String,
pub num_images: FluxProKontextMaxNumImages,
}
#[derive(Copy, Clone, Debug)]
pub enum FluxProKontextMaxNumImages {
One, // Default
@@ -26,12 +24,11 @@ pub enum FluxProKontextMaxNumImages {
Four,
}
pub async fn enqueue_flux_pro_kontext_max_edit_webhook<R: IntoUrl>(
args: FluxProKontextMaxArgs<'_, R>
pub async fn enqueue_flux_pro_kontext_max_edit_webhook<U: IntoUrl, R: IntoUrl>(
args: FluxProKontextMaxArgs<'_, U, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
FluxProKontextMaxNumImages::One => 1,
FluxProKontextMaxNumImages::Two => 2,
FluxProKontextMaxNumImages::Three => 3,
@@ -39,8 +36,8 @@ pub async fn enqueue_flux_pro_kontext_max_edit_webhook<R: IntoUrl>(
};
let request = FluxProKontextMaxEditInput {
prompt: req.prompt,
image_url: req.image_url,
prompt: args.prompt.to_string(),
image_url: args.image_url.as_str().to_string(),
num_images: Some(num_images),
// Maybe expose
@@ -64,7 +61,7 @@ pub async fn enqueue_flux_pro_kontext_max_edit_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_flux_pro_kontext_max_edit_webhook::{enqueue_flux_pro_kontext_max_edit_webhook, FluxProKontextMaxArgs, FluxProKontextMaxNumImages, FluxProKontextMaxRequest};
use crate::requests::webhook::image::edit::enqueue_flux_pro_kontext_max_edit_webhook::{enqueue_flux_pro_kontext_max_edit_webhook, FluxProKontextMaxArgs, FluxProKontextMaxNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::TALL_MOCHI_WITH_GLASSES_IMAGE_URL;
@@ -78,13 +75,11 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = FluxProKontextMaxArgs {
request: FluxProKontextMaxRequest {
prompt: "turn the glasses into sunglasses, make them sleek sunglasses with black rims, square shaped".to_string(),
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL.to_string(),
num_images: FluxProKontextMaxNumImages::One,
},
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
prompt: "turn the glasses into sunglasses, make them sleek sunglasses with black rims, square shaped",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: FluxProKontextMaxNumImages::One,
};
let result = enqueue_flux_pro_kontext_max_edit_webhook(args).await?;
@@ -7,19 +7,17 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct Gemini25FlashEditArgs<'a, R: IntoUrl> {
pub request: Gemini25FlashEditRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct Gemini25FlashEditRequest {
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: Gemini25FlashEditNumImages,
// Optional
pub aspect_ratio: Option<Gemini25FlashEditAspectRatio>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -52,7 +50,7 @@ pub enum Gemini25FlashEditAspectRatio {
}
impl FalRequestCostCalculator for Gemini25FlashEditRequest {
impl <U: IntoUrl> FalRequestCostCalculator for Gemini25FlashEditArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.039 per image. For $1.00, you can run this model 25 times.
match self.num_images {
@@ -68,16 +66,15 @@ impl FalRequestCostCalculator for Gemini25FlashEditRequest {
pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
args: Gemini25FlashEditArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
Gemini25FlashEditNumImages::One => 1,
Gemini25FlashEditNumImages::Two => 2,
Gemini25FlashEditNumImages::Three => 3,
Gemini25FlashEditNumImages::Four => 4,
};
let aspect_ratio = req.aspect_ratio
let aspect_ratio = args.aspect_ratio
.map(|aspect_ratio| match aspect_ratio {
// Auto
Gemini25FlashEditAspectRatio::Auto => "auto",
@@ -98,8 +95,8 @@ pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
.map(|aspect_ratio| aspect_ratio.to_string());
let request = Gemini25FlashEditInput {
prompt: req.prompt,
image_urls: req.image_urls,
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
aspect_ratio,
// Constants
num_images: Some(num_images),
@@ -117,7 +114,7 @@ pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs, Gemini25FlashEditNumImages, Gemini25FlashEditRequest};
use crate::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs, Gemini25FlashEditNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -131,15 +128,13 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Gemini25FlashEditArgs {
request: Gemini25FlashEditRequest {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
],
num_images: Gemini25FlashEditNumImages::Two,
aspect_ratio: None,
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
},
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
],
num_images: Gemini25FlashEditNumImages::Two,
aspect_ratio: None,
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -13,7 +13,12 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct GptEditImageByokArgs<'a, V: IntoUrl> {
pub request: GptEditImageByokRequest,
// Request
pub image_urls: Vec<String>,
pub prompt: &'a str,
pub image_size: GptEditImageSize,
pub num_images: GptEditImageNumImages,
pub quality: GptEditImageQuality,
// Fulfillment
pub api_key: &'a FalApiKey,
@@ -21,15 +26,6 @@ pub struct GptEditImageByokArgs<'a, V: IntoUrl> {
pub webhook_url: V,
}
#[derive(Clone, Debug)]
pub struct GptEditImageByokRequest {
pub image_urls: Vec<String>,
pub prompt: String,
pub image_size: GptEditImageSize,
pub num_images: GptEditImageNumImages,
pub quality: GptEditImageQuality,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageSize {
Auto,
@@ -56,7 +52,7 @@ pub enum GptEditImageNumImages{
// NB: These are BYOK, so they're not Fal's prices
impl FalRequestCostCalculator for GptEditImageByokRequest {
impl <U: IntoUrl> FalRequestCostCalculator for GptEditImageByokArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
let base_cost = match self.quality {
GptEditImageQuality::Auto => 17,
@@ -78,23 +74,22 @@ impl FalRequestCostCalculator for GptEditImageByokRequest {
pub async fn enqueue_gpt_image_1_byok_edit_image_webhook<V: IntoUrl>(
args: GptEditImageByokArgs<'_, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let image_size = match req.image_size {
let image_size = match args.image_size {
GptEditImageSize::Auto => "auto",
GptEditImageSize::Square => "1024x1024",
GptEditImageSize::Horizontal => "1536x1024",
GptEditImageSize::Vertical => "1024x1536",
};
let quality = match req.quality {
let quality = match args.quality {
GptEditImageQuality::Auto => "auto",
GptEditImageQuality::Low => "low",
GptEditImageQuality::Medium => "medium",
GptEditImageQuality::High => "high",
};
let num_images = match req.num_images {
let num_images = match args.num_images {
GptEditImageNumImages::One => 1,
GptEditImageNumImages::Two => 2,
GptEditImageNumImages::Three => 3,
@@ -102,8 +97,8 @@ pub async fn enqueue_gpt_image_1_byok_edit_image_webhook<V: IntoUrl>(
};
let request = GptImage1EditImageInput {
image_urls: req.image_urls,
prompt: req.prompt,
image_urls: args.image_urls,
prompt: args.prompt.to_string(),
image_size: image_size.to_string(),
num_images,
quality: quality.to_string(),
@@ -123,8 +118,8 @@ mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::requests::webhook::image::edit::enqueue_gpt_image_1_byok_edit_image_webhook::{
enqueue_gpt_image_1_byok_edit_image_webhook, GptEditImageByokArgs, GptEditImageByokRequest,
GptEditImageNumImages, GptEditImageQuality, GptEditImageSize,
enqueue_gpt_image_1_byok_edit_image_webhook, GptEditImageByokArgs, GptEditImageNumImages,
GptEditImageQuality, GptEditImageSize,
};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -143,17 +138,15 @@ mod tests {
let openai_api_key = OpenAiApiKey::from_str(&openai_secret);
let args = GptEditImageByokArgs {
request: GptEditImageByokRequest {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
image_size: GptEditImageSize::Horizontal,
num_images: GptEditImageNumImages::One,
quality: GptEditImageQuality::High,
},
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
image_size: GptEditImageSize::Horizontal,
num_images: GptEditImageNumImages::One,
quality: GptEditImageQuality::High,
api_key: &fal_api_key,
openai_api_key: &openai_api_key,
webhook_url: "https://example.com/webhook",
@@ -15,15 +15,8 @@ use reqwest::IntoUrl;
/// (The BYOK variant lives in `enqueue_gpt_image_1_byok_edit_image_webhook.rs`
/// and is deprecated.)
pub struct EnqueueGptImage1EditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueGptImage1EditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueGptImage1EditImageRequest {
// Required
pub prompt: String,
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: EnqueueGptImage1EditImageNumImages,
@@ -34,6 +27,10 @@ pub struct EnqueueGptImage1EditImageRequest {
pub input_fidelity: Option<EnqueueGptImage1EditImageInputFidelity>,
pub background: Option<EnqueueGptImage1EditImageBackground>,
pub output_format: Option<EnqueueGptImage1EditImageOutputFormat>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -82,7 +79,7 @@ pub enum EnqueueGptImage1EditImageOutputFormat {
Webp,
}
impl FalRequestCostCalculator for EnqueueGptImage1EditImageRequest {
impl<U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage1EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Per fal docs (fal-ai/gpt-image-1/edit-image), pricing matches
// text-to-image (input image tokens are billed separately and not included
@@ -118,16 +115,14 @@ impl FalRequestCostCalculator for EnqueueGptImage1EditImageRequest {
pub async fn enqueue_gpt_image_1_edit_image_webhook<R: IntoUrl>(
args: EnqueueGptImage1EditImageArgs<'_, R>,
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images: u8 = match req.num_images {
let num_images: u8 = match args.num_images {
EnqueueGptImage1EditImageNumImages::One => 1,
EnqueueGptImage1EditImageNumImages::Two => 2,
EnqueueGptImage1EditImageNumImages::Three => 3,
EnqueueGptImage1EditImageNumImages::Four => 4,
};
let image_size = req.image_size.map(|s| {
let image_size = args.image_size.map(|s| {
match s {
EnqueueGptImage1EditImageSize::Auto => "auto",
EnqueueGptImage1EditImageSize::Square => "1024x1024",
@@ -137,7 +132,7 @@ pub async fn enqueue_gpt_image_1_edit_image_webhook<R: IntoUrl>(
.to_string()
});
let quality = req.quality.map(|q| {
let quality = args.quality.map(|q| {
match q {
EnqueueGptImage1EditImageQuality::Low => "low",
EnqueueGptImage1EditImageQuality::Medium => "medium",
@@ -146,7 +141,7 @@ pub async fn enqueue_gpt_image_1_edit_image_webhook<R: IntoUrl>(
.to_string()
});
let input_fidelity = req.input_fidelity.map(|f| {
let input_fidelity = args.input_fidelity.map(|f| {
match f {
EnqueueGptImage1EditImageInputFidelity::Low => "low",
EnqueueGptImage1EditImageInputFidelity::High => "high",
@@ -154,7 +149,7 @@ pub async fn enqueue_gpt_image_1_edit_image_webhook<R: IntoUrl>(
.to_string()
});
let background = req.background.map(|b| {
let background = args.background.map(|b| {
match b {
EnqueueGptImage1EditImageBackground::Auto => "auto",
EnqueueGptImage1EditImageBackground::Transparent => "transparent",
@@ -163,7 +158,7 @@ pub async fn enqueue_gpt_image_1_edit_image_webhook<R: IntoUrl>(
.to_string()
});
let output_format = req.output_format.map(|f| {
let output_format = args.output_format.map(|f| {
match f {
EnqueueGptImage1EditImageOutputFormat::Jpeg => "jpeg",
EnqueueGptImage1EditImageOutputFormat::Png => "png",
@@ -173,9 +168,9 @@ pub async fn enqueue_gpt_image_1_edit_image_webhook<R: IntoUrl>(
});
let request = GptImage1NonByokEditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
mask_image_url: req.mask_image_url,
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
mask_image_url: args.mask_image_url,
num_images: Some(num_images),
image_size,
quality,
@@ -198,7 +193,7 @@ mod tests {
use crate::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::{
enqueue_gpt_image_1_edit_image_webhook, EnqueueGptImage1EditImageArgs,
EnqueueGptImage1EditImageNumImages, EnqueueGptImage1EditImageQuality,
EnqueueGptImage1EditImageRequest, EnqueueGptImage1EditImageSize,
EnqueueGptImage1EditImageSize,
};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -214,21 +209,19 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueGptImage1EditImageArgs {
request: EnqueueGptImage1EditImageRequest {
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
num_images: EnqueueGptImage1EditImageNumImages::One,
mask_image_url: None,
image_size: Some(EnqueueGptImage1EditImageSize::Horizontal),
quality: Some(EnqueueGptImage1EditImageQuality::Medium),
input_fidelity: None,
background: None,
output_format: None,
},
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
num_images: EnqueueGptImage1EditImageNumImages::One,
mask_image_url: None,
image_size: Some(EnqueueGptImage1EditImageSize::Horizontal),
quality: Some(EnqueueGptImage1EditImageQuality::Medium),
input_fidelity: None,
background: None,
output_format: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -7,25 +7,22 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueGptImage1p5EditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueGptImage1p5EditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueGptImage1p5EditImageRequest {
// Required
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: EnqueueGptImage1p5EditImageNumImages,
// Optional
// Optional args
pub mask_image_url: Option<String>,
pub image_size: Option<EnqueueGptImage1p5EditImageSize>,
pub background: Option<EnqueueGptImage1p5EditImageBackground>,
pub quality: Option<EnqueueGptImage1p5EditImageQuality>,
pub input_fidelity: Option<EnqueueGptImage1p5EditImageInputFidelity>,
pub output_format: Option<EnqueueGptImage1p5EditImageOutputFormat>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -73,7 +70,8 @@ pub enum EnqueueGptImage1p5EditImageOutputFormat {
Webp,
}
impl FalRequestCostCalculator for EnqueueGptImage1p5EditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage1p5EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost different amounts based on the number of images, quality, and size.
// For low quality, you will be charged $0.009 for 1024x1024 or $0.013 for any other size per image.
@@ -99,19 +97,19 @@ impl FalRequestCostCalculator for EnqueueGptImage1p5EditImageRequest {
}
}
pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
args: EnqueueGptImage1p5EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
EnqueueGptImage1p5EditImageNumImages::One => 1,
EnqueueGptImage1p5EditImageNumImages::Two => 2,
EnqueueGptImage1p5EditImageNumImages::Three => 3,
EnqueueGptImage1p5EditImageNumImages::Four => 4,
};
let image_size = req.image_size
let image_size = args.image_size
.map(|s| match s {
EnqueueGptImage1p5EditImageSize::Square => "1024x1024",
EnqueueGptImage1p5EditImageSize::Wide => "1536x1024",
@@ -119,7 +117,7 @@ pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
})
.map(|resolution| resolution.to_string());
let background = req.background
let background = args.background
.map(|s| match s {
EnqueueGptImage1p5EditImageBackground::Auto => "auto",
EnqueueGptImage1p5EditImageBackground::Transparent => "transparent",
@@ -127,7 +125,7 @@ pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
})
.map(|aspect_ratio| aspect_ratio.to_string());
let quality = req.quality
let quality = args.quality
.map(|s| match s {
EnqueueGptImage1p5EditImageQuality::Low => "low",
EnqueueGptImage1p5EditImageQuality::Medium => "medium",
@@ -135,14 +133,14 @@ pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
})
.map(|aspect_ratio| aspect_ratio.to_string());
let input_fidelity = req.input_fidelity
let input_fidelity = args.input_fidelity
.map(|s| match s {
EnqueueGptImage1p5EditImageInputFidelity::Low => "low",
EnqueueGptImage1p5EditImageInputFidelity::High => "high",
})
.map(|aspect_ratio| aspect_ratio.to_string());
let output_format = req.output_format
let output_format = args.output_format
.map(|s| match s {
EnqueueGptImage1p5EditImageOutputFormat::Jpeg => "jpeg",
EnqueueGptImage1p5EditImageOutputFormat::Png => "png",
@@ -152,12 +150,12 @@ pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
.unwrap_or_else(|| "png".to_string());
let request = GptImage1p5EditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
num_images: Some(num_images),
output_format: Some(output_format),
// Optionals
mask_image_url: req.mask_image_url,
mask_image_url: args.mask_image_url,
image_size,
background,
quality,
@@ -175,10 +173,7 @@ pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{
enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs,
EnqueueGptImage1p5EditImageNumImages, EnqueueGptImage1p5EditImageRequest,
};
use crate::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs, EnqueueGptImage1p5EditImageNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -192,23 +187,21 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueGptImage1p5EditImageArgs {
request: EnqueueGptImage1p5EditImageRequest {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
num_images: EnqueueGptImage1p5EditImageNumImages::Two,
mask_image_url: None,
image_size: None,
background: None,
quality: None,
input_fidelity: None,
output_format: None,
},
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
num_images: EnqueueGptImage1p5EditImageNumImages::Two,
mask_image_url: None,
image_size: None,
background: None,
quality: None,
input_fidelity: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
output_format: None,
};
let result = enqueue_gpt_image_1p5_image_edit_webhook(args).await?;
@@ -9,23 +9,20 @@ use crate::requests::webhook::image::edit::enqueue_gpt_image_2_edit_image_webhoo
use crate::requests::webhook::image::edit::enqueue_gpt_image_2_edit_image_webhook::EnqueueGptImage2EditImageSize::SquareHd;
pub struct EnqueueGptImage2EditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueGptImage2EditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueGptImage2EditImageRequest {
// Required
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: EnqueueGptImage2EditImageNumImages,
// Optional
// Optional args
pub mask_url: Option<String>,
pub image_size: Option<EnqueueGptImage2EditImageSize>,
pub quality: Option<EnqueueGptImage2EditImageQuality>,
pub output_format: Option<EnqueueGptImage2EditImageOutputFormat>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -61,7 +58,8 @@ pub enum EnqueueGptImage2EditImageOutputFormat {
Webp,
}
impl FalRequestCostCalculator for EnqueueGptImage2EditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage2EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Cost table (per image) by approximate pixel dimensions:
//
@@ -101,19 +99,19 @@ impl FalRequestCostCalculator for EnqueueGptImage2EditImageRequest {
}
}
pub async fn enqueue_gpt_image_2_edit_image_webhook<R: IntoUrl>(
args: EnqueueGptImage2EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
EnqueueGptImage2EditImageNumImages::One => 1,
EnqueueGptImage2EditImageNumImages::Two => 2,
EnqueueGptImage2EditImageNumImages::Three => 3,
EnqueueGptImage2EditImageNumImages::Four => 4,
};
let image_size = req.image_size
let image_size = args.image_size
.map(|s| match s {
EnqueueGptImage2EditImageSize::SquareHd => "square_hd",
EnqueueGptImage2EditImageSize::Square => "square",
@@ -125,7 +123,7 @@ pub async fn enqueue_gpt_image_2_edit_image_webhook<R: IntoUrl>(
})
.map(|size| size.to_string());
let quality = req.quality
let quality = args.quality
.map(|s| match s {
EnqueueGptImage2EditImageQuality::Low => "low",
EnqueueGptImage2EditImageQuality::Medium => "medium",
@@ -133,7 +131,7 @@ pub async fn enqueue_gpt_image_2_edit_image_webhook<R: IntoUrl>(
})
.map(|quality| quality.to_string());
let output_format = req.output_format
let output_format = args.output_format
.map(|s| match s {
EnqueueGptImage2EditImageOutputFormat::Jpeg => "jpeg",
EnqueueGptImage2EditImageOutputFormat::Png => "png",
@@ -143,12 +141,12 @@ pub async fn enqueue_gpt_image_2_edit_image_webhook<R: IntoUrl>(
.unwrap_or_else(|| "png".to_string());
let request = GptImage2EditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
num_images: Some(num_images),
output_format: Some(output_format),
// Optionals
mask_url: req.mask_url,
mask_url: args.mask_url,
image_size,
quality,
};
@@ -164,10 +162,7 @@ pub async fn enqueue_gpt_image_2_edit_image_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_gpt_image_2_edit_image_webhook::{
enqueue_gpt_image_2_edit_image_webhook, EnqueueGptImage2EditImageArgs,
EnqueueGptImage2EditImageNumImages, EnqueueGptImage2EditImageRequest,
};
use crate::requests::webhook::image::edit::enqueue_gpt_image_2_edit_image_webhook::{enqueue_gpt_image_2_edit_image_webhook, EnqueueGptImage2EditImageArgs, EnqueueGptImage2EditImageNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -181,21 +176,19 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueGptImage2EditImageArgs {
request: EnqueueGptImage2EditImageRequest {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
num_images: EnqueueGptImage2EditImageNumImages::Two,
mask_url: None,
image_size: None,
quality: None,
output_format: None,
},
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
num_images: EnqueueGptImage2EditImageNumImages::Two,
mask_url: None,
image_size: None,
quality: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
output_format: None,
};
let result = enqueue_gpt_image_2_edit_image_webhook(args).await?;
@@ -7,21 +7,18 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueNanoBanana2EditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueNanoBanana2EditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueNanoBanana2EditImageRequest {
// Required
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: EnqueueNanoBanana2EditImageNumImages,
// Optional
// Optional args
pub resolution: Option<EnqueueNanoBanana2EditImageResolution>,
pub aspect_ratio: Option<EnqueueNanoBanana2EditImageAspectRatio>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -61,7 +58,8 @@ pub enum EnqueueNanoBanana2EditImageAspectRatio {
NineBySixteen,
}
impl FalRequestCostCalculator for EnqueueNanoBanana2EditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueNanoBanana2EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// TODO(bt): Verify actual pricing for Nano Banana 2 on fal.ai.
// 4K outputs may be charged at double the standard rate.
@@ -82,19 +80,19 @@ impl FalRequestCostCalculator for EnqueueNanoBanana2EditImageRequest {
}
}
pub async fn enqueue_nano_banana_2_edit_image_webhook<R: IntoUrl>(
args: EnqueueNanoBanana2EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
EnqueueNanoBanana2EditImageNumImages::One => 1,
EnqueueNanoBanana2EditImageNumImages::Two => 2,
EnqueueNanoBanana2EditImageNumImages::Three => 3,
EnqueueNanoBanana2EditImageNumImages::Four => 4,
};
let resolution = req.resolution
let resolution = args.resolution
.map(|resolution| match resolution {
EnqueueNanoBanana2EditImageResolution::HalfK => "0.5K",
EnqueueNanoBanana2EditImageResolution::OneK => "1K",
@@ -103,7 +101,7 @@ pub async fn enqueue_nano_banana_2_edit_image_webhook<R: IntoUrl>(
})
.map(|r| r.to_string());
let aspect_ratio = req.aspect_ratio
let aspect_ratio = args.aspect_ratio
.map(|ar| match ar {
// Auto
EnqueueNanoBanana2EditImageAspectRatio::Auto => "auto",
@@ -124,8 +122,8 @@ pub async fn enqueue_nano_banana_2_edit_image_webhook<R: IntoUrl>(
.map(|ar| ar.to_string());
let request = NanoBanana2EditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
num_images: Some(num_images),
// Optionals
aspect_ratio,
@@ -149,11 +147,7 @@ pub async fn enqueue_nano_banana_2_edit_image_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::{
enqueue_nano_banana_2_edit_image_webhook, EnqueueNanoBanana2EditImageArgs,
EnqueueNanoBanana2EditImageAspectRatio, EnqueueNanoBanana2EditImageNumImages,
EnqueueNanoBanana2EditImageRequest, EnqueueNanoBanana2EditImageResolution,
};
use crate::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::{enqueue_nano_banana_2_edit_image_webhook, EnqueueNanoBanana2EditImageArgs, EnqueueNanoBanana2EditImageAspectRatio, EnqueueNanoBanana2EditImageNumImages, EnqueueNanoBanana2EditImageResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL, WHITE_HOUSE_SUNSET_IMAGE_URL};
@@ -167,17 +161,15 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueNanoBanana2EditImageArgs {
request: EnqueueNanoBanana2EditImageRequest {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
num_images: EnqueueNanoBanana2EditImageNumImages::Two,
aspect_ratio: Some(EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine),
resolution: Some(EnqueueNanoBanana2EditImageResolution::TwoK),
},
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
num_images: EnqueueNanoBanana2EditImageNumImages::Two,
aspect_ratio: Some(EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine),
resolution: Some(EnqueueNanoBanana2EditImageResolution::TwoK),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -196,17 +188,15 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueNanoBanana2EditImageArgs {
request: EnqueueNanoBanana2EditImageRequest {
image_urls: vec![
WHITE_HOUSE_SUNSET_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "Put the scared man and the t-rex in front of the white house scene. Make the man afraid of the t-rex.".to_string(),
num_images: EnqueueNanoBanana2EditImageNumImages::Two,
aspect_ratio: Some(EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine),
resolution: Some(EnqueueNanoBanana2EditImageResolution::TwoK),
},
image_urls: vec![
WHITE_HOUSE_SUNSET_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "Put the scared man and the t-rex in front of the white house scene. Make the man afraid of the t-rex.",
num_images: EnqueueNanoBanana2EditImageNumImages::Two,
aspect_ratio: Some(EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine),
resolution: Some(EnqueueNanoBanana2EditImageResolution::TwoK),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -7,20 +7,18 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueNanoBananaProEditImageArgs<'a, R: IntoUrl> {
pub request: EnqueueNanoBananaProEditImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueNanoBananaProEditImageRequest {
pub prompt: String,
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: EnqueueNanoBananaProEditImageNumImages,
// Optional args
pub resolution: Option<EnqueueNanoBananaProEditImageResolution>,
pub aspect_ratio: Option<EnqueueNanoBananaProEditImageAspectRatio>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -60,7 +58,7 @@ pub enum EnqueueNanoBananaProEditImageAspectRatio {
}
impl FalRequestCostCalculator for EnqueueNanoBananaProEditImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueNanoBananaProEditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.15 per image.
// For $1.00, you can run this model 7 times.
@@ -86,16 +84,15 @@ impl FalRequestCostCalculator for EnqueueNanoBananaProEditImageRequest {
pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
args: EnqueueNanoBananaProEditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
EnqueueNanoBananaProEditImageNumImages::One => 1,
EnqueueNanoBananaProEditImageNumImages::Two => 2,
EnqueueNanoBananaProEditImageNumImages::Three => 3,
EnqueueNanoBananaProEditImageNumImages::Four => 4,
};
let resolution = req.resolution
let resolution = args.resolution
.map(|resolution| match resolution {
EnqueueNanoBananaProEditImageResolution::OneK => "1K",
EnqueueNanoBananaProEditImageResolution::TwoK => "2K",
@@ -103,7 +100,7 @@ pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
})
.map(|resolution| resolution.to_string());
let aspect_ratio = req.aspect_ratio
let aspect_ratio = args.aspect_ratio
.map(|aspect_ratio| match aspect_ratio {
// Auto
EnqueueNanoBananaProEditImageAspectRatio::Auto => "auto",
@@ -124,8 +121,8 @@ pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
.map(|aspect_ratio| aspect_ratio.to_string());
let request = NanoBananaProEditImageInput {
prompt: req.prompt,
image_urls: req.image_urls,
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
num_images: Some(num_images),
// Optionals
aspect_ratio,
@@ -145,7 +142,7 @@ pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::{enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProEditImageArgs, EnqueueNanoBananaProEditImageAspectRatio, EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageRequest, EnqueueNanoBananaProEditImageResolution};
use crate::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::{enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProEditImageArgs, EnqueueNanoBananaProEditImageAspectRatio, EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
@@ -159,17 +156,15 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueNanoBananaProEditImageArgs {
request: EnqueueNanoBananaProEditImageRequest {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly".to_string(),
num_images: EnqueueNanoBananaProEditImageNumImages::Two,
aspect_ratio: Some(EnqueueNanoBananaProEditImageAspectRatio::SixteenByNine),
resolution: Some(EnqueueNanoBananaProEditImageResolution::TwoK),
},
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
num_images: EnqueueNanoBananaProEditImageNumImages::Two,
aspect_ratio: Some(EnqueueNanoBananaProEditImageAspectRatio::SixteenByNine),
resolution: Some(EnqueueNanoBananaProEditImageResolution::TwoK),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -6,17 +6,11 @@ use crate::requests::http::image::edit::http_qwen_image_edit::{qwen_image_edit,
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct QwenImageEditArgs<'a, R: IntoUrl> {
pub request: QwenImageEditRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct QwenImageEditRequest {
pub prompt: String,
pub image_url: String,
pub struct QwenImageEditArgs<'a, U: IntoUrl, R: IntoUrl> {
// Request required
pub prompt: &'a str,
pub image_url: U,
// Request optional
pub num_images: Option<QwenImageEditNumImages>,
pub image_size: Option<QwenImageEditSize>,
@@ -27,6 +21,10 @@ pub struct QwenImageEditRequest {
/// Options: 'none', 'regular'. Higher acceleration increases speed.
/// 'regular' balances speed and quality. Default value: "none"
pub acceleration: Option<String>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -49,7 +47,7 @@ pub enum QwenImageEditSize {
}
impl FalRequestCostCalculator for QwenImageEditRequest {
impl <U: IntoUrl, R: IntoUrl> FalRequestCostCalculator for QwenImageEditArgs<'_, U, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.03 per megapixel. -- Ugh, not calculating that
let base_cost = 3;
@@ -65,12 +63,11 @@ impl FalRequestCostCalculator for QwenImageEditRequest {
}
pub async fn enqueue_qwen_image_edit_webhook<R: IntoUrl>(
args: QwenImageEditArgs<'_, R>
pub async fn enqueue_qwen_image_edit_webhook<U: IntoUrl, R: IntoUrl>(
args: QwenImageEditArgs<'_, U, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|num| match num {
QwenImageEditNumImages::One => 1,
QwenImageEditNumImages::Two => 2,
@@ -78,7 +75,7 @@ pub async fn enqueue_qwen_image_edit_webhook<R: IntoUrl>(
QwenImageEditNumImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|size| match size {
QwenImageEditSize::Square => "square",
QwenImageEditSize::SquareHd => "square_hd",
@@ -90,8 +87,8 @@ pub async fn enqueue_qwen_image_edit_webhook<R: IntoUrl>(
.map(|size| size.to_string());
let request = QwenImageEditInput {
prompt: req.prompt,
image_url: req.image_url,
prompt: args.prompt.to_string(),
image_url: args.image_url.as_str().to_string(),
image_size,
num_images,
@@ -119,7 +116,7 @@ pub async fn enqueue_qwen_image_edit_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_qwen_image_edit_webhook::{enqueue_qwen_image_edit_webhook, QwenImageEditArgs, QwenImageEditNumImages, QwenImageEditRequest};
use crate::requests::webhook::image::edit::enqueue_qwen_image_edit_webhook::{enqueue_qwen_image_edit_webhook, QwenImageEditArgs, QwenImageEditNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::MOUNTAIN_TREE_IMAGE_URL;
@@ -133,14 +130,12 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = QwenImageEditArgs {
request: QwenImageEditRequest {
prompt: "put christmas lights on the tree, add snow to the mountains".to_string(),
image_url: MOUNTAIN_TREE_IMAGE_URL.to_string(),
num_images: Some(QwenImageEditNumImages::One),
image_size: None,
negative_prompt: None,
acceleration: None,
},
image_url: MOUNTAIN_TREE_IMAGE_URL,
prompt: "put christmas lights on the tree, add snow to the mountains",
num_images: Some(QwenImageEditNumImages::One),
image_size: None,
negative_prompt: None,
acceleration: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -5,26 +5,23 @@ use crate::requests::http::image::edit::http_seededit_v3_edit::{seededit_v3_edit
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct SeedEditV3EditArgs<'a, R: IntoUrl> {
pub request: SeedEditV3EditRequest,
pub struct SeedEditV3EditArgs<'a, U: IntoUrl, R: IntoUrl> {
// Request required
pub prompt: &'a str,
pub image_url: U,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct SeedEditV3EditRequest {
pub prompt: String,
pub image_url: String,
}
pub async fn enqueue_seededit_v3_edit_webhook<R: IntoUrl>(
args: SeedEditV3EditArgs<'_, R>
pub async fn enqueue_seededit_v3_edit_webhook<U: IntoUrl, R: IntoUrl>(
args: SeedEditV3EditArgs<'_, U, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let request = SeedEditV3EditInput {
prompt: req.prompt,
image_url: req.image_url,
prompt: args.prompt.to_string(),
image_url: args.image_url.as_str().to_string(),
// Constants
guidance_scale: None,
@@ -42,7 +39,7 @@ pub async fn enqueue_seededit_v3_edit_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::edit::enqueue_seededit_v3_edit_webhook::{enqueue_seededit_v3_edit_webhook, SeedEditV3EditArgs, SeedEditV3EditRequest};
use crate::requests::webhook::image::edit::enqueue_seededit_v3_edit_webhook::{enqueue_seededit_v3_edit_webhook, SeedEditV3EditArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::MOUNTAIN_TREE_IMAGE_URL;
@@ -56,10 +53,8 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = SeedEditV3EditArgs {
request: SeedEditV3EditRequest {
prompt: "put christmas lights on the tree, add snow to the mountains".to_string(),
image_url: MOUNTAIN_TREE_IMAGE_URL.to_string(),
},
image_url: MOUNTAIN_TREE_IMAGE_URL,
prompt: "put christmas lights on the tree, add snow to the mountains",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -5,18 +5,16 @@ use crate::requests::http::image::infill::http_flux_dev_juggernaut_infill::{flux
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct FluxDevJuggernautInfillArgs<'a, R: IntoUrl> {
pub request: FluxDevJuggernautInfillRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct FluxDevJuggernautInfillRequest {
pub prompt: String,
pub image_url: String,
pub mask_url: String,
pub struct FluxDevJuggernautInfillArgs<'a, U: IntoUrl, R: IntoUrl, L: IntoUrl> {
// Request
pub prompt: &'a str,
pub image_url: U,
pub mask_url: R,
pub num_images: FluxDevJuggernautInfillNumImages,
// Fulfillment
pub webhook_url: L,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -27,13 +25,11 @@ pub enum FluxDevJuggernautInfillNumImages {
Four,
}
pub async fn enqueue_flux_dev_juggernaut_infill_webhook<R: IntoUrl>(
args: FluxDevJuggernautInfillArgs<'_, R>
pub async fn enqueue_flux_dev_juggernaut_infill_webhook<U: IntoUrl, R: IntoUrl, L: IntoUrl>(
args: FluxDevJuggernautInfillArgs<'_, U, R, L>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
FluxDevJuggernautInfillNumImages::One => 1,
FluxDevJuggernautInfillNumImages::Two => 2,
FluxDevJuggernautInfillNumImages::Three => 3,
@@ -41,9 +37,9 @@ pub async fn enqueue_flux_dev_juggernaut_infill_webhook<R: IntoUrl>(
};
let request = FluxDevJuggernautInfillInput {
prompt: req.prompt,
image_url: req.image_url,
mask_url: req.mask_url,
prompt: args.prompt.to_string(),
image_url: args.image_url.as_str().to_string(),
mask_url: args.mask_url.as_str().to_string(),
num_images: Some(num_images),
// Maybe expose
@@ -70,7 +66,7 @@ pub async fn enqueue_flux_dev_juggernaut_infill_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::infill::enqueue_flux_dev_juggernaut_infill_webhook::{enqueue_flux_dev_juggernaut_infill_webhook, FluxDevJuggernautInfillArgs, FluxDevJuggernautInfillRequest, FluxDevJuggernautInfillNumImages};
use crate::requests::webhook::image::infill::enqueue_flux_dev_juggernaut_infill_webhook::{enqueue_flux_dev_juggernaut_infill_webhook, FluxDevJuggernautInfillArgs, FluxDevJuggernautInfillNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL, TALL_MOCHI_WITH_GLASSES_IMAGE_URL};
@@ -84,14 +80,12 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = FluxDevJuggernautInfillArgs {
request: FluxDevJuggernautInfillRequest {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL.to_string(),
mask_url: TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL.to_string(),
prompt: "slick sunglasses, cool glasses, reflection in glasses lenses".to_string(),
num_images: FluxDevJuggernautInfillNumImages::Two,
},
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
mask_url: TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL,
prompt: "slick sunglasses, cool glasses, reflection in glasses lenses",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: FluxDevJuggernautInfillNumImages::Two,
};
let result = enqueue_flux_dev_juggernaut_infill_webhook(args).await?;
@@ -5,18 +5,16 @@ use crate::requests::http::image::infill::http_flux_pro_1_infill::{flux_pro_1_in
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct FluxPro1InfillArgs<'a, R: IntoUrl> {
pub request: FluxPro1InfillRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct FluxPro1InfillRequest {
pub prompt: String,
pub image_url: String,
pub mask_url: String,
pub struct FluxPro1InfillArgs<'a, U: IntoUrl, R: IntoUrl, L: IntoUrl> {
// Request
pub prompt: &'a str,
pub image_url: U,
pub mask_url: R,
pub num_images: FluxPro1InfillNumImages,
// Fulfillment
pub webhook_url: L,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -27,13 +25,11 @@ pub enum FluxPro1InfillNumImages {
Four,
}
pub async fn enqueue_flux_pro_1_infill_webhook<R: IntoUrl>(
args: FluxPro1InfillArgs<'_, R>
pub async fn enqueue_flux_pro_1_infill_webhook<U: IntoUrl, R: IntoUrl, L: IntoUrl>(
args: FluxPro1InfillArgs<'_, U, R, L>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
FluxPro1InfillNumImages::One => 1,
FluxPro1InfillNumImages::Two => 2,
FluxPro1InfillNumImages::Three => 3,
@@ -41,9 +37,9 @@ pub async fn enqueue_flux_pro_1_infill_webhook<R: IntoUrl>(
};
let request = FluxPro1InfillInput {
prompt: req.prompt,
image_url: req.image_url,
mask_url: req.mask_url,
prompt: args.prompt.to_string(),
image_url: args.image_url.as_str().to_string(),
mask_url: args.mask_url.as_str().to_string(),
num_images: Some(num_images),
// Maybe expose
@@ -66,7 +62,7 @@ pub async fn enqueue_flux_pro_1_infill_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::infill::enqueue_flux_pro_1_infill_webhook::{enqueue_flux_pro_1_infill_webhook, FluxPro1InfillArgs, FluxPro1InfillRequest, FluxPro1InfillNumImages};
use crate::requests::webhook::image::infill::enqueue_flux_pro_1_infill_webhook::{enqueue_flux_pro_1_infill_webhook, FluxPro1InfillArgs, FluxPro1InfillNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL, TALL_MOCHI_WITH_GLASSES_IMAGE_URL};
@@ -80,14 +76,12 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = FluxPro1InfillArgs {
request: FluxPro1InfillRequest {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL.to_string(),
mask_url: TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL.to_string(),
prompt: "slick sunglasses, cool glasses, reflection in glasses lenses".to_string(),
num_images: FluxPro1InfillNumImages::One,
},
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
mask_url: TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL,
prompt: "slick sunglasses, cool glasses, reflection in glasses lenses",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: FluxPro1InfillNumImages::One,
};
let result = enqueue_flux_pro_1_infill_webhook(args).await?;
@@ -7,20 +7,17 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueBytedanceSeedreamV4TextToImageArgs<'a, R: IntoUrl> {
pub request: EnqueueBytedanceSeedreamV4TextToImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueBytedanceSeedreamV4TextToImageRequest {
// Request required
pub prompt: String,
pub prompt: &'a str,
// Optional args
pub num_images: Option<EnqueueBytedanceSeedreamV4TextToImageNumImages>,
pub max_images: Option<EnqueueBytedanceSeedreamV4TextToImageMaxImages>,
pub image_size: Option<EnqueueBytedanceSeedreamV4TextToImageSize>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -57,7 +54,7 @@ pub enum EnqueueBytedanceSeedreamV4TextToImageSize {
}
impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV4TextToImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4TextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Copied from edit image version
// Your request will cost $0.03 per image.
@@ -78,9 +75,7 @@ pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4TextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4TextToImageNumImages::One => 1,
EnqueueBytedanceSeedreamV4TextToImageNumImages::Two => 2,
@@ -88,7 +83,7 @@ pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV4TextToImageNumImages::Four => 4,
});
let max_images = req.max_images
let max_images = args.max_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4TextToImageMaxImages::One => 1,
EnqueueBytedanceSeedreamV4TextToImageMaxImages::Two => 2,
@@ -96,7 +91,7 @@ pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV4TextToImageMaxImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|image_size| match image_size {
EnqueueBytedanceSeedreamV4TextToImageSize::Square => "square",
EnqueueBytedanceSeedreamV4TextToImageSize::SquareHd => "square_hd",
@@ -111,7 +106,7 @@ pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
.map(|resolution| resolution.to_string());
let request = SeedreamV4TextToImageInput {
prompt: req.prompt,
prompt: args.prompt.to_string(),
// Optionals
num_images,
max_images,
@@ -132,7 +127,7 @@ pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs, EnqueueBytedanceSeedreamV4TextToImageMaxImages, EnqueueBytedanceSeedreamV4TextToImageNumImages, EnqueueBytedanceSeedreamV4TextToImageRequest, EnqueueBytedanceSeedreamV4TextToImageSize};
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs, EnqueueBytedanceSeedreamV4TextToImageMaxImages, EnqueueBytedanceSeedreamV4TextToImageNumImages, EnqueueBytedanceSeedreamV4TextToImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -145,12 +140,10 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueBytedanceSeedreamV4TextToImageArgs {
request: EnqueueBytedanceSeedreamV4TextToImageRequest {
prompt: "an anime girl is riding a t-rex in the forest".to_string(),
num_images: Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4TextToImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4TextToImageSize::LandscapeSixteenNine),
},
prompt: "an anime girl is riding a t-rex in the forest",
num_images: Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4TextToImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4TextToImageSize::LandscapeSixteenNine),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -8,20 +8,17 @@ use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostC
use crate::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{EnqueueBytedanceSeedreamV4p5EditImageArgs, EnqueueBytedanceSeedreamV4p5EditImageNumImages};
pub struct EnqueueBytedanceSeedreamV4p5TextToImageArgs<'a, R: IntoUrl> {
pub request: EnqueueBytedanceSeedreamV4p5TextToImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueBytedanceSeedreamV4p5TextToImageRequest {
// Request required
pub prompt: String,
pub prompt: &'a str,
// Optional args
pub num_images: Option<EnqueueBytedanceSeedreamV4p5TextToImageNumImages>,
pub max_images: Option<EnqueueBytedanceSeedreamV4p5TextToImageMaxImages>,
pub image_size: Option<EnqueueBytedanceSeedreamV4p5TextToImageSize>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -57,7 +54,7 @@ pub enum EnqueueBytedanceSeedreamV4p5TextToImageSize {
}
impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5TextToImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5TextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// NB: Copied from edit image costs
// Your request will cost $0.04 per image.
@@ -78,9 +75,7 @@ pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4p5TextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::One => 1,
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Two => 2,
@@ -88,7 +83,7 @@ pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Four => 4,
});
let max_images = req.max_images
let max_images = args.max_images
.map(|num_images| match num_images {
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::One => 1,
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Two => 2,
@@ -96,7 +91,7 @@ pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|image_size| match image_size {
EnqueueBytedanceSeedreamV4p5TextToImageSize::Square => "square",
EnqueueBytedanceSeedreamV4p5TextToImageSize::SquareHd => "square_hd",
@@ -110,7 +105,7 @@ pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
.map(|resolution| resolution.to_string());
let request = SeedreamV4p5TextToImageInput {
prompt: req.prompt,
prompt: args.prompt.to_string(),
// Optionals
num_images,
max_images,
@@ -131,7 +126,7 @@ pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs, EnqueueBytedanceSeedreamV4p5TextToImageMaxImages, EnqueueBytedanceSeedreamV4p5TextToImageNumImages, EnqueueBytedanceSeedreamV4p5TextToImageRequest, EnqueueBytedanceSeedreamV4p5TextToImageSize};
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs, EnqueueBytedanceSeedreamV4p5TextToImageMaxImages, EnqueueBytedanceSeedreamV4p5TextToImageNumImages, EnqueueBytedanceSeedreamV4p5TextToImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -144,12 +139,10 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueBytedanceSeedreamV4p5TextToImageArgs {
request: EnqueueBytedanceSeedreamV4p5TextToImageRequest {
prompt: "an anime girl is riding a t-rex in the forest".to_string(),
num_images: Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4p5TextToImageSize::LandscapeSixteenNine),
},
prompt: "an anime girl is riding a t-rex in the forest",
num_images: Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV4p5TextToImageSize::LandscapeSixteenNine),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -7,20 +7,17 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueBytedanceSeedreamV5LiteTextToImageArgs<'a, R: IntoUrl> {
pub request: EnqueueBytedanceSeedreamV5LiteTextToImageRequest,
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct EnqueueBytedanceSeedreamV5LiteTextToImageRequest {
// Request required
pub prompt: String,
pub prompt: &'a str,
// Optional args
pub num_images: Option<EnqueueBytedanceSeedreamV5LiteTextToImageNumImages>,
pub max_images: Option<EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages>,
pub image_size: Option<EnqueueBytedanceSeedreamV5LiteTextToImageSize>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
@@ -56,7 +53,7 @@ pub enum EnqueueBytedanceSeedreamV5LiteTextToImageSize {
}
impl FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteTextToImageRequest {
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteTextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// TODO(bt): Verify actual pricing for Seedream v5 Lite on fal.ai.
let unit_cost = 4;
@@ -76,9 +73,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_text_to_image_webhook<R: IntoUrl
args: EnqueueBytedanceSeedreamV5LiteTextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = req.num_images
let num_images = args.num_images
.map(|n| match n {
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::One => 1,
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two => 2,
@@ -86,7 +81,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_text_to_image_webhook<R: IntoUrl
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Four => 4,
});
let max_images = req.max_images
let max_images = args.max_images
.map(|n| match n {
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::One => 1,
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Two => 2,
@@ -94,7 +89,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_text_to_image_webhook<R: IntoUrl
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Four => 4,
});
let image_size = req.image_size
let image_size = args.image_size
.map(|s| match s {
EnqueueBytedanceSeedreamV5LiteTextToImageSize::Square => "square",
EnqueueBytedanceSeedreamV5LiteTextToImageSize::SquareHd => "square_hd",
@@ -108,7 +103,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_text_to_image_webhook<R: IntoUrl
.map(|s| s.to_string());
let request = SeedreamV5LiteTextToImageInput {
prompt: req.prompt,
prompt: args.prompt.to_string(),
// Optionals
num_images,
max_images,
@@ -128,7 +123,7 @@ pub async fn enqueue_bytedance_seedream_v5_lite_text_to_image_webhook<R: IntoUrl
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs, EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages, EnqueueBytedanceSeedreamV5LiteTextToImageNumImages, EnqueueBytedanceSeedreamV5LiteTextToImageRequest, EnqueueBytedanceSeedreamV5LiteTextToImageSize};
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs, EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages, EnqueueBytedanceSeedreamV5LiteTextToImageNumImages, EnqueueBytedanceSeedreamV5LiteTextToImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -141,12 +136,10 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
request: EnqueueBytedanceSeedreamV5LiteTextToImageRequest {
prompt: "an anime girl is riding a t-rex in the forest".to_string(),
num_images: Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeSixteenNine),
},
prompt: "an anime girl is riding a t-rex in the forest",
num_images: Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two),
max_images: Some(EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Two),
image_size: Some(EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeSixteenNine),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -6,14 +6,9 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct Flux1DevArgs<'a, U: IntoUrl> {
pub request: Flux1DevRequest,
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct Flux1DevRequest {
pub prompt: String,
pub aspect_ratio: Flux1DevAspectRatio,
pub num_images: Flux1DevNumImages,
}
@@ -41,16 +36,14 @@ pub async fn enqueue_flux_1_dev_text_to_image_webhook<U: IntoUrl>(
args: Flux1DevArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
Flux1DevNumImages::One => 1,
Flux1DevNumImages::Two => 2,
Flux1DevNumImages::Three => 3,
Flux1DevNumImages::Four => 4,
};
let image_size = match req.aspect_ratio {
let image_size = match args.aspect_ratio {
Flux1DevAspectRatio::Square => "square",
Flux1DevAspectRatio::SquareHd => "square_hd",
Flux1DevAspectRatio::LandscapeFourByThree => "landscape_4_3",
@@ -60,7 +53,7 @@ pub async fn enqueue_flux_1_dev_text_to_image_webhook<U: IntoUrl>(
};
let request = Flux1DevTextToImageInput {
prompt: req.prompt,
prompt: args.prompt.to_string(),
num_images: Some(num_images),
image_size: Some(image_size.to_string()),
// Maybe abstract
@@ -84,7 +77,7 @@ pub async fn enqueue_flux_1_dev_text_to_image_webhook<U: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image_webhook::{enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs, Flux1DevAspectRatio, Flux1DevNumImages, Flux1DevRequest};
use crate::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image_webhook::{enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs, Flux1DevAspectRatio, Flux1DevNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -97,13 +90,11 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Flux1DevArgs {
request: Flux1DevRequest {
prompt: "a giant robot fighting a dragon in a futuristic city".to_string(),
num_images: Flux1DevNumImages::One,
aspect_ratio: Flux1DevAspectRatio::LandscapeFourByThree,
},
prompt: "a giant robot fighting a dragon in a futuristic city",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: Flux1DevNumImages::One,
aspect_ratio: Flux1DevAspectRatio::LandscapeFourByThree
};
let result = enqueue_flux_1_dev_text_to_image_webhook(args).await?;
@@ -6,14 +6,9 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct Flux1SchnellArgs<'a, U: IntoUrl> {
pub request: Flux1SchnellRequest,
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct Flux1SchnellRequest {
pub prompt: String,
pub aspect_ratio: Flux1SchnellAspectRatio,
pub num_images: Flux1SchnellNumImages,
}
@@ -41,16 +36,14 @@ pub async fn enqueue_flux_1_schnell_text_to_image_webhook<U: IntoUrl>(
args: Flux1SchnellArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
Flux1SchnellNumImages::One => 1,
Flux1SchnellNumImages::Two => 2,
Flux1SchnellNumImages::Three => 3,
Flux1SchnellNumImages::Four => 4,
};
let image_size = match req.aspect_ratio {
let image_size = match args.aspect_ratio {
Flux1SchnellAspectRatio::Square => "square",
Flux1SchnellAspectRatio::SquareHd => "square_hd",
Flux1SchnellAspectRatio::LandscapeFourByThree => "landscape_4_3",
@@ -60,7 +53,7 @@ pub async fn enqueue_flux_1_schnell_text_to_image_webhook<U: IntoUrl>(
};
let request = Flux1SchnellTextToImageInput {
prompt: req.prompt,
prompt: args.prompt.to_string(),
num_images: Some(num_images),
image_size: Some(image_size.to_string()),
// Maybe abstract
@@ -83,7 +76,7 @@ pub async fn enqueue_flux_1_schnell_text_to_image_webhook<U: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_image_webhook::{enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs, Flux1SchnellAspectRatio, Flux1SchnellNumImages, Flux1SchnellRequest};
use crate::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_image_webhook::{enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs, Flux1SchnellAspectRatio, Flux1SchnellNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -96,13 +89,11 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Flux1SchnellArgs {
request: Flux1SchnellRequest {
prompt: "a giant robot fighting a dragon in a futuristic city".to_string(),
num_images: Flux1SchnellNumImages::One,
aspect_ratio: Flux1SchnellAspectRatio::LandscapeSixteenByNine,
},
prompt: "a giant robot fighting a dragon in a futuristic city",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: Flux1SchnellNumImages::One,
aspect_ratio: Flux1SchnellAspectRatio::LandscapeSixteenByNine,
};
let result = enqueue_flux_1_schnell_text_to_image_webhook(args).await?;
@@ -7,14 +7,9 @@ use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct FluxPro11Args<'a, U: IntoUrl> {
pub request: FluxPro11Request,
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
}
#[derive(Clone, Debug)]
pub struct FluxPro11Request {
pub prompt: String,
pub aspect_ratio: FluxPro11AspectRatio,
pub num_images: FluxPro11NumImages,
}
@@ -40,7 +35,7 @@ pub enum FluxPro11NumImages {
}
impl FalRequestCostCalculator for FluxPro11Request {
impl <U: IntoUrl> FalRequestCostCalculator for FluxPro11Args<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.04 per megapixel, billed by rounding up to the nearest
// megapixel. Default image_size values are ~1MP, so 4 cents per image.
@@ -60,16 +55,14 @@ pub async fn enqueue_flux_pro_11_text_to_image_webhook<U: IntoUrl>(
args: FluxPro11Args<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let req = args.request;
let num_images = match req.num_images {
let num_images = match args.num_images {
FluxPro11NumImages::One => 1,
FluxPro11NumImages::Two => 2,
FluxPro11NumImages::Three => 3,
FluxPro11NumImages::Four => 4,
};
let image_size = match req.aspect_ratio {
let image_size = match args.aspect_ratio {
FluxPro11AspectRatio::Square => "square",
FluxPro11AspectRatio::SquareHd => "square_hd",
FluxPro11AspectRatio::LandscapeFourByThree => "landscape_4_3",
@@ -79,7 +72,7 @@ pub async fn enqueue_flux_pro_11_text_to_image_webhook<U: IntoUrl>(
};
let request = FluxPro11TextToImageInput {
prompt: req.prompt,
prompt: args.prompt.to_string(),
num_images: Some(num_images),
image_size: Some(image_size.to_string()),
// Maybe expose
@@ -103,7 +96,7 @@ pub async fn enqueue_flux_pro_11_text_to_image_webhook<U: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args, FluxPro11AspectRatio, FluxPro11NumImages, FluxPro11Request};
use crate::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args, FluxPro11AspectRatio, FluxPro11NumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -116,13 +109,11 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = FluxPro11Args {
request: FluxPro11Request {
prompt: "a giant red panda fighting a dragon in a futuristic city".to_string(),
num_images: FluxPro11NumImages::One,
aspect_ratio: FluxPro11AspectRatio::LandscapeSixteenByNine,
},
prompt: "a giant red panda fighting a dragon in a futuristic city",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: FluxPro11NumImages::One,
aspect_ratio: FluxPro11AspectRatio::LandscapeSixteenByNine,
};
let result = enqueue_flux_pro_11_text_to_image_webhook(args).await?;

Some files were not shown because too many files have changed in this diff Show More