mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 13:02:37 -05:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da8764803 | ||
|
|
a0edec29e7 | ||
|
|
da7082f405 | ||
|
|
33f1829c07 | ||
|
|
5f9941a26a | ||
|
|
24d49a2263 | ||
|
|
961862cdff | ||
|
|
9c7b35671b | ||
|
|
312c603cef | ||
|
|
bdfa31421f | ||
|
|
c78442cf71 | ||
|
|
58b64b694c | ||
|
|
f666e68dcd | ||
|
|
5a7c253189 |
+135
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"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
@@ -1404,6 +1404,22 @@ 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"
|
||||
@@ -11234,6 +11250,7 @@ dependencies = [
|
||||
"async-stripe 0.26.0",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"beeble_client",
|
||||
"billing_artcraft_component",
|
||||
"billing_component",
|
||||
"bootstrap",
|
||||
|
||||
@@ -16,6 +16,7 @@ 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",
|
||||
@@ -244,6 +245,7 @@ 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" }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
$kind: collection
|
||||
name: edit
|
||||
order: 312
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
$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": "newuser2",
|
||||
"password": "newuser2"
|
||||
"username_or_email": "newuser4",
|
||||
"password": "newuser4"
|
||||
}
|
||||
order: 1000
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod beeble_switchx_edit_video;
|
||||
@@ -1,3 +1,4 @@
|
||||
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;
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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?)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod beeble_switchx_edit_video;
|
||||
@@ -1,3 +1,4 @@
|
||||
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;
|
||||
|
||||
+25
-15
@@ -1,3 +1,6 @@
|
||||
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,38 +19,45 @@ pub async fn execute_fal_flux_1_dev(
|
||||
plan: &PlanFalFlux1Dev,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
|
||||
let (webhook_response, outbound_request) = 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(),
|
||||
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: Flux1DevEditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_url: image_url.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_flux_1_dev_edit_image_webhook(args)
|
||||
let resp = enqueue_flux_1_dev_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
// Text-to-image mode
|
||||
let request = Flux1DevRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: Flux1DevRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: plan.num_images.to_t2i(),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_flux_1_dev_text_to_image_webhook(args)
|
||||
let resp = enqueue_flux_1_dev_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
+25
-15
@@ -1,3 +1,6 @@
|
||||
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,38 +19,45 @@ pub async fn execute_fal_flux_1_schnell(
|
||||
plan: &PlanFalFlux1Schnell,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
|
||||
let (webhook_response, outbound_request) = if let Some(image_url) = &plan.maybe_image_url {
|
||||
// Image-to-image (redux) mode
|
||||
let request = Flux1SchnellEditImageRequest {
|
||||
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: Flux1SchnellEditImageRequest {
|
||||
image_url: image_url.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
image_size: plan.edit_image_size,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_flux_1_schnell_edit_image_webhook(args)
|
||||
let resp = enqueue_flux_1_schnell_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
// Text-to-image mode
|
||||
let request = Flux1SchnellRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: Flux1SchnellRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
aspect_ratio: plan.t2i_aspect_ratio,
|
||||
num_images: plan.num_images.to_t2i(),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_flux_1_schnell_text_to_image_webhook(args)
|
||||
let resp = enqueue_flux_1_schnell_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
+11
-5
@@ -1,3 +1,6 @@
|
||||
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,12 +16,14 @@ 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(),
|
||||
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: FluxPro11Request {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: plan.num_images.to_fal(),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
@@ -30,5 +35,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+11
-5
@@ -1,3 +1,6 @@
|
||||
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,12 +16,14 @@ 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(),
|
||||
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: FluxPro11UltraRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: plan.num_images.to_fal(),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
@@ -30,5 +35,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+34
-24
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,46 +21,53 @@ pub async fn execute_fal_gpt_image_1(
|
||||
plan: &PlanFalGptImage1,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
let request = EnqueueGptImage1TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueGptImage1TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gpt_image_1_text_to_image_webhook(args)
|
||||
let resp = enqueue_gpt_image_1_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
let request = EnqueueGptImage1EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
mask_image_url: None,
|
||||
image_size: plan.image_size.map(|s| s.to_edit()),
|
||||
quality: Some(plan.quality.to_edit()),
|
||||
input_fidelity: None,
|
||||
background: None,
|
||||
output_format: None,
|
||||
};
|
||||
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
|
||||
let args = EnqueueGptImage1EditImageArgs {
|
||||
request: EnqueueGptImage1EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
mask_image_url: None,
|
||||
image_size: plan.image_size.map(|s| s.to_edit()),
|
||||
quality: Some(plan.quality.to_edit()),
|
||||
input_fidelity: None,
|
||||
background: None,
|
||||
output_format: None,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gpt_image_1_edit_image_webhook(args)
|
||||
let resp = enqueue_gpt_image_1_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+34
-24
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,45 +21,52 @@ pub async fn execute_fal_gpt_image_1p5(
|
||||
plan: &PlanFalGptImage1p5,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
let request = EnqueueGptImage1p5TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueGptImage1p5TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gpt_image_1p5_text_to_image_webhook(args)
|
||||
let resp = enqueue_gpt_image_1p5_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
let request = EnqueueGptImage1p5EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
mask_image_url: None,
|
||||
image_size: plan.image_size.map(|s| s.to_edit()),
|
||||
background: None,
|
||||
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: EnqueueGptImage1p5EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
mask_image_url: None,
|
||||
image_size: plan.image_size.map(|s| s.to_edit()),
|
||||
background: None,
|
||||
quality: Some(plan.quality.to_edit()),
|
||||
input_fidelity: None,
|
||||
output_format: None,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gpt_image_1p5_image_edit_webhook(args)
|
||||
let resp = enqueue_gpt_image_1p5_image_edit_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
+31
-21
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,43 +21,50 @@ pub async fn execute_fal_gpt_image_2(
|
||||
plan: &PlanFalGptImage2,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
let request = EnqueueGptImage2TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueGptImage2TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gpt_image_2_text_to_image_webhook(args)
|
||||
let resp = enqueue_gpt_image_2_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
let request = EnqueueGptImage2EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueGptImage2EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gpt_image_2_edit_image_webhook(args)
|
||||
let resp = enqueue_gpt_image_2_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+26
-16
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -20,40 +23,47 @@ pub async fn execute_fal_nano_banana(
|
||||
plan: &PlanFalNanoBanana,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
// Text-to-image mode
|
||||
let request = Gemini25FlashTextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: Gemini25FlashTextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
num_images: to_t2i_num_images(plan.num_images),
|
||||
aspect_ratio: plan.t2i_aspect_ratio,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gemini_25_flash_text_to_image_webhook(args)
|
||||
let resp = enqueue_gemini_25_flash_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
// Image-edit mode
|
||||
let request = Gemini25FlashEditRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: Gemini25FlashEditRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: to_edit_num_images(plan.num_images),
|
||||
aspect_ratio: plan.edit_aspect_ratio,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_gemini_25_flash_edit_webhook(args)
|
||||
let resp = enqueue_gemini_25_flash_edit_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+28
-18
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -22,42 +25,49 @@ pub async fn execute_fal_nano_banana_2(
|
||||
plan: &PlanFalNanaBanana2,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
// Text-to-image mode
|
||||
let request = EnqueueNanoBanana2TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueNanoBanana2TextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
num_images: to_t2i_num_images(plan.num_images),
|
||||
resolution: plan.resolution.map(to_t2i_resolution),
|
||||
aspect_ratio: plan.t2i_aspect_ratio,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_nano_banana_2_text_to_image_webhook(args)
|
||||
let resp = enqueue_nano_banana_2_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
// Image-edit mode
|
||||
let request = EnqueueNanoBanana2EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueNanoBanana2EditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_nano_banana_2_edit_image_webhook(args)
|
||||
let resp = enqueue_nano_banana_2_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+28
-18
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -21,42 +24,49 @@ pub async fn execute_fal_nano_banana_pro(
|
||||
plan: &PlanFalNanaBananaPro,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
// Text-to-image mode
|
||||
let request = EnqueueNanoBananaProTextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueNanoBananaProTextToImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
num_images: to_t2i_num_images(plan.num_images),
|
||||
resolution: plan.resolution.map(to_t2i_resolution),
|
||||
aspect_ratio: plan.t2i_aspect_ratio,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_nano_banana_pro_text_to_image_webhook(args)
|
||||
let resp = enqueue_nano_banana_pro_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
// Image-edit mode
|
||||
let request = EnqueueNanoBananaProEditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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: EnqueueNanoBananaProEditImageRequest {
|
||||
prompt: plan.prompt.as_deref().unwrap_or("").to_string(),
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_nano_banana_pro_image_edit_webhook(args)
|
||||
let resp = enqueue_nano_banana_pro_image_edit_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+28
-18
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,39 +21,46 @@ pub async fn execute_fal_seedream_4(
|
||||
plan: &PlanFalSeedream4,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
let request = EnqueueBytedanceSeedreamV4TextToImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: EnqueueBytedanceSeedreamV4TextToImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
num_images: Some(plan.num_images.to_t2i()),
|
||||
max_images: None,
|
||||
image_size: plan.image_size.map(|s| s.to_t2i()),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_bytedance_seedream_v4_text_to_image_webhook(args)
|
||||
let resp = enqueue_bytedance_seedream_v4_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
let request = EnqueueBytedanceSeedreamV4EditImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: EnqueueBytedanceSeedreamV4EditImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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()),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_bytedance_seedream_v4_edit_image_webhook(args)
|
||||
let resp = enqueue_bytedance_seedream_v4_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
+28
-18
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,39 +21,46 @@ pub async fn execute_fal_seedream_4p5(
|
||||
plan: &PlanFalSeedream4p5,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
let request = EnqueueBytedanceSeedreamV4p5TextToImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: EnqueueBytedanceSeedreamV4p5TextToImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
num_images: Some(plan.num_images.to_t2i()),
|
||||
max_images: None,
|
||||
image_size: plan.image_size.map(|s| s.to_t2i()),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args)
|
||||
let resp = enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
let request = EnqueueBytedanceSeedreamV4p5EditImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: EnqueueBytedanceSeedreamV4p5EditImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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()),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_bytedance_seedream_v4p5_edit_image_webhook(args)
|
||||
let resp = enqueue_bytedance_seedream_v4p5_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
+28
-18
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,39 +21,46 @@ pub async fn execute_fal_seedream_5_lite(
|
||||
plan: &PlanFalSeedream5Lite,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let (webhook_response, outbound_request) = if plan.image_urls.is_empty() {
|
||||
let request = EnqueueBytedanceSeedreamV5LiteTextToImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: EnqueueBytedanceSeedreamV5LiteTextToImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
num_images: Some(plan.num_images.to_t2i()),
|
||||
max_images: None,
|
||||
image_size: plan.image_size.map(|s| s.to_t2i()),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args)
|
||||
let resp = enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
} else {
|
||||
let request = EnqueueBytedanceSeedreamV5LiteEditImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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: EnqueueBytedanceSeedreamV5LiteEditImageRequest {
|
||||
prompt: plan.prompt.clone().unwrap_or_default(),
|
||||
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()),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args)
|
||||
let resp = enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
};
|
||||
|
||||
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
|
||||
request_id: webhook_response.request_id,
|
||||
gateway_request_id: webhook_response.gateway_request_id,
|
||||
maybe_outbound_request: Some(outbound_request),
|
||||
}))
|
||||
}
|
||||
|
||||
+8
@@ -1,3 +1,6 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -9,6 +12,11 @@ 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)]
|
||||
|
||||
+15
-7
@@ -1,3 +1,6 @@
|
||||
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,14 +16,18 @@ 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(),
|
||||
duration: plan.duration,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
};
|
||||
|
||||
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
|
||||
|
||||
let args = Kling1p6ProArgs {
|
||||
request: Kling1p6ProRequest {
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
@@ -32,5 +39,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+14
-6
@@ -1,3 +1,6 @@
|
||||
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,13 +16,17 @@ 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(),
|
||||
duration: plan.duration,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
};
|
||||
|
||||
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
|
||||
|
||||
let args = Kling2p1MasterArgs {
|
||||
request: Kling2p1MasterRequest {
|
||||
image_url: plan.image_url.clone(),
|
||||
prompt: plan.prompt.clone(),
|
||||
duration: plan.duration,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
@@ -31,5 +38,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,3 +1,6 @@
|
||||
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,14 +16,18 @@ 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 args = Kling2p1ProArgs {
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
@@ -32,5 +39,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+24
-16
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -22,33 +25,37 @@ pub async fn execute_fal_kling_2_5_turbo_pro(
|
||||
plan: &PlanFalKling2p5TurboPro,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalKling2p5TurboProMode::TextToVideo => {
|
||||
let request = EnqueueKlingV2p5TurboProTextToVideoRequest {
|
||||
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: EnqueueKlingV2p5TurboProTextToVideoRequest {
|
||||
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),
|
||||
},
|
||||
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
|
||||
(enqueue_kling_v2p5_turbo_pro_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalKling2p5TurboProMode::ImageToVideo { image_url, end_image_url } => {
|
||||
let request = EnqueueKlingV2p5TurboProImageToVideoRequest {
|
||||
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: EnqueueKlingV2p5TurboProImageToVideoRequest {
|
||||
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),
|
||||
},
|
||||
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
|
||||
(enqueue_kling_v2p5_turbo_pro_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,6 +65,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+25
-17
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -21,34 +24,38 @@ pub async fn execute_fal_kling_2_6_pro(
|
||||
plan: &PlanFalKling2p6Pro,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalKling2p6ProMode::TextToVideo => {
|
||||
let request = EnqueueKlingV2p6ProTextToVideoRequest {
|
||||
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: EnqueueKlingV2p6ProTextToVideoRequest {
|
||||
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),
|
||||
},
|
||||
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
|
||||
(enqueue_kling_v2p6_pro_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalKling2p6ProMode::ImageToVideo { image_url } => {
|
||||
let request = EnqueueKlingV2p6ProImageToVideoRequest {
|
||||
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: EnqueueKlingV2p6ProImageToVideoRequest {
|
||||
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),
|
||||
},
|
||||
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
|
||||
(enqueue_kling_v2p6_pro_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,6 +65,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -22,38 +25,42 @@ pub async fn execute_fal_kling_3p0_pro(
|
||||
plan: &PlanFalKling3p0Pro,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalKling3p0Mode::TextToVideo => {
|
||||
let request = EnqueueKling3p0ProTextToVideoRequest {
|
||||
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: EnqueueKling3p0ProTextToVideoRequest {
|
||||
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,
|
||||
},
|
||||
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
|
||||
(enqueue_kling_3p0_pro_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => {
|
||||
let request = EnqueueKling3p0ProImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: image_url.clone(),
|
||||
end_image_url: end_image_url.clone(),
|
||||
generate_audio: plan.generate_audio,
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
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: EnqueueKling3p0ProImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: image_url.clone(),
|
||||
end_image_url: end_image_url.clone(),
|
||||
generate_audio: plan.generate_audio,
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
duration: plan.duration.map(to_i2v_duration),
|
||||
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
|
||||
shot_type: None,
|
||||
},
|
||||
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
|
||||
(enqueue_kling_3p0_pro_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,6 +70,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -23,38 +26,42 @@ pub async fn execute_fal_kling_3p0_standard(
|
||||
plan: &PlanFalKling3p0Standard,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalKling3p0Mode::TextToVideo => {
|
||||
let request = EnqueueKling3p0StandardTextToVideoRequest {
|
||||
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: EnqueueKling3p0StandardTextToVideoRequest {
|
||||
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,
|
||||
},
|
||||
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
|
||||
(enqueue_kling_3p0_standard_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => {
|
||||
let request = EnqueueKling3p0StandardImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: image_url.clone(),
|
||||
end_image_url: end_image_url.clone(),
|
||||
generate_audio: plan.generate_audio,
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
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: EnqueueKling3p0StandardImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: image_url.clone(),
|
||||
end_image_url: end_image_url.clone(),
|
||||
generate_audio: plan.generate_audio,
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
duration: plan.duration.map(to_i2v_duration),
|
||||
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
|
||||
shot_type: None,
|
||||
},
|
||||
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
|
||||
(enqueue_kling_3p0_standard_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,6 +71,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+18
-10
@@ -1,3 +1,6 @@
|
||||
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,17 +16,21 @@ 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(),
|
||||
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: Seedance1LiteRequest {
|
||||
image_url: plan.image_url.clone(),
|
||||
end_frame_image_url: plan.end_image_url.clone(),
|
||||
prompt: plan.prompt.clone(),
|
||||
duration: plan.duration,
|
||||
resolution: plan.resolution,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
camera_fixed: false,
|
||||
seed: None,
|
||||
},
|
||||
request,
|
||||
api_key: &fal_client.api_key,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
};
|
||||
@@ -35,5 +42,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+27
-19
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -23,36 +26,40 @@ pub async fn execute_fal_seedance_1p5_pro(
|
||||
plan: &PlanFalSeedance1p5Pro,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalSeedance1p5ProMode::TextToVideo => {
|
||||
let request = EnqueueSeedance1p5ProTextToVideoRequest {
|
||||
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: EnqueueSeedance1p5ProTextToVideoRequest {
|
||||
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,
|
||||
},
|
||||
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
|
||||
(enqueue_seedance_1p5_pro_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalSeedance1p5ProMode::ImageToVideo { image_url, end_image_url } => {
|
||||
let request = EnqueueSeedance1p5ProImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: image_url.clone(),
|
||||
end_image_url: end_image_url.clone(),
|
||||
resolution: plan.resolution.map(to_i2v_resolution),
|
||||
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: EnqueueSeedance1p5ProImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: image_url.clone(),
|
||||
end_image_url: end_image_url.clone(),
|
||||
resolution: plan.resolution.map(to_i2v_resolution),
|
||||
duration: plan.duration.map(to_i2v_duration),
|
||||
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
|
||||
generate_audio: plan.generate_audio,
|
||||
},
|
||||
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
|
||||
(enqueue_seedance_1p5_pro_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -62,6 +69,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+24
-16
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -22,34 +25,38 @@ pub async fn execute_fal_sora_2(
|
||||
plan: &PlanFalSora2,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalSora2Mode::TextToVideo => {
|
||||
// Text-to-video does not support Auto aspect ratio or Auto resolution.
|
||||
let request = EnqueueSora2TextToVideoRequest {
|
||||
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: EnqueueSora2TextToVideoRequest {
|
||||
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),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_sora_2_text_to_video_webhook(args).await
|
||||
(enqueue_sora_2_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalSora2Mode::ImageToVideo { image_url } => {
|
||||
let request = EnqueueSora2ImageToVideoRequest {
|
||||
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: EnqueueSora2ImageToVideoRequest {
|
||||
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),
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_sora_2_image_to_video_webhook(args).await
|
||||
(enqueue_sora_2_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,6 +66,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+24
-16
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -23,34 +26,38 @@ pub async fn execute_fal_sora_2_pro(
|
||||
plan: &PlanFalSora2Pro,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalSora2ProMode::TextToVideo => {
|
||||
// Text-to-video does not support Auto aspect ratio or Auto resolution.
|
||||
let request = EnqueueSora2ProTextToVideoRequest {
|
||||
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: EnqueueSora2ProTextToVideoRequest {
|
||||
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),
|
||||
},
|
||||
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
|
||||
(enqueue_sora_2_pro_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalSora2ProMode::ImageToVideo { image_url } => {
|
||||
let request = EnqueueSora2ProImageToVideoRequest {
|
||||
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: EnqueueSora2ProImageToVideoRequest {
|
||||
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),
|
||||
},
|
||||
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
|
||||
(enqueue_sora_2_pro_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,6 +67,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+26
-16
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -18,42 +21,49 @@ pub async fn execute_fal_veo_2(
|
||||
plan: &PlanFalVeo2,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalVeo2Mode::TextToVideo => {
|
||||
let request = Veo2TextToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
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: Veo2TextToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
duration: plan.duration,
|
||||
aspect_ratio: plan.aspect_ratio.unwrap_or(Veo2AspectRatio::Auto),
|
||||
},
|
||||
request,
|
||||
api_key: &fal_client.api_key,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
};
|
||||
enqueue_veo_2_text_to_video_webhook(args)
|
||||
let resp = enqueue_veo_2_text_to_video_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
|
||||
(resp, outbound)
|
||||
}
|
||||
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: Veo2Request {
|
||||
image_url: image_url.to_string(),
|
||||
prompt: plan.prompt.clone(),
|
||||
duration: plan.duration,
|
||||
},
|
||||
request,
|
||||
api_key: &fal_client.api_key,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
};
|
||||
enqueue_veo_2_image_to_video_webhook(args)
|
||||
let resp = enqueue_veo_2_image_to_video_webhook(args)
|
||||
.await
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
.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),
|
||||
}))
|
||||
}
|
||||
|
||||
+57
-47
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -17,6 +20,60 @@ use fal_client::requests::webhook::video::text::enqueue_veo_3_text_to_video_webh
|
||||
Veo3TextToVideoArgs, Veo3TextToVideoRequest,
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -66,50 +123,3 @@ 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 {
|
||||
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,
|
||||
},
|
||||
api_key: &fal_client.api_key,
|
||||
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 {
|
||||
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,
|
||||
},
|
||||
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,
|
||||
}))
|
||||
}
|
||||
|
||||
+16
-8
@@ -1,3 +1,6 @@
|
||||
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,15 +16,19 @@ 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(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
duration: plan.duration,
|
||||
resolution: plan.resolution,
|
||||
generate_audio: plan.generate_audio,
|
||||
};
|
||||
|
||||
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
|
||||
|
||||
let args = Veo3FastArgs {
|
||||
request: Veo3FastRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
image_url: plan.start_frame_url.clone(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
duration: plan.duration,
|
||||
resolution: plan.resolution,
|
||||
generate_audio: plan.generate_audio,
|
||||
},
|
||||
request,
|
||||
api_key: &fal_client.api_key,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
};
|
||||
@@ -33,5 +40,6 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
+42
-32
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -30,55 +33,61 @@ pub async fn execute_fal_veo_3p1(
|
||||
plan: &PlanFalVeo3p1,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let webhook_response = match &plan.mode {
|
||||
let (webhook_response, outbound_request) = match &plan.mode {
|
||||
FalVeo3p1Mode::TextToVideo => {
|
||||
let request = EnqueueVeo3p1TextToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
duration: plan.duration.map(to_t2v_duration),
|
||||
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
|
||||
resolution: plan.resolution.map(to_t2v_resolution),
|
||||
generate_audio: plan.generate_audio,
|
||||
enhance_prompt: None,
|
||||
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: EnqueueVeo3p1TextToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
duration: plan.duration.map(to_t2v_duration),
|
||||
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
|
||||
resolution: plan.resolution.map(to_t2v_resolution),
|
||||
generate_audio: plan.generate_audio,
|
||||
enhance_prompt: None,
|
||||
negative_prompt: plan.negative_prompt.clone(),
|
||||
seed: None,
|
||||
auto_fix: None,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_veo_3p1_text_to_video_webhook(args).await
|
||||
(enqueue_veo_3p1_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalVeo3p1Mode::ImageToVideo { start_frame_url } => {
|
||||
let request = EnqueueVeo3p1ImageToVideoRequest {
|
||||
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: EnqueueVeo3p1ImageToVideoRequest {
|
||||
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,
|
||||
},
|
||||
request,
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
api_key: &fal_client.api_key,
|
||||
};
|
||||
enqueue_veo_3p1_image_to_video_webhook(args).await
|
||||
(enqueue_veo_3p1_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalVeo3p1Mode::FirstLastFrame { first_frame_url, last_frame_url } => {
|
||||
let request = EnqueueVeo3p1FirstLastFrameImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
first_frame_url: first_frame_url.clone(),
|
||||
last_frame_url: last_frame_url.clone(),
|
||||
duration: plan.duration.map(to_flf_duration),
|
||||
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: EnqueueVeo3p1FirstLastFrameImageToVideoRequest {
|
||||
prompt: plan.prompt.clone(),
|
||||
first_frame_url: first_frame_url.clone(),
|
||||
last_frame_url: last_frame_url.clone(),
|
||||
duration: plan.duration.map(to_flf_duration),
|
||||
aspect_ratio: plan.aspect_ratio.map(to_flf_aspect_ratio),
|
||||
resolution: plan.resolution.map(to_flf_resolution),
|
||||
generate_audio: plan.generate_audio,
|
||||
},
|
||||
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
|
||||
(enqueue_veo_3p1_first_last_frame_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -88,6 +97,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+42
-32
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
@@ -33,55 +36,61 @@ pub async fn execute_fal_veo_3p1_fast(
|
||||
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let inner = &plan.inner;
|
||||
|
||||
let webhook_response = match &inner.mode {
|
||||
let (webhook_response, outbound_request) = match &inner.mode {
|
||||
FalVeo3p1Mode::TextToVideo => {
|
||||
let request = EnqueueVeo3p1FastTextToVideoRequest {
|
||||
prompt: inner.prompt.clone(),
|
||||
duration: inner.duration.map(to_t2v_duration),
|
||||
aspect_ratio: inner.aspect_ratio.map(to_t2v_aspect_ratio),
|
||||
resolution: inner.resolution.map(to_t2v_resolution),
|
||||
generate_audio: inner.generate_audio,
|
||||
enhance_prompt: None,
|
||||
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: EnqueueVeo3p1FastTextToVideoRequest {
|
||||
prompt: inner.prompt.clone(),
|
||||
duration: inner.duration.map(to_t2v_duration),
|
||||
aspect_ratio: inner.aspect_ratio.map(to_t2v_aspect_ratio),
|
||||
resolution: inner.resolution.map(to_t2v_resolution),
|
||||
generate_audio: inner.generate_audio,
|
||||
enhance_prompt: None,
|
||||
negative_prompt: inner.negative_prompt.clone(),
|
||||
seed: None,
|
||||
auto_fix: None,
|
||||
},
|
||||
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
|
||||
(enqueue_veo_3p1_fast_text_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalVeo3p1Mode::ImageToVideo { start_frame_url } => {
|
||||
let request = EnqueueVeo3p1FastImageToVideoRequest {
|
||||
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: EnqueueVeo3p1FastImageToVideoRequest {
|
||||
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,
|
||||
},
|
||||
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
|
||||
(enqueue_veo_3p1_fast_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
FalVeo3p1Mode::FirstLastFrame { first_frame_url, last_frame_url } => {
|
||||
let request = EnqueueVeo3p1FastFirstLastFrameImageToVideoRequest {
|
||||
prompt: inner.prompt.clone(),
|
||||
first_frame_url: first_frame_url.clone(),
|
||||
last_frame_url: last_frame_url.clone(),
|
||||
duration: inner.duration.map(to_flf_duration),
|
||||
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: EnqueueVeo3p1FastFirstLastFrameImageToVideoRequest {
|
||||
prompt: inner.prompt.clone(),
|
||||
first_frame_url: first_frame_url.clone(),
|
||||
last_frame_url: last_frame_url.clone(),
|
||||
duration: inner.duration.map(to_flf_duration),
|
||||
aspect_ratio: inner.aspect_ratio.map(to_flf_aspect_ratio),
|
||||
resolution: inner.resolution.map(to_flf_resolution),
|
||||
generate_audio: inner.generate_audio,
|
||||
},
|
||||
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
|
||||
(enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook(args).await, outbound)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,6 +100,7 @@ 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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -1,3 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -24,6 +27,11 @@ 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)]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,31 @@
|
||||
[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"] }
|
||||
@@ -0,0 +1,24 @@
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod beeble_api_key;
|
||||
@@ -0,0 +1,26 @@
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod beeble_client_error;
|
||||
pub mod beeble_error;
|
||||
pub mod beeble_generic_api_error;
|
||||
pub mod beeble_specific_api_error;
|
||||
@@ -0,0 +1,8 @@
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_utils;
|
||||
|
||||
// Public API
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod requests;
|
||||
pub mod webhook_api;
|
||||
@@ -0,0 +1,126 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod create_upload_url;
|
||||
mod request_types;
|
||||
pub mod upload_bytes;
|
||||
@@ -0,0 +1,13 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod get_job_status;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod create_upload_url;
|
||||
pub mod get_job_status;
|
||||
pub mod start_generation;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
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())
|
||||
}
|
||||
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())
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub(crate) mod handle_error_response;
|
||||
pub(crate) mod request_types;
|
||||
pub mod start_generation;
|
||||
@@ -0,0 +1,55 @@
|
||||
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>,
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod get_test_api_key;
|
||||
@@ -0,0 +1,115 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod beeble_webhook_payload;
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod aspect_ratio_to_artcraft_flux_1_dev;
|
||||
pub mod router_aspect_ratio_to_enums_aspect_ratio;
|
||||
pub mod aspect_ratio_to_artcraft_flux_1_schnell;
|
||||
pub mod aspect_ratio_to_artcraft_flux_pro_1p1;
|
||||
pub mod aspect_ratio_to_artcraft_flux_pro_1p1_ultra;
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterCommonAspectRatio;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio as EnumsCommonAspectRatio;
|
||||
|
||||
/// Map from the router's CommonAspectRatio to the enums crate's CommonAspectRatio.
|
||||
pub fn router_aspect_ratio_to_enums_aspect_ratio(ratio: RouterCommonAspectRatio) -> EnumsCommonAspectRatio {
|
||||
match ratio {
|
||||
RouterCommonAspectRatio::Auto => EnumsCommonAspectRatio::Auto,
|
||||
RouterCommonAspectRatio::Square => EnumsCommonAspectRatio::Square,
|
||||
RouterCommonAspectRatio::WideThreeByTwo => EnumsCommonAspectRatio::WideThreeByTwo,
|
||||
RouterCommonAspectRatio::WideFourByThree => EnumsCommonAspectRatio::WideFourByThree,
|
||||
RouterCommonAspectRatio::WideFiveByFour => EnumsCommonAspectRatio::WideFiveByFour,
|
||||
RouterCommonAspectRatio::WideSixteenByNine => EnumsCommonAspectRatio::WideSixteenByNine,
|
||||
RouterCommonAspectRatio::WideTwentyOneByNine => EnumsCommonAspectRatio::WideTwentyOneByNine,
|
||||
RouterCommonAspectRatio::TallTwoByThree => EnumsCommonAspectRatio::TallTwoByThree,
|
||||
RouterCommonAspectRatio::TallThreeByFour => EnumsCommonAspectRatio::TallThreeByFour,
|
||||
RouterCommonAspectRatio::TallFourByFive => EnumsCommonAspectRatio::TallFourByFive,
|
||||
RouterCommonAspectRatio::TallNineBySixteen => EnumsCommonAspectRatio::TallNineBySixteen,
|
||||
RouterCommonAspectRatio::TallNineByTwentyOne => EnumsCommonAspectRatio::TallNineByTwentyOne,
|
||||
RouterCommonAspectRatio::Wide => EnumsCommonAspectRatio::Wide,
|
||||
RouterCommonAspectRatio::Tall => EnumsCommonAspectRatio::Tall,
|
||||
RouterCommonAspectRatio::Auto2k => EnumsCommonAspectRatio::Auto2k,
|
||||
RouterCommonAspectRatio::Auto3k => EnumsCommonAspectRatio::Auto3k,
|
||||
RouterCommonAspectRatio::Auto4k => EnumsCommonAspectRatio::Auto4k,
|
||||
RouterCommonAspectRatio::SquareHd => EnumsCommonAspectRatio::SquareHd,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod aspect_ratio;
|
||||
pub mod resolution;
|
||||
pub mod quality;
|
||||
pub mod models;
|
||||
pub mod quality;
|
||||
pub mod resolution;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod tauri_image_model_to_enums_model;
|
||||
pub mod tauri_image_model_to_generation_model;
|
||||
pub mod tauri_image_model_to_router_model;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
use enums::common::generation::common_image_model::CommonImageModel;
|
||||
|
||||
use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel;
|
||||
|
||||
/// Map TauriImageModel to the omni endpoint's CommonImageModel (enums crate).
|
||||
/// Returns None for models not yet supported by the omni endpoint.
|
||||
pub fn tauri_image_model_to_enums_model(model: TauriImageModel) -> Option<CommonImageModel> {
|
||||
match model {
|
||||
TauriImageModel::Flux1Dev => Some(CommonImageModel::Flux1Dev),
|
||||
TauriImageModel::Flux1Schnell => Some(CommonImageModel::Flux1Schnell),
|
||||
TauriImageModel::FluxPro1 => Some(CommonImageModel::FluxPro11), // TODO: Might be a slight mismatch
|
||||
TauriImageModel::FluxPro11 => Some(CommonImageModel::FluxPro11),
|
||||
TauriImageModel::FluxPro11Ultra => Some(CommonImageModel::FluxPro11Ultra),
|
||||
TauriImageModel::GptImage1 => Some(CommonImageModel::GptImage1),
|
||||
TauriImageModel::GptImage1p5 => Some(CommonImageModel::GptImage1p5),
|
||||
TauriImageModel::GptImage2 => Some(CommonImageModel::GptImage2),
|
||||
TauriImageModel::NanoBanana => Some(CommonImageModel::NanoBanana),
|
||||
TauriImageModel::NanoBanana2 => Some(CommonImageModel::NanoBanana2),
|
||||
TauriImageModel::NanoBananaPro => Some(CommonImageModel::NanoBananaPro),
|
||||
TauriImageModel::Gemini25Flash => Some(CommonImageModel::NanoBanana),
|
||||
TauriImageModel::Seedream4 => Some(CommonImageModel::Seedream4),
|
||||
TauriImageModel::Seedream4p5 => Some(CommonImageModel::Seedream4p5),
|
||||
TauriImageModel::Seedream5Lite => Some(CommonImageModel::Seedream5Lite),
|
||||
// Not accounted for yet
|
||||
TauriImageModel::GrokImage => None,
|
||||
TauriImageModel::Recraft3 => None,
|
||||
TauriImageModel::Midjourney => None,
|
||||
TauriImageModel::FluxProKontextMax => None,
|
||||
TauriImageModel::QwenEdit2511Angles => None,
|
||||
TauriImageModel::Flux2LoraAngles => None,
|
||||
TauriImageModel::FluxDevJuggernaut => None,
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
|
||||
/// Map TauriImageModel to GenerationModel for frontend events.
|
||||
pub fn tauri_image_model_to_generation_model(model: TauriImageModel) -> GenerationModel {
|
||||
match model {
|
||||
TauriImageModel::Flux1Dev => GenerationModel::Flux1Dev,
|
||||
TauriImageModel::Flux1Schnell => GenerationModel::Flux1Schnell,
|
||||
TauriImageModel::FluxPro11 => GenerationModel::FluxPro11,
|
||||
TauriImageModel::FluxPro11Ultra => GenerationModel::FluxPro11Ultra,
|
||||
TauriImageModel::GrokImage => GenerationModel::GrokImage,
|
||||
TauriImageModel::Recraft3 => GenerationModel::Flux1Dev, // Fallback
|
||||
TauriImageModel::GptImage1 => GenerationModel::GptImage1,
|
||||
TauriImageModel::GptImage1p5 => GenerationModel::GptImage1p5,
|
||||
TauriImageModel::GptImage2 => GenerationModel::GptImage2,
|
||||
TauriImageModel::Gemini25Flash => GenerationModel::NanoBanana,
|
||||
TauriImageModel::NanoBanana => GenerationModel::NanoBanana,
|
||||
TauriImageModel::NanoBanana2 => GenerationModel::NanoBanana2,
|
||||
TauriImageModel::NanoBananaPro => GenerationModel::NanoBananaPro,
|
||||
TauriImageModel::Seedream4 => GenerationModel::Seedream4,
|
||||
TauriImageModel::Seedream4p5 => GenerationModel::Seedream4p5,
|
||||
TauriImageModel::Seedream5Lite => GenerationModel::Seedream5Lite,
|
||||
TauriImageModel::Midjourney => GenerationModel::Flux1Dev, // Fallback
|
||||
TauriImageModel::FluxProKontextMax => GenerationModel::FluxProKontextMax,
|
||||
TauriImageModel::QwenEdit2511Angles => GenerationModel::QwenEdit2511Angles,
|
||||
TauriImageModel::Flux2LoraAngles => GenerationModel::Flux2LoraAngles,
|
||||
TauriImageModel::FluxDevJuggernaut => GenerationModel::FluxDevJuggernaut,
|
||||
TauriImageModel::FluxPro1 => GenerationModel::FluxPro1,
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
use artcraft_router::api::common_image_model::CommonImageModel;
|
||||
|
||||
use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel;
|
||||
|
||||
/// Map TauriImageModel to the artcraft_router's CommonImageModel.
|
||||
/// Returns None for models not supported by the router (Grok, Midjourney, etc.).
|
||||
pub fn tauri_image_model_to_router_model(model: TauriImageModel) -> Option<CommonImageModel> {
|
||||
match model {
|
||||
TauriImageModel::Flux1Dev => Some(CommonImageModel::Flux1Dev), // Text-to-Image
|
||||
TauriImageModel::Flux1Schnell => Some(CommonImageModel::Flux1Schnell), // Text-to-Image
|
||||
TauriImageModel::FluxPro1 => Some(CommonImageModel::FluxPro11), // TODO: Might be a slight mismatch
|
||||
TauriImageModel::FluxPro11 => Some(CommonImageModel::FluxPro11), // Text-to-Image
|
||||
TauriImageModel::FluxPro11Ultra => Some(CommonImageModel::FluxPro11Ultra), // Text-to-Image
|
||||
TauriImageModel::GptImage1 => Some(CommonImageModel::GptImage1), // Text-to-Image
|
||||
TauriImageModel::GptImage1p5 => Some(CommonImageModel::GptImage1p5), // Text-to-Image
|
||||
TauriImageModel::GptImage2 => Some(CommonImageModel::GptImage2), // Text-to-Image
|
||||
TauriImageModel::NanoBanana => Some(CommonImageModel::NanoBanana), // Text-to-Image
|
||||
TauriImageModel::NanoBanana2 => Some(CommonImageModel::NanoBanana2), // Text-to-Image
|
||||
TauriImageModel::NanoBananaPro => Some(CommonImageModel::NanoBananaPro), // Text-to-Image
|
||||
TauriImageModel::Gemini25Flash => Some(CommonImageModel::NanoBanana), // Text-to-Image
|
||||
TauriImageModel::Seedream4 => Some(CommonImageModel::Seedream4), // Text-to-Image
|
||||
TauriImageModel::Seedream4p5 => Some(CommonImageModel::Seedream4p5), // Text-to-Image
|
||||
TauriImageModel::Seedream5Lite => Some(CommonImageModel::Seedream5Lite), // Text-to-Image
|
||||
TauriImageModel::QwenEdit2511Angles => Some(CommonImageModel::QwenEdit2511Angles),
|
||||
TauriImageModel::Flux2LoraAngles => Some(CommonImageModel::Flux2LoraAngles),
|
||||
// Not accounted for yet
|
||||
TauriImageModel::GrokImage => None,
|
||||
TauriImageModel::Recraft3 => None,
|
||||
TauriImageModel::Midjourney => None,
|
||||
TauriImageModel::FluxProKontextMax => None,
|
||||
TauriImageModel::FluxDevJuggernaut => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod image;
|
||||
@@ -1 +1,2 @@
|
||||
pub mod resolution_to_artcraft_nano_banana_pro;
|
||||
pub mod resolution_to_artcraft_nano_banana_pro;
|
||||
pub mod router_resolution_to_enums_resolution;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
use artcraft_router::api::common_resolution::CommonResolution as RouterCommonResolution;
|
||||
use enums::common::generation::common_resolution::CommonResolution as EnumsCommonResolution;
|
||||
|
||||
/// Map from the router's CommonResolution to the enums crate's CommonResolution.
|
||||
pub fn router_resolution_to_enums_resolution(res: RouterCommonResolution) -> EnumsCommonResolution {
|
||||
match res {
|
||||
RouterCommonResolution::OneK => EnumsCommonResolution::OneK,
|
||||
RouterCommonResolution::TwoK => EnumsCommonResolution::TwoK,
|
||||
RouterCommonResolution::ThreeK => EnumsCommonResolution::ThreeK,
|
||||
RouterCommonResolution::FourK => EnumsCommonResolution::FourK,
|
||||
RouterCommonResolution::HalfK => EnumsCommonResolution::HalfK,
|
||||
RouterCommonResolution::FourEightyP => EnumsCommonResolution::FourEightyP,
|
||||
RouterCommonResolution::SevenTwentyP => EnumsCommonResolution::SevenTwentyP,
|
||||
RouterCommonResolution::TenEightyP => EnumsCommonResolution::TenEightyP,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageSuccessResponse;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::EnqueueEditImageSuccessResponse;
|
||||
use crate::core::commands::response::shorthand::ResponseOrErrorMessage;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_flux_kontext_edit::handle_artcraft_flux_kontext_edit;
|
||||
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_gpt_image_1_edit::handle_artcraft_gpt_image_1_edit;
|
||||
use crate::core::commands::enqueue::image_edit::artcraft::handle_image_edit_artcraft_via_router::handle_image_edit_artcraft_via_router;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
|
||||
use crate::core::commands::deprecated::image_edit::artcraft::handle_artcraft_flux_kontext_edit::handle_artcraft_flux_kontext_edit;
|
||||
use crate::core::commands::deprecated::image_edit::artcraft::handle_artcraft_gpt_image_1_edit::handle_artcraft_gpt_image_1_edit;
|
||||
use crate::core::commands::deprecated::image_edit::artcraft::handle_image_edit_artcraft_via_router::handle_image_edit_artcraft_via_router;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as DesktopCommonAspectRatio;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{
|
||||
EditImageResolution, EditImageSize, EnqueueEditImageCommand,
|
||||
};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
+3
-3
@@ -2,9 +2,9 @@ use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRa
|
||||
use crate::core::api_adapters::quality::common_quality::CommonQuality;
|
||||
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::artcraft::handle_image_edit_artcraft::handle_image_edit_artcraft;
|
||||
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::core::commands::enqueue::image_edit::sora::handle_image_edit_sora::handle_image_edit_sora;
|
||||
use crate::core::commands::deprecated::image_edit::artcraft::handle_image_edit_artcraft::handle_image_edit_artcraft;
|
||||
use crate::core::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::core::commands::deprecated::image_edit::sora::handle_image_edit_sora::handle_image_edit_sora;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::ImageEditModel;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::ImageEditModel;
|
||||
|
||||
pub fn image_edit_model_to_model_type(model: ImageEditModel) -> CommonModelType {
|
||||
match model {
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
|
||||
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::core::commands::enqueue::image_edit::sora::handle_sora_gpt_image_1_edit::handle_sora_gpt_image_1_edit;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
|
||||
use crate::core::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::core::commands::deprecated::image_edit::sora::handle_sora_gpt_image_1_edit::handle_sora_gpt_image_1_edit;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageSize, EnqueueEditImageCommand, EnqueueEditImageErrorType};
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageSize, EnqueueEditImageCommand, EnqueueEditImageErrorType};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::core::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_flux_dev_juggernaut_inpaint::handle_artcraft_flux_dev_juggernaut_inpaint;
|
||||
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_flux_pro_1_inpaint::handle_artcraft_flux_pro_1_inpaint;
|
||||
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_flux_pro_kontext_inpaint::handle_artcraft_flux_pro_kontext_inpaint;
|
||||
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_nano_banana_inpaint::handle_artcraft_nano_banana_inpaint;
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::{EnqueueInpaintImageCommand, ImageInpaintModel};
|
||||
use crate::core::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_dev_juggernaut_inpaint::handle_artcraft_flux_dev_juggernaut_inpaint;
|
||||
use crate::core::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_pro_1_inpaint::handle_artcraft_flux_pro_1_inpaint;
|
||||
use crate::core::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_pro_kontext_inpaint::handle_artcraft_flux_pro_kontext_inpaint;
|
||||
use crate::core::commands::deprecated::image_inpaint::artcraft::handle_artcraft_nano_banana_inpaint::handle_artcraft_nano_banana_inpaint;
|
||||
use crate::core::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::{EnqueueInpaintImageCommand, ImageInpaintModel};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
use crate::core::api_adapters::quality::common_quality::CommonQuality;
|
||||
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_inpaint_artcraft::handle_inpaint_artcraft;
|
||||
use crate::core::commands::enqueue::image_inpaint::inpaint_models::image_inpaint_model_to_model_type;
|
||||
use crate::core::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::core::commands::deprecated::image_inpaint::artcraft::handle_inpaint_artcraft::handle_inpaint_artcraft;
|
||||
use crate::core::commands::deprecated::image_inpaint::inpaint_models::image_inpaint_model_to_model_type;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::ImageInpaintModel;
|
||||
use crate::core::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::ImageInpaintModel;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
|
||||
pub fn image_inpaint_model_to_model_type(model: ImageInpaintModel) -> CommonModelType {
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod image_edit;
|
||||
pub mod image_inpaint;
|
||||
pub mod text_to_image;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user