Compare commits

...
Author SHA1 Message Date
Brandon Thomas 3b88979859 remove print statements 2026-04-08 10:22:56 -04:00
Brandon Thomas 20f16236e6 transcode video 2026-04-08 10:21:37 -04:00
Brandon Thomas df3dac8982 ffmpeg transcode 2026-04-08 10:00:10 -04:00
bflatastic ccea741443 fix safari mobile api calls/login not working since its third party 2026-04-08 14:45:29 +07:00
Brandon Thomas 2b9882d2d7 fix tests 2026-04-08 00:03:47 -04:00
bflatastic 55cab30dec comment out web generation access for now 2026-04-08 10:06:22 +07:00
Brandon Thomas a2f204b1be additional context for pages (#1317) 2026-04-07 19:07:49 -04:00
Brandon Thomas 12e4ff7a29 Omni Gen Video Models (#1316) 2026-04-07 17:05:52 -04:00
Brandon Thomas 8b73569238 omni image models (#1314) 2026-04-07 03:38:23 -04:00
Brandon Thomas badabe57eb fix inference-job 2026-04-07 00:49:18 -04:00
Brandon Thomas 2e62eef85b Bt cleanup web (#1313) 2026-04-07 00:43:21 -04:00
Brandon Thomas fd9f002615 avt cleanup (fix paging) (#1312) 2026-04-07 00:29:18 -04:00
Brandon Thomas a0e50067bb stop the session and session jobs alert pages by fixing error handling (#1310)
- was still getting dozens of pages from these two endpoints
- fixed the error chain so this is semantically correct
- improved code quality of legacy code/endpoints
2026-04-06 20:56:28 -04:00
Brandon Thomas 2b97694ebf omni gen for a few image models (#1308) 2026-04-06 04:10:14 -04:00
Brandon Thomas 0f6998cb6b bump artcraft 0.23 -> 0.24 (#1307) 2026-04-05 17:57:42 -04:00
Brandon Thomas 8076d1b830 fix builds (#1306) 2026-04-05 02:55:08 -04:00
Brandon Thomas 9842734ce7 more cleanup of deprecated or orphaned code (#1305) 2026-04-05 02:35:55 -04:00
Brandon Thomas 72761e11f3 delete public enums crate - old tech debt / cruft (#1304)
Ages ago I tried to hide what we were doing from "Uberduck". That was
stupid. As if it mattered.

Never again.
2026-04-04 23:04:32 -04:00
bflatastic 60c97ff312 characters modal bug overflowing on very small screens 2026-04-05 06:40:55 +07:00
BFlat 03ed457cb5 remove character limits on seedance 2 and nano banana models (#1303) 2026-04-05 06:24:30 +07:00
Brandon Thomas e89229896f character descriptions max length (#1302) 2026-04-04 16:50:44 -04:00
Brandon Thomas 56c1b57cce bump website version to 0.23 2026-04-03 19:16:02 -04:00
bflatastic 122c1d6688 mention text area small styling fix 2026-04-04 05:40:52 +07:00
Brandon Thomas 6768208af3 bump artcraft 0.22 -> 0.23 2026-04-03 18:31:21 -04:00
Brandon Thomas 99b5a9774a Frontend failure text updates (#1300) 2026-04-03 18:20:06 -04:00
BFlat f55a4c722e mentions text area (#1299)
- content editable div instead of textarea when mentions is needed
- no more overlayed text areas and misaligned cursors
2026-04-04 05:16:14 +07:00
Tanish Manku f337d630e3 Fix: Allow asset.localhost in CSP for Windows drag-drop (#1298)
On Windows production builds, convertFileSrc() generates
https://asset.localhost/... URLs. The connect-src CSP only had "asset:"
(covering asset:// on macOS/Linux) but not https://asset.localhost,
causing the fetch() in GlobalFileDropHandler to be silently blocked —
overlay appeared but no modal opened on drop.
2026-04-03 14:46:32 -07:00
396 changed files with 14213 additions and 3880 deletions
+5 -1
View File
@@ -5,7 +5,7 @@
- Two spaces for indentation (not four)
- No minimum supported Rust version; use latest stable features freely
- `#[macro_use] extern crate serde_derive;` is used in binary crates; library crates use `use serde_derive::{Serialize, Deserialize};`
- Prefer `log` crate macros (`info!`, `warn!`, `error!`) over `println!`
- Never use `println!` or `eprintln!` outside of tests; use `log` crate macros (`info!`, `warn!`, `error!`, `debug!`, `trace!`) instead
- Use `anyhow::Result` / `AnyhowResult` for fallible functions in application code
- Enum Display/Debug: use `impl_enum_display_and_debug_using_to_str!` macro, not `derive_more::Display` (CLion doesn't understand it)
- Fields that are optional use the `maybe_` prefix: `maybe_creator_user_token`, `maybe_prompt_token`
@@ -51,6 +51,10 @@ then the story, then the fine print.
- Group imports: std, external crates, internal workspace crates, `crate::` imports
- Use fully qualified paths for one-off references; `use` for repeated references
- Prefer specific imports over wildcards, except in `api_doc.rs` and test modules
- When two crates export a type with the same name, alias with a suffix describing the source crate:
`use enums::...::CommonResolution as CommonResolutionEnum;`
`use artcraft_router::...::CommonResolution as CommonResolutionRouter;`
This avoids long fully-qualified paths in function signatures and bodies.
## TypeScript / Frontend
+2
View File
@@ -60,6 +60,8 @@ artcraft/
- Actix-web for HTTP services
- SQLx for MySQL and SQLite
- A mix of wreq and reqwest for Rust HTTP clients
- Never use `println!` or `eprintln!` outside of tests; use `log` crate macros instead
- When two crates export the same type name, alias imports with a suffix: `use foo::Bar as BarFoo;`
- TypeScript with Nx, React, Vite, Zustand, and Three.js
- Use two spaces for indentation
Generated
+16 -39
View File
@@ -508,7 +508,6 @@ version = "0.0.1"
dependencies = [
"anyhow",
"chrono",
"config",
"datetimes",
"dotenv",
"easyenv",
@@ -523,6 +522,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"tokens",
"tokio",
@@ -727,7 +727,6 @@ dependencies = [
"anyhow",
"chrono",
"enums",
"enums_public",
"serde",
"serde_derive",
"serde_json",
@@ -748,7 +747,6 @@ dependencies = [
"cloudflare_errors",
"cookie 0.18.1",
"enums",
"enums_public",
"errors",
"log",
"mimetypes",
@@ -3133,7 +3131,6 @@ dependencies = [
"bucket_paths",
"clap 4.5.60",
"cloud_storage",
"config",
"easyenv",
"elasticsearch",
"enums",
@@ -3144,6 +3141,7 @@ dependencies = [
"mimetypes",
"mysql_queries",
"rand 0.9.2",
"shared_env_var_config",
"sqlx",
"storyteller_root",
"tokens",
@@ -3159,7 +3157,6 @@ dependencies = [
"bucket_paths",
"clap 4.5.60",
"cloud_storage",
"config",
"easyenv",
"enums",
"errors",
@@ -3167,6 +3164,7 @@ dependencies = [
"log",
"mimetypes",
"mysql_queries",
"shared_env_var_config",
"sqlx",
"tokens",
"tokio",
@@ -3438,6 +3436,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"storyteller_root",
"subprocess",
@@ -3445,6 +3444,7 @@ dependencies = [
"tempdir",
"tokens",
"tokio",
"url",
"workspace-build-acceleration",
"zip 0.6.6",
]
@@ -3610,7 +3610,6 @@ version = "0.0.1"
dependencies = [
"bucket_paths",
"clap 4.5.60",
"config",
"easyenv",
"elasticsearch",
"elasticsearch_schema",
@@ -3622,6 +3621,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"storyteller_root",
"strum",
@@ -3701,6 +3701,7 @@ dependencies = [
"serde_derive",
"serde_json",
"server_environment",
"shared_env_var_config",
"sqlx",
"workspace-build-acceleration",
]
@@ -3794,20 +3795,6 @@ dependencies = [
"workspace-build-acceleration",
]
[[package]]
name = "enums_public"
version = "0.0.1"
dependencies = [
"enums",
"serde",
"serde_derive",
"serde_json",
"strum",
"toml 1.0.7+spec-1.1.0",
"utoipa",
"workspace-build-acceleration",
]
[[package]]
name = "env"
version = "1.0.1"
@@ -3904,7 +3891,6 @@ dependencies = [
"anyhow",
"bootstrap",
"chrono",
"config",
"dotenv",
"easyenv",
"elasticsearch",
@@ -3923,6 +3909,7 @@ dependencies = [
"serde_derive",
"serde_json",
"server_environment",
"shared_env_var_config",
"sqlx",
"storyteller_root",
"tokens",
@@ -5764,6 +5751,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"subprocess",
"subprocess_common",
@@ -6101,7 +6089,6 @@ dependencies = [
name = "jwt_signer"
version = "0.0.1"
dependencies = [
"errors",
"hmac 0.10.1",
"jwt",
"sha2 0.9.9",
@@ -6146,13 +6133,13 @@ dependencies = [
name = "kubectl-pod-cleanup"
version = "0.0.1"
dependencies = [
"config",
"easyenv",
"errors",
"log",
"once_cell",
"rand 0.8.5",
"regex",
"shared_env_var_config",
"workspace-build-acceleration",
]
@@ -6856,7 +6843,6 @@ dependencies = [
"bucket_paths",
"clap 4.5.60",
"cloud_storage",
"config",
"easyenv",
"enums",
"errors",
@@ -6867,6 +6853,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"storyteller_root",
"strum",
@@ -8657,16 +8644,6 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "prompt_engineering"
version = "0.1.0"
dependencies = [
"once_cell",
"regex",
"speculoos",
"workspace-build-acceleration",
]
[[package]]
name = "prost"
version = "0.12.6"
@@ -10197,7 +10174,6 @@ dependencies = [
"bucket_paths",
"cloud_storage",
"concurrency",
"config",
"easyenv",
"enums",
"errors",
@@ -11257,7 +11233,6 @@ dependencies = [
"elasticsearch",
"elasticsearch_schema",
"enums",
"enums_public",
"errors",
"fal_client",
"ffmpeg_utils",
@@ -11288,7 +11263,6 @@ dependencies = [
"pager",
"password",
"primitives",
"prompt_engineering",
"r2d2",
"rand 0.9.2",
"redis 1.0.2",
@@ -12888,6 +12862,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"subprocess",
"tempdir",
@@ -12921,6 +12896,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"tempdir",
"tokio",
@@ -13377,7 +13353,6 @@ dependencies = [
"bucket_paths",
"cloud_storage",
"concurrency",
"config",
"easyenv",
"errors",
"ffmpeg_utils",
@@ -13459,6 +13434,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"subprocess",
"tempdir",
@@ -13489,6 +13465,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"shared_env_var_config",
"sqlx",
"subprocess",
"tempdir",
@@ -14760,7 +14737,6 @@ dependencies = [
"bucket_paths",
"cloud_storage",
"concurrency",
"config",
"easyenv",
"enums",
"errors",
@@ -14773,6 +14749,7 @@ dependencies = [
"serde_derive",
"serde_json",
"server_environment",
"shared_env_var_config",
"sqlx",
"tokens",
"tokio",
-3
View File
@@ -94,7 +94,6 @@ members = [
"crates/lib/deprecated/http_server_common", # TODO(bt, 2023-03-20): Move useful pieces to actix_helpers
"crates/lib/deprecated/image_algorithms",
"crates/lib/deprecated/machine_learning/ml_weights_registry",
"crates/lib/deprecated/machine_learning/prompt_engineering",
"crates/lib/deprecated/subprocess_common",
"crates/lib/deprecated/tts_common",
@@ -117,7 +116,6 @@ members = [
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
"crates/schema/public/composite_identifiers",
"crates/schema/public/enums",
"crates/schema/public/enums_public",
"crates/schema/public/reusable_types", # TODO(bt, 2022-12-21): Deprecate
"crates/schema/public/tokens",
@@ -238,7 +236,6 @@ cookie_store = { path = "crates/lib/cookie_store" }
crockford = { path = "crates/lib/data/crockford" }
easyenv = { path = "crates/lib/easyenv" }
enums = { path = "crates/schema/public/enums" }
enums_public = { path = "crates/schema/public/enums_public" }
errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/api_clients/fal_client" }
ffmpeg_utils = { path = "crates/lib/ffmpeg_utils" }
@@ -0,0 +1,22 @@
$kind: http-request
name: Add Wallet Banked Balance
url: "{{api_host}}/v1/moderation/wallet/wallet_2pz2h3m2bdfvna7m3b92ccwwr/add_ba\
nked_balance"
method: POST
headers:
- key: Accept
value: application/json
- key: Session
value: ""
disabled: true
- key: Session
value: ""
disabled: true
body:
type: json
content: |
{
"title": "test alert from thread 2",
"description": "testing testing test"
}
order: 3365370184335612
@@ -0,0 +1,21 @@
$kind: http-request
name: Create Wallet for User
url: "{{api_host}}/v1/moderation/wallet/create_for_user"
method: POST
headers:
- key: Accept
value: application/json
- key: Session
value: ""
disabled: true
- key: Session
value: ""
disabled: true
body:
type: json
content: |
{
"user_token": "user_jg7ng4kdt2x5y", // newuser946
"payments_namespace": "artcraft"
}
order: 4100053395280358
@@ -21,7 +21,6 @@ workspace-build-acceleration.workspace = true
# Internal
enums.workspace = true
enums_public.workspace = true
tokens.workspace = true
# External
@@ -89,7 +89,7 @@ mod tests {
#[test]
fn test_serialization_text_to_image() {
let request = EstimateImageCostRequest {
model: CommonImageModel::NanaBananaPro,
model: CommonImageModel::NanoBananaPro,
provider: GenerationProvider::Artcraft,
generation_mode: GenerationMode::TextToImage,
aspect_ratio: None,
@@ -103,7 +103,7 @@ mod tests {
#[test]
fn test_serialization_image_edit() {
let request = EstimateImageCostRequest {
model: CommonImageModel::NanaBananaPro,
model: CommonImageModel::NanoBananaPro,
provider: GenerationProvider::Artcraft,
generation_mode: GenerationMode::ImageEdit { count: 2 },
aspect_ratio: None,
@@ -28,7 +28,7 @@ pub struct GenerateKling16ProImageToVideoRequest {
pub duration: Option<GenerateKling16ProDuration>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateKling16ProAspectRatio {
/// 16:9 aspect ratio
@@ -39,7 +39,7 @@ pub enum GenerateKling16ProAspectRatio {
Square,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateKling16ProDuration {
FiveSeconds,
@@ -24,7 +24,7 @@ pub struct GenerateKling21MasterImageToVideoRequest {
pub duration: Option<GenerateKling21MasterDuration>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateKling21MasterAspectRatio {
/// 16:9 aspect ratio
@@ -35,7 +35,7 @@ pub enum GenerateKling21MasterAspectRatio {
Square,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateKling21MasterDuration {
FiveSeconds,
@@ -28,7 +28,7 @@ pub struct GenerateKling21ProImageToVideoRequest {
pub duration: Option<GenerateKling21ProDuration>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateKling21ProAspectRatio {
/// 16:9 aspect ratio
@@ -39,7 +39,7 @@ pub enum GenerateKling21ProAspectRatio {
Square,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateKling21ProDuration {
FiveSeconds,
@@ -31,14 +31,14 @@ pub struct GenerateSeedance10LiteImageToVideoRequest {
pub aspect_ratio: Option<GenerateSeedance10LiteAspectRatio>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateSeedance10LiteResolution {
FourEightyP,
SevenTwentyP,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateSeedance10LiteDuration {
FiveSeconds,
@@ -46,7 +46,7 @@ pub enum GenerateSeedance10LiteDuration {
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateSeedance10LiteAspectRatio {
Auto,
@@ -24,7 +24,7 @@ pub struct GenerateVeo2ImageToVideoRequest {
pub duration: Option<GenerateVeo2Duration>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo2AspectRatio {
Auto,
@@ -33,7 +33,7 @@ pub enum GenerateVeo2AspectRatio {
TallNineSixteen, // 9:16
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo2Duration {
FiveSeconds,
@@ -27,14 +27,14 @@ pub struct GenerateVeo3FastImageToVideoRequest {
pub generate_audio: Option<bool>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo3FastResolution {
SevenTwentyP, // 720p
TenEightyP, // 1080p
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo3FastDuration {
EightSeconds,
@@ -37,7 +37,7 @@ pub struct GenerateVeo3ImageToVideoRequest {
// pub auto_fix: Option<bool>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo3AspectRatio {
WideSixteenNine, // 16:9
@@ -45,14 +45,14 @@ pub enum GenerateVeo3AspectRatio {
Square, // 1:1
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo3Resolution {
SevenTwentyP, // 720p
TenEightyP, // 1080p
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateVeo3Duration {
EightSeconds,
@@ -41,14 +41,14 @@ pub struct Kling2p5TurboProMultiFunctionVideoGenRequest {
pub aspect_ratio: Option<Kling2p5TurboProMultiFunctionVideoGenAspectRatio>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling2p5TurboProMultiFunctionVideoGenDuration {
FiveSeconds,
TenSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling2p5TurboProMultiFunctionVideoGenAspectRatio {
Square,
@@ -45,14 +45,14 @@ pub struct Kling2p6ProMultiFunctionVideoGenRequest {
pub aspect_ratio: Option<Kling2p6ProMultiFunctionVideoGenAspectRatio>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling2p6ProMultiFunctionVideoGenDuration {
FiveSeconds,
TenSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling2p6ProMultiFunctionVideoGenAspectRatio {
Square,
@@ -35,14 +35,14 @@ pub struct Sora2MultiFunctionVideoGenRequest {
pub aspect_ratio: Option<Sora2MultiFunctionVideoGenAspectRatio>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Sora2MultiFunctionVideoGenResolution {
Auto,
SevenTwentyP,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Sora2MultiFunctionVideoGenDuration {
FourSeconds,
@@ -50,7 +50,7 @@ pub enum Sora2MultiFunctionVideoGenDuration {
TwelveSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Sora2MultiFunctionVideoGenAspectRatio {
Auto,
@@ -35,7 +35,7 @@ pub struct Sora2ProMultiFunctionVideoGenRequest {
pub aspect_ratio: Option<Sora2ProMultiFunctionVideoGenAspectRatio>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Sora2ProMultiFunctionVideoGenResolution {
Auto,
@@ -43,7 +43,7 @@ pub enum Sora2ProMultiFunctionVideoGenResolution {
TenEightyP,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Sora2ProMultiFunctionVideoGenDuration {
FourSeconds,
@@ -51,7 +51,7 @@ pub enum Sora2ProMultiFunctionVideoGenDuration {
TwelveSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Sora2ProMultiFunctionVideoGenAspectRatio {
Auto,
@@ -60,7 +60,7 @@ pub struct Veo3p1FastMultiFunctionVideoGenRequest {
pub auto_fix: Option<bool>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Veo3p1FastMultiFunctionVideoGenDuration {
FourSeconds,
@@ -68,7 +68,7 @@ pub enum Veo3p1FastMultiFunctionVideoGenDuration {
EightSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Veo3p1FastMultiFunctionVideoGenAspectRatio {
Auto,
@@ -76,7 +76,7 @@ pub enum Veo3p1FastMultiFunctionVideoGenAspectRatio {
NineBySixteen,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Veo3p1FastMultiFunctionVideoGenResolution {
SevenTwentyP,
@@ -60,7 +60,7 @@ pub struct Veo3p1MultiFunctionVideoGenRequest {
pub auto_fix: Option<bool>,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Veo3p1MultiFunctionVideoGenDuration {
FourSeconds,
@@ -68,7 +68,7 @@ pub enum Veo3p1MultiFunctionVideoGenDuration {
EightSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Veo3p1MultiFunctionVideoGenAspectRatio {
Auto,
@@ -76,7 +76,7 @@ pub enum Veo3p1MultiFunctionVideoGenAspectRatio {
NineBySixteen,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Veo3p1MultiFunctionVideoGenResolution {
SevenTwentyP,
@@ -124,7 +124,7 @@ pub struct OmniGenImageModelDetails {
impl Default for OmniGenImageModelDetails {
fn default() -> Self {
Self {
model: CommonImageModel::NanaBananaPro,
model: CommonImageModel::NanoBananaPro,
full_name: None,
text_prompt_supported: None,
text_prompt_max_length: None,
@@ -26,7 +26,6 @@ workspace-build-acceleration.workspace = true
artcraft_api_defs.workspace = true
cloudflare_errors.workspace = true
enums.workspace = true
enums_public.workspace = true
errors.workspace = true
uuid_utils.workspace = true
mimetypes.workspace = true
@@ -15,7 +15,7 @@ use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::common::visibility::Visibility;
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use enums::by_table::model_weights::weights_types::WeightsType;
use log::debug;
use serde_derive::Deserialize;
use tokens::tokens::batch_generations::BatchGenerationToken;
@@ -193,7 +193,7 @@ pub struct GetMediaFileModeratorFields {
#[derive(Deserialize, Debug)]
pub struct GetMediaFileModelInfo {
pub weight_token: ModelWeightToken,
pub weight_type: PublicWeightsType,
pub weight_type: WeightsType,
pub weight_category: WeightsCategory,
pub title: String,
@@ -8,7 +8,6 @@ use crate::utils::filter_bad_response::filter_bad_response;
use crate::utils::http_get_anonymous::http_get_anonymous;
use chrono::{DateTime, Utc};
use enums::common::generation_provider::GenerationProvider;
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
@@ -7,7 +7,6 @@ use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
use crate::utils::filter_bad_response::filter_bad_response;
use crate::utils::http_get_anonymous::http_get_anonymous;
use chrono::{DateTime, Utc};
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
@@ -8,7 +8,6 @@ use crate::utils::filter_bad_response::filter_bad_response;
use crate::utils::http_get_anonymous::http_get_anonymous;
use chrono::{DateTime, Utc};
use enums::common::generation_provider::GenerationProvider;
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
@@ -5,7 +5,6 @@ use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
use crate::utils::filter_bad_response::filter_bad_response;
use crate::utils::http_get_anonymous::http_get_anonymous;
use chrono::{DateTime, Utc};
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
@@ -5,7 +5,6 @@ use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
use crate::utils::filter_bad_response::filter_bad_response;
use crate::utils::http_get_anonymous::http_get_anonymous;
use chrono::{DateTime, Utc};
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
@@ -1,6 +1,6 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum AudioListRef<'a> {
MediaFileTokens(&'a Vec<MediaFileToken>),
Urls(&'a Vec<String>),
@@ -1,7 +1,7 @@
use tokens::tokens::characters::CharacterToken;
/// Forward-compatible character reference list.
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum CharacterListRef<'a> {
CharacterTokens(&'a Vec<CharacterToken>),
@@ -16,11 +16,11 @@ pub enum CommonImageModel {
#[serde(rename = "gpt_image_1p5")]
GptImage1p5,
#[serde(rename = "nano_banana")]
NanaBanana,
NanoBanana,
#[serde(rename = "nano_banana_2")]
NanaBanana2,
NanoBanana2,
#[serde(rename = "nano_banana_pro")]
NanaBananaPro,
NanoBananaPro,
#[serde(rename = "seedream_4")]
Seedream4,
#[serde(rename = "seedream_4p5")]
@@ -1,6 +1,6 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum ImageListRef<'a> {
MediaFileTokens(&'a Vec<MediaFileToken>),
Urls(&'a Vec<String>),
@@ -1,6 +1,6 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum ImageRef<'a> {
MediaFileToken(&'a MediaFileToken),
Url(&'a str),
@@ -1,6 +1,6 @@
use tokens::tokens::media_files::MediaFileToken;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum VideoListRef<'a> {
MediaFileTokens(&'a Vec<MediaFileToken>),
Urls(&'a Vec<String>),
@@ -37,7 +37,7 @@ mod tests {
fn estimate_usd_cents(image_batch_count: u16) -> u64 {
let request = GenerateImageRequest {
model: CommonImageModel::NanaBanana,
model: CommonImageModel::NanoBanana,
provider: Provider::Artcraft,
prompt: None,
image_inputs: None,
@@ -45,7 +45,7 @@ mod tests {
fn estimate_usd_cents(resolution: Option<CommonResolution>, image_batch_count: u16) -> u64 {
let request = GenerateImageRequest {
model: CommonImageModel::NanaBanana2,
model: CommonImageModel::NanoBanana2,
provider: Provider::Artcraft,
prompt: None,
image_inputs: None,
@@ -44,7 +44,7 @@ mod tests {
fn estimate_usd_cents(resolution: Option<CommonResolution>, image_batch_count: u16) -> u64 {
let request = GenerateImageRequest {
model: CommonImageModel::NanaBananaPro,
model: CommonImageModel::NanoBananaPro,
provider: Provider::Artcraft,
prompt: None,
image_inputs: None,
@@ -0,0 +1,26 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev;
pub(crate) fn estimate_image_cost_fal_flux_1_dev(
plan: &PlanFalFlux1Dev<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.025 per megapixel, billed by rounding up to nearest megapixel.
// Default image sizes are ~1MP (1024x1024), so ~$0.025/image = 3 cents rounded up.
//
// Fal docs:
// Your request will cost $0.025 per megapixel, with images billed by rounding up
// to the nearest megapixel.
//
let cost_per_image: u64 = 3;
let num_images = plan.num_images.as_u64();
let cost_in_usd_cents = cost_per_image * num_images;
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,26 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell;
pub(crate) fn estimate_image_cost_fal_flux_1_schnell(
plan: &PlanFalFlux1Schnell<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.003 per megapixel, billed by rounding up to nearest megapixel.
// Default image sizes are ~1MP (1024x1024), so ~$0.003/image = 1 cent rounded up.
//
// Fal docs:
// Your request will cost $0.003 per megapixel, with images billed by rounding up
// to the nearest megapixel.
//
let cost_per_image: u64 = 1;
let num_images = plan.num_images.as_u64();
let cost_in_usd_cents = cost_per_image * num_images;
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,21 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11;
pub(crate) fn estimate_image_cost_fal_flux_pro_1p1(
plan: &PlanFalFluxPro11<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04 per megapixel, billed by rounding up to nearest megapixel.
// Default image sizes are ~1MP (1024x1024), so ~$0.04/image = 4 cents.
let cost_per_image: u64 = 4;
let num_images = plan.num_images.as_u64();
let cost_in_usd_cents = cost_per_image * num_images;
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,20 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra;
pub(crate) fn estimate_image_cost_fal_flux_pro_1p1_ultra(
plan: &PlanFalFluxPro11Ultra<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.06 per image.
let cost_per_image: u64 = 6;
let num_images = plan.num_images.as_u64();
let cost_in_usd_cents = cost_per_image * num_images;
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,33 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::{
FalGptImage1p5ImageSize, FalGptImage1p5Quality, PlanFalGptImage1p5,
};
pub(crate) fn estimate_image_cost_fal_gpt_image_1p5(
plan: &PlanFalGptImage1p5<'_>,
) -> ImageGenerationCostEstimate {
// Per fal docs:
// Low: $0.009 (1024x1024) / $0.013 (other)
// Medium: $0.034 (1024x1024) / $0.050–0.051 (other)
// High: $0.133 (1024x1024) / $0.199–0.200 (other)
// Round up to whole cents.
let is_square = matches!(plan.image_size, None | Some(FalGptImage1p5ImageSize::Square));
let cost_per_image: u64 = match (plan.quality, is_square) {
(FalGptImage1p5Quality::Low, true) => 1,
(FalGptImage1p5Quality::Low, false) => 2,
(FalGptImage1p5Quality::Medium, true) => 4,
(FalGptImage1p5Quality::Medium, false) => 6,
(FalGptImage1p5Quality::High, true) => 14,
(FalGptImage1p5Quality::High, false) => 20,
};
let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64();
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,34 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana::{
FalNbNumImages, PlanFalNanoBanana,
};
pub(crate) fn estimate_image_cost_fal_nano_banana(
plan: &PlanFalNanoBanana<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.039/image (no resolution multiplier — Gemini 25 Flash has no resolution option).
// Rounded to 4 cents per image.
//
// Fal docs:
// Your request will cost $0.039 per image. For $1.00, you can run this model 25 times.
//
let cost_per_image: u64 = 4;
let num_images: u64 = match plan.num_images {
FalNbNumImages::One => 1,
FalNbNumImages::Two => 2,
FalNbNumImages::Three => 3,
FalNbNumImages::Four => 4,
};
let cost_in_usd_cents = cost_per_image * num_images;
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,44 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_2::{
FalNb2NumImages, FalNb2Resolution, PlanFalNanaBanana2,
};
pub(crate) fn estimate_image_cost_fal_nano_banana_2(
plan: &PlanFalNanaBanana2<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.08/image base (1K). Multipliers: 0.5K = 0.75x, 2K = 1.5x, 4K = 2x.
// (Stored as USD cents.)
//
// Fal docs:
// Your request will cost $0.08 per image. For $1.00, you can run this model 12 times.
// 2K and 4K outputs will be charged at 1.5 times and 2 times the standard rate, respectively.
// 0.5K (512px) resolution outputs will be charged at 0.75 times the standard rate. If web
// search is used, an additional $0.015 will be charged. If high thinking is used,
// an additional $0.002 will be charged. Note: Pricing is subject to change.
//
let cost_per_image: u64 = match plan.resolution {
Some(FalNb2Resolution::HalfK) => 6, // $0.06 = $0.08 * 0.75
Some(FalNb2Resolution::OneK) => 8, // $0.08 base
Some(FalNb2Resolution::TwoK) => 12, // $0.12 = $0.08 * 1.5
Some(FalNb2Resolution::FourK) => 16, // $0.16 = $0.08 * 2.0
None => 8, // default = 1K base
};
let num_images: u64 = match plan.num_images {
FalNb2NumImages::One => 1,
FalNb2NumImages::Two => 2,
FalNb2NumImages::Three => 3,
FalNb2NumImages::Four => 4,
};
let cost_in_usd_cents = cost_per_image * num_images;
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -42,7 +42,7 @@ mod tests {
fn estimate_usd_cents(resolution: Option<CommonResolution>, image_batch_count: u16) -> u64 {
let request = GenerateImageRequest {
model: CommonImageModel::NanaBananaPro,
model: CommonImageModel::NanoBananaPro,
provider: Provider::Fal,
prompt: None,
image_inputs: None,
@@ -0,0 +1,19 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4;
pub(crate) fn estimate_image_cost_fal_seedream_4(
plan: &PlanFalSeedream4<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.03 per image.
let cost_per_image: u64 = 3;
let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64();
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,19 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5;
pub(crate) fn estimate_image_cost_fal_seedream_4p5(
plan: &PlanFalSeedream4p5<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.04 per image.
let cost_per_image: u64 = 4;
let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64();
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,19 @@
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite;
pub(crate) fn estimate_image_cost_fal_seedream_5_lite(
plan: &PlanFalSeedream5Lite<'_>,
) -> ImageGenerationCostEstimate {
// Pricing: $0.035 per image — round up to 4 cents.
let cost_per_image: u64 = 4;
let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64();
ImageGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -1 +1,11 @@
pub(crate) mod estimate_image_cost_fal_flux_1_dev;
pub(crate) mod estimate_image_cost_fal_flux_1_schnell;
pub(crate) mod estimate_image_cost_fal_flux_pro_1p1;
pub(crate) mod estimate_image_cost_fal_flux_pro_1p1_ultra;
pub(crate) mod estimate_image_cost_fal_gpt_image_1p5;
pub(crate) mod estimate_image_cost_fal_seedream_4;
pub(crate) mod estimate_image_cost_fal_seedream_4p5;
pub(crate) mod estimate_image_cost_fal_seedream_5_lite;
pub(crate) mod estimate_image_cost_fal_nano_banana;
pub(crate) mod estimate_image_cost_fal_nano_banana_2;
pub(crate) mod estimate_image_cost_fal_nano_banana_pro;
@@ -0,0 +1,49 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev;
use fal_client::requests::webhook::image::edit::enqueue_flux_1_dev_edit_image_webhook::{
enqueue_flux_1_dev_edit_image_webhook, Flux1DevEditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image_webhook::{
enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs,
};
pub async fn execute_fal_flux_1_dev(
plan: &PlanFalFlux1Dev<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
// Image-to-image mode
let args = Flux1DevEditImageArgs {
prompt: plan.prompt.unwrap_or(""),
image_url: image_url.clone(),
num_images: plan.num_images.to_edit(),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_flux_1_dev_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Text-to-image mode
let args = Flux1DevArgs {
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_t2i(),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_flux_1_dev_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,49 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell;
use fal_client::requests::webhook::image::edit::enqueue_flux_1_schnell_edit_image_webhook::{
enqueue_flux_1_schnell_edit_image_webhook, Flux1SchnellEditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_image_webhook::{
enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs,
};
pub async fn execute_fal_flux_1_schnell(
plan: &PlanFalFlux1Schnell<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
// Image-to-image (redux) mode
let args = Flux1SchnellEditImageArgs {
image_url: image_url.clone(),
num_images: plan.num_images.to_edit(),
image_size: plan.edit_image_size,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_flux_1_schnell_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Text-to-image mode
let args = Flux1SchnellArgs {
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.t2i_aspect_ratio,
num_images: plan.num_images.to_t2i(),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_flux_1_schnell_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,32 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11;
use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{
enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args,
};
pub async fn execute_fal_flux_pro_1p1(
plan: &PlanFalFluxPro11<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let args = FluxPro11Args {
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_fal(),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let webhook_response = enqueue_flux_pro_11_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,32 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra;
use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_to_image_webhook::{
enqueue_flux_pro_11_ultra_text_to_image_webhook, FluxPro11UltraArgs,
};
pub async fn execute_fal_flux_pro_1p1_ultra(
plan: &PlanFalFluxPro11Ultra<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let args = FluxPro11UltraArgs {
prompt: plan.prompt.unwrap_or(""),
aspect_ratio: plan.aspect_ratio,
num_images: plan.num_images.to_fal(),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
let webhook_response = enqueue_flux_pro_11_ultra_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,56 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::PlanFalGptImage1p5;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{
enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{
enqueue_gpt_image_1p5_text_to_image_webhook, EnqueueGptImage1p5TextToImageArgs,
};
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 args = EnqueueGptImage1p5TextToImageArgs {
prompt: plan.prompt.unwrap_or(""),
num_images: plan.num_images.to_t2i(),
image_size: plan.image_size.map(|s| s.to_t2i()),
background: None,
quality: Some(plan.quality.to_t2i()),
output_format: None,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_gpt_image_1p5_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueGptImage1p5EditImageArgs {
prompt: plan.prompt.unwrap_or(""),
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,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_gpt_image_1p5_image_edit_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,72 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana::{
FalNbNumImages, PlanFalNanoBanana,
};
use fal_client::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{
enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs,
Gemini25FlashEditNumImages,
};
use fal_client::requests::webhook::image::text::enqueue_gemini_25_flash_text_to_image_webhook::{
enqueue_gemini_25_flash_text_to_image_webhook, Gemini25FlashTextToImageArgs,
Gemini25FlashTextToImageNumImages,
};
pub async fn execute_fal_nano_banana(
plan: &PlanFalNanoBanana<'_>,
fal_client: &RouterFalClient,
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
let webhook_response = if plan.image_urls.is_empty() {
// Text-to-image mode
let args = Gemini25FlashTextToImageArgs {
prompt: plan.prompt.unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
aspect_ratio: plan.t2i_aspect_ratio,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_gemini_25_flash_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Image-edit mode
let args = Gemini25FlashEditArgs {
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
aspect_ratio: plan.edit_aspect_ratio,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_gemini_25_flash_edit_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
fn to_t2i_num_images(n: FalNbNumImages) -> Gemini25FlashTextToImageNumImages {
match n {
FalNbNumImages::One => Gemini25FlashTextToImageNumImages::One,
FalNbNumImages::Two => Gemini25FlashTextToImageNumImages::Two,
FalNbNumImages::Three => Gemini25FlashTextToImageNumImages::Three,
FalNbNumImages::Four => Gemini25FlashTextToImageNumImages::Four,
}
}
fn to_edit_num_images(n: FalNbNumImages) -> Gemini25FlashEditNumImages {
match n {
FalNbNumImages::One => Gemini25FlashEditNumImages::One,
FalNbNumImages::Two => Gemini25FlashEditNumImages::Two,
FalNbNumImages::Three => Gemini25FlashEditNumImages::Three,
FalNbNumImages::Four => Gemini25FlashEditNumImages::Four,
}
}
@@ -0,0 +1,92 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_2::{
FalNb2NumImages, FalNb2Resolution, PlanFalNanaBanana2,
};
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::{
enqueue_nano_banana_2_edit_image_webhook, EnqueueNanoBanana2EditImageArgs,
EnqueueNanoBanana2EditImageNumImages, EnqueueNanoBanana2EditImageResolution,
};
use fal_client::requests::webhook::image::text::enqueue_nano_banana_2_text_to_image_webhook::{
enqueue_nano_banana_2_text_to_image_webhook, EnqueueNanoBanana2TextToImageArgs,
EnqueueNanoBanana2TextToImageNumImages, EnqueueNanoBanana2TextToImageResolution,
};
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() {
// Text-to-image mode
let args = EnqueueNanoBanana2TextToImageArgs {
prompt: plan.prompt.unwrap_or(""),
num_images: to_t2i_num_images(plan.num_images),
resolution: plan.resolution.map(to_t2i_resolution),
aspect_ratio: plan.t2i_aspect_ratio,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_nano_banana_2_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
// Image-edit mode
let args = EnqueueNanoBanana2EditImageArgs {
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: to_edit_num_images(plan.num_images),
resolution: plan.resolution.map(to_edit_resolution),
aspect_ratio: plan.edit_aspect_ratio,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_nano_banana_2_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
fn to_t2i_num_images(n: FalNb2NumImages) -> EnqueueNanoBanana2TextToImageNumImages {
match n {
FalNb2NumImages::One => EnqueueNanoBanana2TextToImageNumImages::One,
FalNb2NumImages::Two => EnqueueNanoBanana2TextToImageNumImages::Two,
FalNb2NumImages::Three => EnqueueNanoBanana2TextToImageNumImages::Three,
FalNb2NumImages::Four => EnqueueNanoBanana2TextToImageNumImages::Four,
}
}
fn to_edit_num_images(n: FalNb2NumImages) -> EnqueueNanoBanana2EditImageNumImages {
match n {
FalNb2NumImages::One => EnqueueNanoBanana2EditImageNumImages::One,
FalNb2NumImages::Two => EnqueueNanoBanana2EditImageNumImages::Two,
FalNb2NumImages::Three => EnqueueNanoBanana2EditImageNumImages::Three,
FalNb2NumImages::Four => EnqueueNanoBanana2EditImageNumImages::Four,
}
}
fn to_t2i_resolution(r: FalNb2Resolution) -> EnqueueNanoBanana2TextToImageResolution {
match r {
FalNb2Resolution::HalfK => EnqueueNanoBanana2TextToImageResolution::HalfK,
FalNb2Resolution::OneK => EnqueueNanoBanana2TextToImageResolution::OneK,
FalNb2Resolution::TwoK => EnqueueNanoBanana2TextToImageResolution::TwoK,
FalNb2Resolution::FourK => EnqueueNanoBanana2TextToImageResolution::FourK,
}
}
fn to_edit_resolution(r: FalNb2Resolution) -> EnqueueNanoBanana2EditImageResolution {
match r {
FalNb2Resolution::HalfK => EnqueueNanoBanana2EditImageResolution::HalfK,
FalNb2Resolution::OneK => EnqueueNanoBanana2EditImageResolution::OneK,
FalNb2Resolution::TwoK => EnqueueNanoBanana2EditImageResolution::TwoK,
FalNb2Resolution::FourK => EnqueueNanoBanana2EditImageResolution::FourK,
}
}
@@ -0,0 +1,50 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{
enqueue_bytedance_seedream_v4_edit_image_webhook, EnqueueBytedanceSeedreamV4EditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{
enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs,
};
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 args = EnqueueBytedanceSeedreamV4TextToImageArgs {
prompt: plan.prompt.unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_bytedance_seedream_v4_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueBytedanceSeedreamV4EditImageArgs {
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_edit()),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_bytedance_seedream_v4_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,50 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{
enqueue_bytedance_seedream_v4p5_edit_image_webhook, EnqueueBytedanceSeedreamV4p5EditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{
enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs,
};
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 args = EnqueueBytedanceSeedreamV4p5TextToImageArgs {
prompt: plan.prompt.unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueBytedanceSeedreamV4p5EditImageArgs {
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_edit()),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_bytedance_seedream_v4p5_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -0,0 +1,50 @@
use crate::client::router_fal_client::RouterFalClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_image::generate_image_response::{
FalImageResponsePayload, GenerateImageResponse,
};
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{
enqueue_bytedance_seedream_v5_lite_edit_image_webhook, EnqueueBytedanceSeedreamV5LiteEditImageArgs,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{
enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs,
};
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 args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
prompt: plan.prompt.unwrap_or(""),
num_images: Some(plan.num_images.to_t2i()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_t2i()),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
} else {
let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
prompt: plan.prompt.unwrap_or(""),
image_urls: plan.image_urls.clone(),
num_images: Some(plan.num_images.to_edit()),
max_images: None,
image_size: plan.image_size.map(|s| s.to_edit()),
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
};
Ok(GenerateImageResponse::Fal(FalImageResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
}))
}
@@ -1 +1,11 @@
pub mod generate_image_fal_flux_1_dev;
pub mod generate_image_fal_flux_1_schnell;
pub mod generate_image_fal_flux_pro_1p1;
pub mod generate_image_fal_flux_pro_1p1_ultra;
pub mod generate_image_fal_gpt_image_1p5;
pub mod generate_image_fal_seedream_4;
pub mod generate_image_fal_seedream_4p5;
pub mod generate_image_fal_seedream_5_lite;
pub mod generate_image_fal_nano_banana;
pub mod generate_image_fal_nano_banana_2;
pub mod generate_image_fal_nano_banana_pro;
@@ -10,19 +10,30 @@ use crate::errors::client_error::ClientError;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_dev::plan_generate_image_artcraft_flux_1_dev;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_schnell::plan_generate_image_artcraft_flux_1_schnell;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_2_lora_angles::plan_generate_image_artcraft_flux_2_lora_angles;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1::plan_generate_image_artcraft_flux_pro_1p1;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1_ultra::plan_generate_image_artcraft_flux_pro_1p1_ultra;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_gpt_image_1p5::plan_generate_image_artcraft_gpt_image_1p5;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana::plan_generate_image_artcraft_nano_banana;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_2::plan_generate_image_artcraft_nano_banana_2;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::plan_generate_image_artcraft_nano_banana_pro;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::plan_generate_image_artcraft_qwen_edit_2511_angles;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::plan_generate_image_artcraft_seedream_4;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::plan_generate_image_artcraft_seedream_4p5;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::plan_generate_image_artcraft_seedream_5_lite;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::plan_generate_image_artcraft_qwen_edit_2511_angles;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_2_lora_angles::plan_generate_image_artcraft_flux_2_lora_angles;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::plan_generate_image_fal_flux_1_dev;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::plan_generate_image_fal_flux_1_schnell;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::plan_generate_image_fal_flux_pro_1p1;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::plan_generate_image_fal_flux_pro_1p1_ultra;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::plan_generate_image_fal_gpt_image_1p5;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::plan_generate_image_fal_seedream_4;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::plan_generate_image_fal_seedream_4p5;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::plan_generate_image_fal_seedream_5_lite;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana::plan_generate_image_fal_nano_banana;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_2::plan_generate_image_fal_nano_banana_2;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::plan_generate_image_fal_nano_banana_pro;
#[derive(Debug)]
pub struct GenerateImageRequest<'a> {
/// Which model to use.
pub model: CommonImageModel,
@@ -86,9 +97,9 @@ impl<'a> GenerateImageRequest<'a> {
CommonImageModel::FluxPro11 => plan_generate_image_artcraft_flux_pro_1p1(self),
CommonImageModel::FluxPro11Ultra => plan_generate_image_artcraft_flux_pro_1p1_ultra(self),
CommonImageModel::GptImage1p5 => plan_generate_image_artcraft_gpt_image_1p5(self),
CommonImageModel::NanaBanana => plan_generate_image_artcraft_nano_banana(self),
CommonImageModel::NanaBanana2 => plan_generate_image_artcraft_nano_banana_2(self),
CommonImageModel::NanaBananaPro => plan_generate_image_artcraft_nano_banana_pro(self),
CommonImageModel::NanoBanana => plan_generate_image_artcraft_nano_banana(self),
CommonImageModel::NanoBanana2 => plan_generate_image_artcraft_nano_banana_2(self),
CommonImageModel::NanoBananaPro => plan_generate_image_artcraft_nano_banana_pro(self),
CommonImageModel::Seedream4 => plan_generate_image_artcraft_seedream_4(self),
CommonImageModel::Seedream4p5 => plan_generate_image_artcraft_seedream_4p5(self),
CommonImageModel::Seedream5Lite => plan_generate_image_artcraft_seedream_5_lite(self),
@@ -99,7 +110,17 @@ impl<'a> GenerateImageRequest<'a> {
fn build_fal(&self) -> Result<ImageGenerationPlan<'_>, ArtcraftRouterError> {
match self.model {
CommonImageModel::NanaBananaPro => plan_generate_image_fal_nano_banana_pro(self),
CommonImageModel::Flux1Dev => plan_generate_image_fal_flux_1_dev(self),
CommonImageModel::Flux1Schnell => plan_generate_image_fal_flux_1_schnell(self),
CommonImageModel::FluxPro11 => plan_generate_image_fal_flux_pro_1p1(self),
CommonImageModel::FluxPro11Ultra => plan_generate_image_fal_flux_pro_1p1_ultra(self),
CommonImageModel::GptImage1p5 => plan_generate_image_fal_gpt_image_1p5(self),
CommonImageModel::Seedream4 => plan_generate_image_fal_seedream_4(self),
CommonImageModel::Seedream4p5 => plan_generate_image_fal_seedream_4p5(self),
CommonImageModel::Seedream5Lite => plan_generate_image_fal_seedream_5_lite(self),
CommonImageModel::NanoBanana => plan_generate_image_fal_nano_banana(self),
CommonImageModel::NanoBanana2 => plan_generate_image_fal_nano_banana_2(self),
CommonImageModel::NanoBananaPro => plan_generate_image_fal_nano_banana_pro(self),
_ => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "provider",
@@ -13,6 +13,16 @@ use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraf
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_seedream_5_lite::estimate_image_cost_artcraft_seedream_5_lite;
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_qwen_edit_2511_angles::estimate_image_cost_artcraft_qwen_edit_2511_angles;
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_flux_2_lora_angles::estimate_image_cost_artcraft_flux_2_lora_angles;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_1_dev::estimate_image_cost_fal_flux_1_dev;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_1_schnell::estimate_image_cost_fal_flux_1_schnell;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_pro_1p1::estimate_image_cost_fal_flux_pro_1p1;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_flux_pro_1p1_ultra::estimate_image_cost_fal_flux_pro_1p1_ultra;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_gpt_image_1p5::estimate_image_cost_fal_gpt_image_1p5;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_seedream_4::estimate_image_cost_fal_seedream_4;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_seedream_4p5::estimate_image_cost_fal_seedream_4p5;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_seedream_5_lite::estimate_image_cost_fal_seedream_5_lite;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana::estimate_image_cost_fal_nano_banana;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana_2::estimate_image_cost_fal_nano_banana_2;
use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana_pro::estimate_image_cost_fal_nano_banana_pro;
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_1_dev::execute_artcraft_flux_1_dev;
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_1_schnell::execute_artcraft_flux_1_schnell;
@@ -27,6 +37,16 @@ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_5_lite::execute_artcraft_seedream_5_lite;
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_qwen_edit_2511_angles::execute_artcraft_qwen_edit_2511_angles;
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_2_lora_angles::execute_artcraft_flux_2_lora_angles;
use crate::generate::generate_image::execute::fal::generate_image_fal_flux_1_dev::execute_fal_flux_1_dev;
use crate::generate::generate_image::execute::fal::generate_image_fal_flux_1_schnell::execute_fal_flux_1_schnell;
use crate::generate::generate_image::execute::fal::generate_image_fal_flux_pro_1p1::execute_fal_flux_pro_1p1;
use crate::generate::generate_image::execute::fal::generate_image_fal_flux_pro_1p1_ultra::execute_fal_flux_pro_1p1_ultra;
use crate::generate::generate_image::execute::fal::generate_image_fal_gpt_image_1p5::execute_fal_gpt_image_1p5;
use crate::generate::generate_image::execute::fal::generate_image_fal_seedream_4::execute_fal_seedream_4;
use crate::generate::generate_image::execute::fal::generate_image_fal_seedream_4p5::execute_fal_seedream_4p5;
use crate::generate::generate_image::execute::fal::generate_image_fal_seedream_5_lite::execute_fal_seedream_5_lite;
use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana::execute_fal_nano_banana;
use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana_2::execute_fal_nano_banana_2;
use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana_pro::execute_fal_nano_banana_pro;
use crate::generate::generate_image::generate_image_response::GenerateImageResponse;
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
@@ -43,6 +63,16 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::PlanArtcraftSeedream5Lite;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::PlanArtcraftQwenEdit2511Angles;
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_2_lora_angles::PlanArtcraftFlux2LoraAngles;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1p5::PlanFalGptImage1p5;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana::PlanFalNanoBanana;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_2::PlanFalNanaBanana2;
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::PlanFalNanaBananaPro;
#[derive(Debug)]
@@ -60,6 +90,16 @@ pub enum ImageGenerationPlan<'a> {
ArtcraftSeedream5Lite(PlanArtcraftSeedream5Lite<'a>),
ArtcraftQwenEdit2511Angles(PlanArtcraftQwenEdit2511Angles<'a>),
ArtcraftFlux2LoraAngles(PlanArtcraftFlux2LoraAngles<'a>),
FalFlux1Dev(PlanFalFlux1Dev<'a>),
FalFlux1Schnell(PlanFalFlux1Schnell<'a>),
FalFluxPro11(PlanFalFluxPro11<'a>),
FalFluxPro11Ultra(PlanFalFluxPro11Ultra<'a>),
FalGptImage1p5(PlanFalGptImage1p5<'a>),
FalSeedream4(PlanFalSeedream4<'a>),
FalSeedream4p5(PlanFalSeedream4p5<'a>),
FalSeedream5Lite(PlanFalSeedream5Lite<'a>),
FalNanoBanana(PlanFalNanoBanana<'a>),
FalNanaBanana2(PlanFalNanaBanana2<'a>),
FalNanaBananaPro(PlanFalNanaBananaPro<'a>),
}
@@ -121,6 +161,46 @@ impl<'a> ImageGenerationPlan<'a> {
let artcraft_client = client.get_artcraft_client_ref()?;
execute_artcraft_flux_2_lora_angles(plan, artcraft_client).await
}
ImageGenerationPlan::FalFlux1Dev(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_flux_1_dev(plan, fal_client).await
}
ImageGenerationPlan::FalFlux1Schnell(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_flux_1_schnell(plan, fal_client).await
}
ImageGenerationPlan::FalFluxPro11(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_flux_pro_1p1(plan, fal_client).await
}
ImageGenerationPlan::FalFluxPro11Ultra(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_flux_pro_1p1_ultra(plan, fal_client).await
}
ImageGenerationPlan::FalGptImage1p5(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_gpt_image_1p5(plan, fal_client).await
}
ImageGenerationPlan::FalSeedream4(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_seedream_4(plan, fal_client).await
}
ImageGenerationPlan::FalSeedream4p5(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_seedream_4p5(plan, fal_client).await
}
ImageGenerationPlan::FalSeedream5Lite(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_seedream_5_lite(plan, fal_client).await
}
ImageGenerationPlan::FalNanoBanana(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_nano_banana(plan, fal_client).await
}
ImageGenerationPlan::FalNanaBanana2(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_nano_banana_2(plan, fal_client).await
}
ImageGenerationPlan::FalNanaBananaPro(plan) => {
let fal_client = client.get_fal_client_ref()?;
execute_fal_nano_banana_pro(plan, fal_client).await
@@ -169,6 +249,36 @@ impl<'a> ImageGenerationPlan<'a> {
ImageGenerationPlan::ArtcraftFlux2LoraAngles(plan) => {
estimate_image_cost_artcraft_flux_2_lora_angles(plan)
}
ImageGenerationPlan::FalFlux1Dev(plan) => {
estimate_image_cost_fal_flux_1_dev(plan)
}
ImageGenerationPlan::FalFlux1Schnell(plan) => {
estimate_image_cost_fal_flux_1_schnell(plan)
}
ImageGenerationPlan::FalFluxPro11(plan) => {
estimate_image_cost_fal_flux_pro_1p1(plan)
}
ImageGenerationPlan::FalFluxPro11Ultra(plan) => {
estimate_image_cost_fal_flux_pro_1p1_ultra(plan)
}
ImageGenerationPlan::FalGptImage1p5(plan) => {
estimate_image_cost_fal_gpt_image_1p5(plan)
}
ImageGenerationPlan::FalSeedream4(plan) => {
estimate_image_cost_fal_seedream_4(plan)
}
ImageGenerationPlan::FalSeedream4p5(plan) => {
estimate_image_cost_fal_seedream_4p5(plan)
}
ImageGenerationPlan::FalSeedream5Lite(plan) => {
estimate_image_cost_fal_seedream_5_lite(plan)
}
ImageGenerationPlan::FalNanoBanana(plan) => {
estimate_image_cost_fal_nano_banana(plan)
}
ImageGenerationPlan::FalNanaBanana2(plan) => {
estimate_image_cost_fal_nano_banana_2(plan)
}
ImageGenerationPlan::FalNanaBananaPro(plan) => {
estimate_image_cost_fal_nano_banana_pro(plan)
}
@@ -1 +1,11 @@
pub mod plan_generate_image_fal_flux_1_dev;
pub mod plan_generate_image_fal_flux_1_schnell;
pub mod plan_generate_image_fal_flux_pro_1p1;
pub mod plan_generate_image_fal_flux_pro_1p1_ultra;
pub mod plan_generate_image_fal_gpt_image_1p5;
pub mod plan_generate_image_fal_seedream_4;
pub mod plan_generate_image_fal_seedream_4p5;
pub mod plan_generate_image_fal_seedream_5_lite;
pub mod plan_generate_image_fal_nano_banana;
pub mod plan_generate_image_fal_nano_banana_2;
pub mod plan_generate_image_fal_nano_banana_pro;
@@ -0,0 +1,158 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_flux_1_dev_edit_image_webhook::Flux1DevEditImageNumImages;
use fal_client::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image_webhook::{
Flux1DevAspectRatio, Flux1DevNumImages,
};
/// Shared num-images type for both t2i and edit modes.
#[derive(Debug, Clone, Copy)]
pub enum FalFlux1DevNumImages {
One,
Two,
Three,
Four,
}
#[derive(Debug, Clone)]
pub struct PlanFalFlux1Dev<'a> {
pub prompt: Option<&'a str>,
/// Image URL for editing. None = text-to-image mode.
pub maybe_image_url: Option<String>,
pub aspect_ratio: Flux1DevAspectRatio,
pub num_images: FalFlux1DevNumImages,
}
pub fn plan_generate_image_fal_flux_1_dev<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let maybe_image_url = resolve_single_image_url(request.image_inputs)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFlux1Dev(PlanFalFlux1Dev {
prompt: request.prompt,
maybe_image_url,
aspect_ratio,
num_images,
}))
}
fn resolve_single_image_url(
image_inputs: Option<ImageListRef<'_>>,
) -> Result<Option<String>, ArtcraftRouterError> {
match image_inputs {
None => Ok(None),
Some(ImageListRef::Urls(urls)) => {
if urls.is_empty() {
Ok(None)
} else if urls.len() == 1 {
Ok(Some(urls[0].clone()))
} else {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: format!("Flux 1 Dev image-to-image supports exactly 1 image, got {}", urls.len()),
}))
}
}
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Flux1DevAspectRatio, ArtcraftRouterError> {
use Flux1DevAspectRatio as Ar;
match aspect_ratio {
None => Ok(Ar::Square),
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => Ok(Ar::Square),
Some(CommonAspectRatio::Square) => Ok(Ar::Square),
Some(CommonAspectRatio::SquareHd) => Ok(Ar::SquareHd),
Some(CommonAspectRatio::WideFourByThree) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::PortraitNineBySixteen),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallFourByFive) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Ar::PortraitNineBySixteen),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Ar::Square),
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalFlux1DevNumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalFlux1DevNumImages::One),
2 => Ok(FalFlux1DevNumImages::Two),
3 => Ok(FalFlux1DevNumImages::Three),
4 => Ok(FalFlux1DevNumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalFlux1DevNumImages::Four),
},
}
}
impl FalFlux1DevNumImages {
pub fn to_t2i(self) -> Flux1DevNumImages {
match self {
Self::One => Flux1DevNumImages::One,
Self::Two => Flux1DevNumImages::Two,
Self::Three => Flux1DevNumImages::Three,
Self::Four => Flux1DevNumImages::Four,
}
}
pub fn to_edit(self) -> Flux1DevEditImageNumImages {
match self {
Self::One => Flux1DevEditImageNumImages::One,
Self::Two => Flux1DevEditImageNumImages::Two,
Self::Three => Flux1DevEditImageNumImages::Three,
Self::Four => Flux1DevEditImageNumImages::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
@@ -0,0 +1,169 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_flux_1_schnell_edit_image_webhook::{
Flux1SchnellEditImageNumImages, Flux1SchnellEditImageSize,
};
use fal_client::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_image_webhook::{
Flux1SchnellAspectRatio, Flux1SchnellNumImages,
};
/// Shared num-images type for both t2i and edit modes.
#[derive(Debug, Clone, Copy)]
pub enum FalFlux1SchnellNumImages {
One,
Two,
Three,
Four,
}
#[derive(Debug, Clone)]
pub struct PlanFalFlux1Schnell<'a> {
pub prompt: Option<&'a str>,
/// Image URL for editing. None = text-to-image mode.
pub maybe_image_url: Option<String>,
pub t2i_aspect_ratio: Flux1SchnellAspectRatio,
pub edit_image_size: Option<Flux1SchnellEditImageSize>,
pub num_images: FalFlux1SchnellNumImages,
}
pub fn plan_generate_image_fal_flux_1_schnell<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let maybe_image_url = resolve_single_image_url(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_image_size = plan_edit_image_size(request.aspect_ratio);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFlux1Schnell(PlanFalFlux1Schnell {
prompt: request.prompt,
maybe_image_url,
t2i_aspect_ratio,
edit_image_size,
num_images,
}))
}
fn resolve_single_image_url(
image_inputs: Option<ImageListRef<'_>>,
) -> Result<Option<String>, ArtcraftRouterError> {
match image_inputs {
None => Ok(None),
Some(ImageListRef::Urls(urls)) => {
if urls.is_empty() {
Ok(None)
} else if urls.len() == 1 {
Ok(Some(urls[0].clone()))
} else {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: format!("Flux 1 Schnell redux supports exactly 1 image, got {}", urls.len()),
}))
}
}
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
fn plan_t2i_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Flux1SchnellAspectRatio, ArtcraftRouterError> {
use Flux1SchnellAspectRatio as Ar;
match aspect_ratio {
None => Ok(Ar::Square),
Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) | Some(CommonAspectRatio::Auto4k) => Ok(Ar::Square),
Some(CommonAspectRatio::Square) => Ok(Ar::Square),
Some(CommonAspectRatio::SquareHd) => Ok(Ar::SquareHd),
Some(CommonAspectRatio::WideFourByThree) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::PortraitNineBySixteen),
Some(CommonAspectRatio::WideFiveByFour) | Some(CommonAspectRatio::WideThreeByTwo) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallFourByFive) | Some(CommonAspectRatio::TallTwoByThree) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Ar::PortraitNineBySixteen),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Ar::Square),
},
}
}
fn plan_edit_image_size(aspect_ratio: Option<CommonAspectRatio>) -> Option<Flux1SchnellEditImageSize> {
use Flux1SchnellEditImageSize as Size;
match aspect_ratio {
None => None,
Some(CommonAspectRatio::Square) => Some(Size::Square),
Some(CommonAspectRatio::SquareHd) => Some(Size::SquareHd),
Some(CommonAspectRatio::WideFourByThree) => Some(Size::LandscapeFourByThree),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Some(Size::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallThreeByFour) => Some(Size::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Some(Size::PortraitNineBySixteen),
_ => Some(Size::Square),
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalFlux1SchnellNumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalFlux1SchnellNumImages::One),
2 => Ok(FalFlux1SchnellNumImages::Two),
3 => Ok(FalFlux1SchnellNumImages::Three),
4 => Ok(FalFlux1SchnellNumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalFlux1SchnellNumImages::Four),
},
}
}
impl FalFlux1SchnellNumImages {
pub fn to_t2i(self) -> Flux1SchnellNumImages {
match self {
Self::One => Flux1SchnellNumImages::One,
Self::Two => Flux1SchnellNumImages::Two,
Self::Three => Flux1SchnellNumImages::Three,
Self::Four => Flux1SchnellNumImages::Four,
}
}
pub fn to_edit(self) -> Flux1SchnellEditImageNumImages {
match self {
Self::One => Flux1SchnellEditImageNumImages::One,
Self::Two => Flux1SchnellEditImageNumImages::Two,
Self::Three => Flux1SchnellEditImageNumImages::Three,
Self::Four => Flux1SchnellEditImageNumImages::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
@@ -0,0 +1,127 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_image_webhook::{
FluxPro11AspectRatio, FluxPro11NumImages,
};
#[derive(Debug, Clone, Copy)]
pub enum FalFluxPro11NumImages {
One,
Two,
Three,
Four,
}
#[derive(Debug, Clone)]
pub struct PlanFalFluxPro11<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: FluxPro11AspectRatio,
pub num_images: FalFluxPro11NumImages,
}
pub fn plan_generate_image_fal_flux_pro_1p1<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
if request.image_inputs.is_some() {
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: "Flux Pro 1.1 is text-to-image only".to_string(),
}));
}
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFluxPro11(PlanFalFluxPro11 {
prompt: request.prompt,
aspect_ratio,
num_images,
}))
}
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FluxPro11AspectRatio, ArtcraftRouterError> {
use FluxPro11AspectRatio as Ar;
match aspect_ratio {
None => Ok(Ar::Square),
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => Ok(Ar::Square),
Some(CommonAspectRatio::Square) => Ok(Ar::Square),
Some(CommonAspectRatio::SquareHd) => Ok(Ar::SquareHd),
Some(CommonAspectRatio::WideFourByThree) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::PortraitNineBySixteen),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::TallFourByFive) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Ar::PortraitNineBySixteen),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Ar::Square),
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalFluxPro11NumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalFluxPro11NumImages::One),
2 => Ok(FalFluxPro11NumImages::Two),
3 => Ok(FalFluxPro11NumImages::Three),
4 => Ok(FalFluxPro11NumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalFluxPro11NumImages::Four),
},
}
}
impl FalFluxPro11NumImages {
pub fn to_fal(self) -> FluxPro11NumImages {
match self {
Self::One => FluxPro11NumImages::One,
Self::Two => FluxPro11NumImages::Two,
Self::Three => FluxPro11NumImages::Three,
Self::Four => FluxPro11NumImages::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
@@ -0,0 +1,126 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_to_image_webhook::{
FluxPro11UltraAspectRatio, FluxPro11UltraNumImages,
};
#[derive(Debug, Clone, Copy)]
pub enum FalFluxPro11UltraNumImages {
One,
Two,
Three,
Four,
}
#[derive(Debug, Clone)]
pub struct PlanFalFluxPro11Ultra<'a> {
pub prompt: Option<&'a str>,
pub aspect_ratio: FluxPro11UltraAspectRatio,
pub num_images: FalFluxPro11UltraNumImages,
}
pub fn plan_generate_image_fal_flux_pro_1p1_ultra<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
if request.image_inputs.is_some() {
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_inputs",
value: "Flux Pro 1.1 Ultra is text-to-image only".to_string(),
}));
}
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalFluxPro11Ultra(PlanFalFluxPro11Ultra {
prompt: request.prompt,
aspect_ratio,
num_images,
}))
}
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FluxPro11UltraAspectRatio, ArtcraftRouterError> {
use FluxPro11UltraAspectRatio as Ar;
match aspect_ratio {
None => Ok(Ar::Square),
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => Ok(Ar::Square),
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Ar::Square),
Some(CommonAspectRatio::WideFourByThree) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Ar::LandscapeThreeByTwo),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::LandscapeSixteenByNine),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Ar::LandscapeTwentyOneByNine),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Ar::PortraitThreeByFour),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Ar::PortraitTwoByThree),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::PortraitNineBySixteen),
Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Ar::PortraitNineByTwentyOne),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Ar::LandscapeFourByThree),
Some(CommonAspectRatio::TallFourByFive) => Ok(Ar::PortraitThreeByFour),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Ar::Square),
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalFluxPro11UltraNumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalFluxPro11UltraNumImages::One),
2 => Ok(FalFluxPro11UltraNumImages::Two),
3 => Ok(FalFluxPro11UltraNumImages::Three),
4 => Ok(FalFluxPro11UltraNumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalFluxPro11UltraNumImages::Four),
},
}
}
impl FalFluxPro11UltraNumImages {
pub fn to_fal(self) -> FluxPro11UltraNumImages {
match self {
Self::One => FluxPro11UltraNumImages::One,
Self::Two => FluxPro11UltraNumImages::Two,
Self::Three => FluxPro11UltraNumImages::Three,
Self::Four => FluxPro11UltraNumImages::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
@@ -0,0 +1,197 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{
EnqueueGptImage1p5EditImageNumImages, EnqueueGptImage1p5EditImageQuality,
EnqueueGptImage1p5EditImageSize,
};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{
EnqueueGptImage1p5TextToImageNumImages, EnqueueGptImage1p5TextToImageQuality,
EnqueueGptImage1p5TextToImageSize,
};
#[derive(Debug, Clone, Copy)]
pub enum FalGptImage1p5NumImages {
One,
Two,
Three,
Four,
}
/// Quality is shared between t2i and edit (Low / Medium / High).
#[derive(Debug, Clone, Copy)]
pub enum FalGptImage1p5Quality {
Low,
Medium,
High,
}
/// GPT Image 1.5 supports three image sizes: 1024x1024, 1536x1024, 1024x1536.
#[derive(Debug, Clone, Copy)]
pub enum FalGptImage1p5ImageSize {
Square,
Wide,
Tall,
}
#[derive(Debug, Clone)]
pub struct PlanFalGptImage1p5<'a> {
pub prompt: Option<&'a str>,
pub image_urls: Vec<String>,
pub image_size: Option<FalGptImage1p5ImageSize>,
pub quality: FalGptImage1p5Quality,
pub num_images: FalGptImage1p5NumImages,
}
pub fn plan_generate_image_fal_gpt_image_1p5<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio);
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalGptImage1p5(PlanFalGptImage1p5 {
prompt: request.prompt,
image_urls,
image_size,
quality: FalGptImage1p5Quality::Medium,
num_images,
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
Some(ImageListRef::Urls(urls)) => Ok(urls.clone()),
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
fn plan_image_size(aspect_ratio: Option<CommonAspectRatio>) -> Option<FalGptImage1p5ImageSize> {
use FalGptImage1p5ImageSize as S;
match aspect_ratio {
None
| Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => None,
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Some(S::Square),
Some(CommonAspectRatio::WideThreeByTwo)
| Some(CommonAspectRatio::WideFourByThree)
| Some(CommonAspectRatio::WideFiveByFour)
| Some(CommonAspectRatio::WideSixteenByNine)
| Some(CommonAspectRatio::WideTwentyOneByNine)
| Some(CommonAspectRatio::Wide) => Some(S::Wide),
Some(CommonAspectRatio::TallTwoByThree)
| Some(CommonAspectRatio::TallThreeByFour)
| Some(CommonAspectRatio::TallFourByFive)
| Some(CommonAspectRatio::TallNineBySixteen)
| Some(CommonAspectRatio::TallNineByTwentyOne)
| Some(CommonAspectRatio::Tall) => Some(S::Tall),
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalGptImage1p5NumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalGptImage1p5NumImages::One),
2 => Ok(FalGptImage1p5NumImages::Two),
3 => Ok(FalGptImage1p5NumImages::Three),
4 => Ok(FalGptImage1p5NumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalGptImage1p5NumImages::Four),
},
}
}
impl FalGptImage1p5NumImages {
pub fn to_t2i(self) -> EnqueueGptImage1p5TextToImageNumImages {
use EnqueueGptImage1p5TextToImageNumImages as T;
match self {
Self::One => T::One,
Self::Two => T::Two,
Self::Three => T::Three,
Self::Four => T::Four,
}
}
pub fn to_edit(self) -> EnqueueGptImage1p5EditImageNumImages {
use EnqueueGptImage1p5EditImageNumImages as E;
match self {
Self::One => E::One,
Self::Two => E::Two,
Self::Three => E::Three,
Self::Four => E::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
impl FalGptImage1p5Quality {
pub fn to_t2i(self) -> EnqueueGptImage1p5TextToImageQuality {
use EnqueueGptImage1p5TextToImageQuality as T;
match self {
Self::Low => T::Low,
Self::Medium => T::Medium,
Self::High => T::High,
}
}
pub fn to_edit(self) -> EnqueueGptImage1p5EditImageQuality {
use EnqueueGptImage1p5EditImageQuality as E;
match self {
Self::Low => E::Low,
Self::Medium => E::Medium,
Self::High => E::High,
}
}
}
impl FalGptImage1p5ImageSize {
pub fn to_t2i(self) -> EnqueueGptImage1p5TextToImageSize {
use EnqueueGptImage1p5TextToImageSize as T;
match self {
Self::Square => T::Square,
Self::Wide => T::Wide,
Self::Tall => T::Tall,
}
}
pub fn to_edit(self) -> EnqueueGptImage1p5EditImageSize {
use EnqueueGptImage1p5EditImageSize as E;
match self {
Self::Square => E::Square,
Self::Wide => E::Wide,
Self::Tall => E::Tall,
}
}
}
@@ -0,0 +1,174 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::Gemini25FlashEditAspectRatio;
use fal_client::requests::webhook::image::text::enqueue_gemini_25_flash_text_to_image_webhook::Gemini25FlashTextToImageAspectRatio;
/// Intermediate num-images type shared between t2i and edit enums.
#[derive(Debug, Clone, Copy)]
pub enum FalNbNumImages {
One,
Two,
Three,
Four,
}
#[derive(Debug, Clone)]
pub struct PlanFalNanoBanana<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
/// Pre-resolved aspect ratio for text-to-image mode.
pub t2i_aspect_ratio: Option<Gemini25FlashTextToImageAspectRatio>,
/// Pre-resolved aspect ratio for image-edit mode.
pub edit_aspect_ratio: Option<Gemini25FlashEditAspectRatio>,
/// Pre-resolved number of images (1–4, shared for both modes).
pub num_images: FalNbNumImages,
}
pub fn plan_generate_image_fal_nano_banana<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_aspect_ratio = plan_edit_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalNanoBanana(PlanFalNanoBanana {
prompt: request.prompt,
image_urls,
t2i_aspect_ratio,
edit_aspect_ratio,
num_images,
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
Some(ImageListRef::Urls(urls)) => Ok(urls.clone()),
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
// Gemini 25 Flash text-to-image supported aspect ratios:
// 1:1, 5:4, 4:3, 3:2, 16:9, 21:9, 4:5, 3:4, 2:3, 9:16
// (no Auto for text-to-image)
fn plan_t2i_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Gemini25FlashTextToImageAspectRatio>, ArtcraftRouterError> {
use Gemini25FlashTextToImageAspectRatio as T2iAr;
match aspect_ratio {
None => Ok(None),
// Auto not valid for text-to-image; fall back to square
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => Ok(Some(T2iAr::OneByOne)),
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(T2iAr::OneByOne)),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(T2iAr::FiveByFour)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(T2iAr::FourByThree)),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(T2iAr::ThreeByTwo)),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(T2iAr::SixteenByNine)),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(T2iAr::TwentyOneByNine)),
Some(CommonAspectRatio::TallFourByFive) => Ok(Some(T2iAr::FourByFive)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(T2iAr::ThreeByFour)),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(T2iAr::TwoByThree)),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(T2iAr::NineBySixteen)),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(T2iAr::NineBySixteen))
}
},
}
}
// Gemini 25 Flash image-edit supported aspect ratios: same as t2i plus Auto
fn plan_edit_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
is_edit_mode: bool,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Gemini25FlashEditAspectRatio>, ArtcraftRouterError> {
use Gemini25FlashEditAspectRatio as EditAr;
match aspect_ratio {
None => Ok(None),
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => {
if is_edit_mode {
Ok(Some(EditAr::Auto))
} else {
Ok(Some(EditAr::OneByOne))
}
}
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(EditAr::OneByOne)),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(EditAr::FiveByFour)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(EditAr::FourByThree)),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(EditAr::ThreeByTwo)),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(EditAr::SixteenByNine)),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(EditAr::TwentyOneByNine)),
Some(CommonAspectRatio::TallFourByFive) => Ok(Some(EditAr::FourByFive)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(EditAr::ThreeByFour)),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(EditAr::TwoByThree)),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(EditAr::NineBySixteen)),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(EditAr::NineBySixteen))
}
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalNbNumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalNbNumImages::One),
2 => Ok(FalNbNumImages::Two),
3 => Ok(FalNbNumImages::Three),
4 => Ok(FalNbNumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(FalNbNumImages::Four),
},
}
}
@@ -0,0 +1,202 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_resolution::CommonResolution;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::EnqueueNanoBanana2EditImageAspectRatio;
use fal_client::requests::webhook::image::text::enqueue_nano_banana_2_text_to_image_webhook::EnqueueNanoBanana2TextToImageAspectRatio;
/// Intermediate resolution type shared between t2i and edit enums.
#[derive(Debug, Clone, Copy)]
pub enum FalNb2Resolution {
HalfK,
OneK,
TwoK,
FourK,
}
/// Intermediate num-images type shared between t2i and edit enums.
#[derive(Debug, Clone, Copy)]
pub enum FalNb2NumImages {
One,
Two,
Three,
Four,
}
#[derive(Debug, Clone)]
pub struct PlanFalNanaBanana2<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
/// Pre-resolved aspect ratio for text-to-image mode.
pub t2i_aspect_ratio: Option<EnqueueNanoBanana2TextToImageAspectRatio>,
/// Pre-resolved aspect ratio for image-edit mode.
pub edit_aspect_ratio: Option<EnqueueNanoBanana2EditImageAspectRatio>,
/// Pre-resolved resolution (shared for both modes).
pub resolution: Option<FalNb2Resolution>,
/// Pre-resolved number of images (1–4, shared for both modes).
pub num_images: FalNb2NumImages,
}
pub fn plan_generate_image_fal_nano_banana_2<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let t2i_aspect_ratio = plan_t2i_aspect_ratio(request.aspect_ratio, strategy)?;
let edit_aspect_ratio = plan_edit_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
let resolution = plan_resolution(request.resolution)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalNanaBanana2(PlanFalNanaBanana2 {
prompt: request.prompt,
image_urls,
t2i_aspect_ratio,
edit_aspect_ratio,
resolution,
num_images,
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
Some(ImageListRef::Urls(urls)) => Ok(urls.clone()),
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
// NB2 text-to-image supported aspect ratios:
// auto, 1:1, 5:4, 4:3, 3:2, 16:9, 21:9, 4:5, 3:4, 2:3, 9:16
fn plan_t2i_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<EnqueueNanoBanana2TextToImageAspectRatio>, ArtcraftRouterError> {
use EnqueueNanoBanana2TextToImageAspectRatio as T2iAr;
match aspect_ratio {
None => Ok(None),
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => Ok(Some(T2iAr::Auto)),
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(T2iAr::OneByOne)),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(T2iAr::FiveByFour)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(T2iAr::FourByThree)),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(T2iAr::ThreeByTwo)),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(T2iAr::SixteenByNine)),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(T2iAr::TwentyOneByNine)),
Some(CommonAspectRatio::TallFourByFive) => Ok(Some(T2iAr::FourByFive)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(T2iAr::ThreeByFour)),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(T2iAr::TwoByThree)),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(T2iAr::NineBySixteen)),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(T2iAr::NineBySixteen))
}
},
}
}
// NB2 image-edit supported aspect ratios: same as t2i plus Auto
fn plan_edit_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
is_edit_mode: bool,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<EnqueueNanoBanana2EditImageAspectRatio>, ArtcraftRouterError> {
use EnqueueNanoBanana2EditImageAspectRatio as EditAr;
match aspect_ratio {
None => Ok(None),
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => {
if is_edit_mode {
Ok(Some(EditAr::Auto))
} else {
Ok(Some(EditAr::OneByOne))
}
}
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(EditAr::OneByOne)),
Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(EditAr::FiveByFour)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(EditAr::FourByThree)),
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(EditAr::ThreeByTwo)),
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(EditAr::SixteenByNine)),
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(EditAr::TwentyOneByNine)),
Some(CommonAspectRatio::TallFourByFive) => Ok(Some(EditAr::FourByFive)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(EditAr::ThreeByFour)),
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(EditAr::TwoByThree)),
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(EditAr::NineBySixteen)),
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(EditAr::NineBySixteen))
}
},
}
}
fn plan_resolution(
resolution: Option<CommonResolution>,
) -> Result<Option<FalNb2Resolution>, ArtcraftRouterError> {
match resolution {
None => Ok(None),
Some(CommonResolution::HalfK) => Ok(Some(FalNb2Resolution::HalfK)),
Some(CommonResolution::OneK) => Ok(Some(FalNb2Resolution::OneK)),
Some(CommonResolution::TwoK) => Ok(Some(FalNb2Resolution::TwoK)),
Some(CommonResolution::FourK) => Ok(Some(FalNb2Resolution::FourK)),
Some(CommonResolution::ThreeK) => Ok(Some(FalNb2Resolution::TwoK)),
Some(CommonResolution::FourEightyP) => Ok(Some(FalNb2Resolution::HalfK)),
Some(CommonResolution::SevenTwentyP) => Ok(Some(FalNb2Resolution::OneK)),
Some(CommonResolution::TenEightyP) => Ok(Some(FalNb2Resolution::OneK)),
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalNb2NumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalNb2NumImages::One),
2 => Ok(FalNb2NumImages::Two),
3 => Ok(FalNb2NumImages::Three),
4 => Ok(FalNb2NumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(FalNb2NumImages::Four),
},
}
}
@@ -250,7 +250,7 @@ mod tests {
fn base_fal_request() -> GenerateImageRequest<'static> {
GenerateImageRequest {
model: CommonImageModel::NanaBananaPro,
model: CommonImageModel::NanoBananaPro,
provider: Provider::Fal,
prompt: Some("a cat in space"),
image_inputs: None,
@@ -0,0 +1,209 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{
EnqueueBytedanceSeedreamV4EditImageNumImages, EnqueueBytedanceSeedreamV4EditImageSize,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{
EnqueueBytedanceSeedreamV4TextToImageNumImages, EnqueueBytedanceSeedreamV4TextToImageSize,
};
#[derive(Debug, Clone, Copy)]
pub enum FalSeedream4NumImages {
One,
Two,
Three,
Four,
}
/// Shared image-size shape between Seedream V4 t2i and edit (variants are identical).
#[derive(Debug, Clone, Copy)]
pub enum FalSeedream4ImageSize {
Square,
SquareHd,
PortraitFourThree,
PortraitSixteenNine,
LandscapeFourThree,
LandscapeSixteenNine,
Auto,
Auto2k,
Auto4k,
}
#[derive(Debug, Clone)]
pub struct PlanFalSeedream4<'a> {
pub prompt: Option<&'a str>,
/// Image URLs for editing. Empty vec = text-to-image mode.
pub image_urls: Vec<String>,
pub image_size: Option<FalSeedream4ImageSize>,
pub num_images: FalSeedream4NumImages,
}
pub fn plan_generate_image_fal_seedream_4<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let is_edit_mode = request.image_inputs.is_some();
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, is_edit_mode, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalSeedream4(PlanFalSeedream4 {
prompt: request.prompt,
image_urls,
image_size,
num_images,
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
Some(ImageListRef::Urls(urls)) => Ok(urls.clone()),
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
fn plan_image_size(
aspect_ratio: Option<CommonAspectRatio>,
is_edit_mode: bool,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<FalSeedream4ImageSize>, ArtcraftRouterError> {
use FalSeedream4ImageSize as S;
match aspect_ratio {
None => Ok(None),
Some(CommonAspectRatio::Auto) => {
if is_edit_mode { Ok(Some(S::Auto)) } else { Ok(Some(S::Square)) }
}
Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)),
Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto4k)),
Some(CommonAspectRatio::Square) => Ok(Some(S::Square)),
Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)),
Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)),
Some(unsupported @ CommonAspectRatio::WideFiveByFour)
| Some(unsupported @ CommonAspectRatio::WideThreeByTwo)
| Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Some(S::LandscapeSixteenNine)),
},
Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)),
Some(unsupported @ CommonAspectRatio::TallFourByFive)
| Some(unsupported @ CommonAspectRatio::TallTwoByThree)
| Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Some(S::PortraitSixteenNine)),
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalSeedream4NumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalSeedream4NumImages::One),
2 => Ok(FalSeedream4NumImages::Two),
3 => Ok(FalSeedream4NumImages::Three),
4 => Ok(FalSeedream4NumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalSeedream4NumImages::Four),
},
}
}
impl FalSeedream4NumImages {
pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4TextToImageNumImages {
use EnqueueBytedanceSeedreamV4TextToImageNumImages as T;
match self {
Self::One => T::One,
Self::Two => T::Two,
Self::Three => T::Three,
Self::Four => T::Four,
}
}
pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4EditImageNumImages {
use EnqueueBytedanceSeedreamV4EditImageNumImages as E;
match self {
Self::One => E::One,
Self::Two => E::Two,
Self::Three => E::Three,
Self::Four => E::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
impl FalSeedream4ImageSize {
pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4TextToImageSize {
use EnqueueBytedanceSeedreamV4TextToImageSize as T;
match self {
Self::Square => T::Square,
Self::SquareHd => T::SquareHd,
Self::PortraitFourThree => T::PortraitFourThree,
Self::PortraitSixteenNine => T::PortraitSixteenNine,
Self::LandscapeFourThree => T::LandscapeFourThree,
Self::LandscapeSixteenNine => T::LandscapeSixteenNine,
Self::Auto => T::Auto,
Self::Auto2k => T::Auto2k,
Self::Auto4k => T::Auto4k,
}
}
pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4EditImageSize {
use EnqueueBytedanceSeedreamV4EditImageSize as E;
match self {
Self::Square => E::Square,
Self::SquareHd => E::SquareHd,
Self::PortraitFourThree => E::PortraitFourThree,
Self::PortraitSixteenNine => E::PortraitSixteenNine,
Self::LandscapeFourThree => E::LandscapeFourThree,
Self::LandscapeSixteenNine => E::LandscapeSixteenNine,
Self::Auto => E::Auto,
Self::Auto2k => E::Auto2k,
Self::Auto4k => E::Auto4k,
}
}
}
@@ -0,0 +1,201 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{
EnqueueBytedanceSeedreamV4p5EditImageNumImages, EnqueueBytedanceSeedreamV4p5EditImageSize,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{
EnqueueBytedanceSeedreamV4p5TextToImageNumImages, EnqueueBytedanceSeedreamV4p5TextToImageSize,
};
#[derive(Debug, Clone, Copy)]
pub enum FalSeedream4p5NumImages {
One,
Two,
Three,
Four,
}
/// Note: Seedream V4.5 lacks the bare "Auto" image_size; only Auto2k/Auto4k are supported.
#[derive(Debug, Clone, Copy)]
pub enum FalSeedream4p5ImageSize {
Square,
SquareHd,
PortraitFourThree,
PortraitSixteenNine,
LandscapeFourThree,
LandscapeSixteenNine,
Auto2k,
Auto4k,
}
#[derive(Debug, Clone)]
pub struct PlanFalSeedream4p5<'a> {
pub prompt: Option<&'a str>,
pub image_urls: Vec<String>,
pub image_size: Option<FalSeedream4p5ImageSize>,
pub num_images: FalSeedream4p5NumImages,
}
pub fn plan_generate_image_fal_seedream_4p5<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalSeedream4p5(PlanFalSeedream4p5 {
prompt: request.prompt,
image_urls,
image_size,
num_images,
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
Some(ImageListRef::Urls(urls)) => Ok(urls.clone()),
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
fn plan_image_size(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<FalSeedream4p5ImageSize>, ArtcraftRouterError> {
use FalSeedream4p5ImageSize as S;
match aspect_ratio {
None => Ok(None),
// No bare Auto for v4.5 — fall back to Auto2k.
Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)),
Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto4k)),
Some(CommonAspectRatio::Square) => Ok(Some(S::Square)),
Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)),
Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)),
Some(unsupported @ CommonAspectRatio::WideFiveByFour)
| Some(unsupported @ CommonAspectRatio::WideThreeByTwo)
| Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Some(S::LandscapeSixteenNine)),
},
Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)),
Some(unsupported @ CommonAspectRatio::TallFourByFive)
| Some(unsupported @ CommonAspectRatio::TallTwoByThree)
| Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Some(S::PortraitSixteenNine)),
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalSeedream4p5NumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalSeedream4p5NumImages::One),
2 => Ok(FalSeedream4p5NumImages::Two),
3 => Ok(FalSeedream4p5NumImages::Three),
4 => Ok(FalSeedream4p5NumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalSeedream4p5NumImages::Four),
},
}
}
impl FalSeedream4p5NumImages {
pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4p5TextToImageNumImages {
use EnqueueBytedanceSeedreamV4p5TextToImageNumImages as T;
match self {
Self::One => T::One,
Self::Two => T::Two,
Self::Three => T::Three,
Self::Four => T::Four,
}
}
pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4p5EditImageNumImages {
use EnqueueBytedanceSeedreamV4p5EditImageNumImages as E;
match self {
Self::One => E::One,
Self::Two => E::Two,
Self::Three => E::Three,
Self::Four => E::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
impl FalSeedream4p5ImageSize {
pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV4p5TextToImageSize {
use EnqueueBytedanceSeedreamV4p5TextToImageSize as T;
match self {
Self::Square => T::Square,
Self::SquareHd => T::SquareHd,
Self::PortraitFourThree => T::PortraitFourThree,
Self::PortraitSixteenNine => T::PortraitSixteenNine,
Self::LandscapeFourThree => T::LandscapeFourThree,
Self::LandscapeSixteenNine => T::LandscapeSixteenNine,
Self::Auto2k => T::Auto2k,
Self::Auto4k => T::Auto4k,
}
}
pub fn to_edit(self) -> EnqueueBytedanceSeedreamV4p5EditImageSize {
use EnqueueBytedanceSeedreamV4p5EditImageSize as E;
match self {
Self::Square => E::Square,
Self::SquareHd => E::SquareHd,
Self::PortraitFourThree => E::PortraitFourThree,
Self::PortraitSixteenNine => E::PortraitSixteenNine,
Self::LandscapeFourThree => E::LandscapeFourThree,
Self::LandscapeSixteenNine => E::LandscapeSixteenNine,
Self::Auto2k => E::Auto2k,
Self::Auto4k => E::Auto4k,
}
}
}
@@ -0,0 +1,210 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_resolution::CommonResolution;
use crate::api::image_list_ref::ImageListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{
EnqueueBytedanceSeedreamV5LiteEditImageNumImages, EnqueueBytedanceSeedreamV5LiteEditImageSize,
};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages, EnqueueBytedanceSeedreamV5LiteTextToImageSize,
};
#[derive(Debug, Clone, Copy)]
pub enum FalSeedream5LiteNumImages {
One,
Two,
Three,
Four,
}
/// Note: Seedream V5 Lite supports Auto2k and Auto3k (no 4K, no bare Auto).
#[derive(Debug, Clone, Copy)]
pub enum FalSeedream5LiteImageSize {
Square,
SquareHd,
PortraitFourThree,
PortraitSixteenNine,
LandscapeFourThree,
LandscapeSixteenNine,
Auto2k,
Auto3k,
}
#[derive(Debug, Clone)]
pub struct PlanFalSeedream5Lite<'a> {
pub prompt: Option<&'a str>,
pub image_urls: Vec<String>,
pub image_size: Option<FalSeedream5LiteImageSize>,
pub num_images: FalSeedream5LiteNumImages,
}
pub fn plan_generate_image_fal_seedream_5_lite<'a>(
request: &'a GenerateImageRequest<'a>,
) -> Result<ImageGenerationPlan<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let image_urls = resolve_image_list_ref(request.image_inputs)?;
let image_size = plan_image_size(request.aspect_ratio, request.resolution, strategy)?;
let num_images = plan_num_images(request.image_batch_count, strategy)?;
Ok(ImageGenerationPlan::FalSeedream5Lite(PlanFalSeedream5Lite {
prompt: request.prompt,
image_urls,
image_size,
num_images,
}))
}
fn resolve_image_list_ref(
image_list_ref: Option<ImageListRef<'_>>,
) -> Result<Vec<String>, ArtcraftRouterError> {
match image_list_ref {
None => Ok(vec![]),
Some(ImageListRef::Urls(urls)) => Ok(urls.clone()),
Some(ImageListRef::MediaFileTokens(_)) => {
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
}
}
}
fn plan_image_size(
aspect_ratio: Option<CommonAspectRatio>,
resolution: Option<CommonResolution>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<FalSeedream5LiteImageSize>, ArtcraftRouterError> {
use FalSeedream5LiteImageSize as S;
// Resolution-based auto sizes take precedence when an explicit Auto* aspect ratio
// isn't specified but a resolution is — Seedream v5 lite has no 4K, fall back to 3K.
match aspect_ratio {
None => match resolution {
Some(CommonResolution::TwoK) => Ok(Some(S::Auto2k)),
Some(CommonResolution::ThreeK) | Some(CommonResolution::FourK) => Ok(Some(S::Auto3k)),
_ => Ok(None),
},
Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)),
// No 4K — fall back to 3K.
Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto3k)),
Some(CommonAspectRatio::Square) => Ok(Some(S::Square)),
Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)),
Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)),
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)),
Some(unsupported @ CommonAspectRatio::WideFiveByFour)
| Some(unsupported @ CommonAspectRatio::WideThreeByTwo)
| Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Some(S::LandscapeSixteenNine)),
},
Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)),
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)),
Some(unsupported @ CommonAspectRatio::TallFourByFive)
| Some(unsupported @ CommonAspectRatio::TallTwoByThree)
| Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
_ => Ok(Some(S::PortraitSixteenNine)),
},
}
}
fn plan_num_images(
image_batch_count: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<FalSeedream5LiteNumImages, ArtcraftRouterError> {
let count = image_batch_count.unwrap_or(1);
match count {
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
1 => Ok(FalSeedream5LiteNumImages::One),
2 => Ok(FalSeedream5LiteNumImages::Two),
3 => Ok(FalSeedream5LiteNumImages::Three),
4 => Ok(FalSeedream5LiteNumImages::Four),
_ => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "image_batch_count",
value: format!("{}", count),
}))
}
_ => Ok(FalSeedream5LiteNumImages::Four),
},
}
}
impl FalSeedream5LiteNumImages {
pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV5LiteTextToImageNumImages {
use EnqueueBytedanceSeedreamV5LiteTextToImageNumImages as T;
match self {
Self::One => T::One,
Self::Two => T::Two,
Self::Three => T::Three,
Self::Four => T::Four,
}
}
pub fn to_edit(self) -> EnqueueBytedanceSeedreamV5LiteEditImageNumImages {
use EnqueueBytedanceSeedreamV5LiteEditImageNumImages as E;
match self {
Self::One => E::One,
Self::Two => E::Two,
Self::Three => E::Three,
Self::Four => E::Four,
}
}
pub fn as_u64(self) -> u64 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Three => 3,
Self::Four => 4,
}
}
}
impl FalSeedream5LiteImageSize {
pub fn to_t2i(self) -> EnqueueBytedanceSeedreamV5LiteTextToImageSize {
use EnqueueBytedanceSeedreamV5LiteTextToImageSize as T;
match self {
Self::Square => T::Square,
Self::SquareHd => T::SquareHd,
Self::PortraitFourThree => T::PortraitFourThree,
Self::PortraitSixteenNine => T::PortraitSixteenNine,
Self::LandscapeFourThree => T::LandscapeFourThree,
Self::LandscapeSixteenNine => T::LandscapeSixteenNine,
Self::Auto2k => T::Auto2k,
Self::Auto3k => T::Auto3k,
}
}
pub fn to_edit(self) -> EnqueueBytedanceSeedreamV5LiteEditImageSize {
use EnqueueBytedanceSeedreamV5LiteEditImageSize as E;
match self {
Self::Square => E::Square,
Self::SquareHd => E::SquareHd,
Self::PortraitFourThree => E::PortraitFourThree,
Self::PortraitSixteenNine => E::PortraitSixteenNine,
Self::LandscapeFourThree => E::LandscapeFourThree,
Self::LandscapeSixteenNine => E::LandscapeSixteenNine,
Self::Auto2k => E::Auto2k,
Self::Auto3k => E::Auto3k,
}
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_1_6_pro::PlanArtcraftKling16Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_1_6_pro(
plan: &PlanArtcraftKling16Pro<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v1p6_pro: 5s = $0.475 (rounded up to 48¢), 10s = $0.95 (95¢).
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 95 } else { 48 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_master::PlanArtcraftKling21Master;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_1_master(
plan: &PlanArtcraftKling21Master<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p1_master: 5s = $1.40, 10s = $2.80.
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 280 } else { 140 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_pro::PlanArtcraftKling21Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_1_pro(
plan: &PlanArtcraftKling21Pro<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p1_pro: 5s = $0.45, 10s = $0.90.
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 90 } else { 45 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_5_turbo_pro::PlanArtcraftKling2p5TurboPro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_5_turbo_pro(
plan: &PlanArtcraftKling2p5TurboPro<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p5_turbo_pro: 5s = $0.35, 10s = $0.70.
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 70 } else { 35 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,26 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_6_pro::PlanArtcraftKling2p6Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_6_pro(
plan: &PlanArtcraftKling2p6Pro<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p6_pro:
// audio off: $0.07/sec → 5s=35¢, 10s=70¢
// audio on: $0.14/sec → 5s=70¢, 10s=140¢
// Default duration is 5s; default generate_audio is true.
let cost_in_usd_cents: u64 = match (plan.generate_audio_for_cost(), plan.is_ten_seconds()) {
(false, false) => 35,
(false, true) => 70,
(true, false) => 70,
(true, true) => 140,
};
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,51 @@
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteResolution;
use fal_client::creds::fal_api_key::FalApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{
Seedance1LiteArgs, Seedance1LiteDuration, Seedance1LiteResolution,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance_1_0_lite::PlanArtcraftSeedance10Lite;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_seedance_1_0_lite(
plan: &PlanArtcraftSeedance10Lite<'_>,
) -> VideoGenerationCostEstimate {
// Legacy handler defaults: 5 seconds, 720p. Delegate to the Fal client's
// cost calculator to guarantee parity with what we actually charge.
let api_key = FalApiKey::from_str("");
let duration = if plan.duration_seconds_for_cost() == 10 {
Seedance1LiteDuration::TenSeconds
} else {
Seedance1LiteDuration::FiveSeconds
};
let resolution = match plan.resolution_for_cost() {
GenerateSeedance10LiteResolution::FourEightyP => Seedance1LiteResolution::FourEightyP,
GenerateSeedance10LiteResolution::SevenTwentyP => Seedance1LiteResolution::SevenTwentyP,
};
let end_frame: Option<&str> = None;
let args = Seedance1LiteArgs {
image_url: "",
end_frame_image_url: end_frame,
prompt: "",
duration,
resolution,
aspect_ratio: None,
camera_fixed: false,
seed: None,
api_key: &api_key,
webhook_url: "https://example.com",
};
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2::PlanArtcraftSora2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_sora_2(
plan: &PlanArtcraftSora2<'_>,
) -> VideoGenerationCostEstimate {
// Sora 2: $0.10/second. Default duration (Fal client): 4 seconds.
let cost_in_usd_cents = plan.duration_seconds_for_cost() * 10;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,21 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2_pro::PlanArtcraftSora2Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_sora_2_pro(
plan: &PlanArtcraftSora2Pro<'_>,
) -> VideoGenerationCostEstimate {
// Sora 2 Pro: $0.30/second @ 720p, $0.50/second @ 1080p.
// Default duration 4s. Default resolution: 1080p for text-to-video, 720p (Auto) for image-to-video.
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.is_ten_eighty_p_for_cost() { 50 } else { 30 };
let cost_in_usd_cents = per_second_cents * seconds;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,20 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_2::PlanArtcraftVeo2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_2(
plan: &PlanArtcraftVeo2<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_2 cost calculator: 5s = $2.50, +$0.50/s above 5s.
let seconds = plan.duration_seconds_for_cost();
let extra = seconds.saturating_sub(5);
let cost_in_usd_cents = 250 + extra * 50;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,21 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3::PlanArtcraftVeo3;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3(
plan: &PlanArtcraftVeo3<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3 cost calculator (720p/1080p):
// $0.20/sec audio off, $0.40/sec audio on. Default 8s, audio off (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 40 } else { 20 };
let cost_in_usd_cents = per_second_cents * seconds;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,21 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3_fast::PlanArtcraftVeo3Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3_fast(
plan: &PlanArtcraftVeo3Fast<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3_fast cost calculator (720p/1080p):
// $0.10/sec audio off, $0.15/sec audio on. Default 8s, audio off (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 15 } else { 10 };
let cost_in_usd_cents = per_second_cents * seconds;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,21 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1::PlanArtcraftVeo3p1;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3p1(
plan: &PlanArtcraftVeo3p1<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3p1 cost calculator (720p/1080p):
// $0.20/sec audio off, $0.40/sec audio on. Default 6s, audio on (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 40 } else { 20 };
let cost_in_usd_cents = per_second_cents * seconds;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,21 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1_fast::PlanArtcraftVeo3p1Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3p1_fast(
plan: &PlanArtcraftVeo3p1Fast<'_>,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3p1_fast cost calculator (720p/1080p):
// $0.10/sec audio off, $0.15/sec audio on. Default 6s, audio on (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 15 } else { 10 };
let cost_in_usd_cents = per_second_cents * seconds;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -1,4 +1,17 @@
pub mod estimate_video_cost_artcraft_kling_1_6_pro;
pub mod estimate_video_cost_artcraft_kling_2_1_master;
pub mod estimate_video_cost_artcraft_kling_2_1_pro;
pub mod estimate_video_cost_artcraft_kling_2_5_turbo_pro;
pub mod estimate_video_cost_artcraft_kling_2_6_pro;
pub mod estimate_video_cost_artcraft_kling3p0_pro;
pub mod estimate_video_cost_artcraft_kling3p0_standard;
pub mod estimate_video_cost_artcraft_seedance_1_0_lite;
pub mod estimate_video_cost_artcraft_seedance1p5_pro;
pub mod estimate_video_cost_artcraft_seedance2p0;
pub mod estimate_video_cost_artcraft_sora_2;
pub mod estimate_video_cost_artcraft_sora_2_pro;
pub mod estimate_video_cost_artcraft_veo_2;
pub mod estimate_video_cost_artcraft_veo_3;
pub mod estimate_video_cost_artcraft_veo_3_fast;
pub mod estimate_video_cost_artcraft_veo_3p1;
pub mod estimate_video_cost_artcraft_veo_3p1_fast;
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::PlanFalKling16Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_1_6_pro(
plan: &PlanFalKling16Pro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v1p6_pro: 5s = 48¢, 10s = 95¢ (Default → 5s).
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 95 } else { 48 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_master::PlanFalKling21Master;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_2_1_master(
plan: &PlanFalKling21Master,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p1_master: 5s = $1.40, 10s = $2.80.
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 280 } else { 140 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_pro::PlanFalKling21Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_2_1_pro(
plan: &PlanFalKling21Pro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p1_pro: 5s = 45¢, 10s = 90¢.
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 90 } else { 45 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_5_turbo_pro::PlanFalKling2p5TurboPro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_2_5_turbo_pro(
plan: &PlanFalKling2p5TurboPro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p5_turbo_pro: 5s = 35¢, 10s = 70¢. None → 5s.
let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 70 } else { 35 };
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,26 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_6_pro::PlanFalKling2p6Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_2_6_pro(
plan: &PlanFalKling2p6Pro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p6_pro:
// audio off: $0.07/sec → 5s=35¢, 10s=70¢
// audio on: $0.14/sec → 5s=70¢, 10s=140¢
// Default duration is 5s; default generate_audio is true.
let cost_in_usd_cents: u64 = match (plan.generate_audio_for_cost(), plan.is_ten_seconds()) {
(false, false) => 35,
(false, true) => 70,
(true, false) => 70,
(true, true) => 140,
};
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,23 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::PlanFalKling3p0Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_3p0_pro(
plan: &PlanFalKling3p0Pro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_3p0_pro:
// audio off: $0.224/sec (rate=224 in tenths-of-cents)
// audio on: $0.336/sec (rate=336)
// ceiling-divided to whole cents.
let seconds = plan.duration_seconds_for_cost();
let rate: u64 = if plan.generate_audio_for_cost() { 336 } else { 224 };
let cost_in_usd_cents = (rate * seconds + 9) / 10;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,22 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_standard::PlanFalKling3p0Standard;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_kling_3p0_standard(
plan: &PlanFalKling3p0Standard,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_3p0_standard:
// audio off: $0.168/sec (rate=168)
// audio on: $0.252/sec (rate=252)
let seconds = plan.duration_seconds_for_cost();
let rate: u64 = if plan.generate_audio_for_cost() { 252 } else { 168 };
let cost_in_usd_cents = (rate * seconds + 9) / 10;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,37 @@
use fal_client::creds::fal_api_key::FalApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::Seedance1LiteArgs;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1_0_lite::PlanFalSeedance10Lite;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_seedance_1_0_lite(
plan: &PlanFalSeedance10Lite,
) -> VideoGenerationCostEstimate {
// Delegate to the Fal client's cost calculator to guarantee parity with billing.
let api_key = FalApiKey::from_str("");
let end_frame: Option<&str> = plan.end_image_url.as_deref();
let args = Seedance1LiteArgs {
image_url: plan.image_url.as_str(),
end_frame_image_url: end_frame,
prompt: plan.prompt.as_str(),
duration: plan.duration,
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
camera_fixed: false,
seed: None,
api_key: &api_key,
webhook_url: "https://example.com",
};
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,62 @@
use fal_client::creds::fal_api_key::FalApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
EnqueueSeedance1p5ProTextToVideoArgs, EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoResolution,
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1p5_pro::{
FalSeedance1p5ProDuration, FalSeedance1p5ProResolution, PlanFalSeedance1p5Pro,
};
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_seedance_1p5_pro(
plan: &PlanFalSeedance1p5Pro,
) -> VideoGenerationCostEstimate {
// Seedance 1.5 Pro t2v and i2v have identical pricing; delegate to the t2v
// calculator to guarantee parity with billing.
let api_key = FalApiKey::from_str("");
let args = EnqueueSeedance1p5ProTextToVideoArgs {
prompt: String::new(),
resolution: plan.resolution.map(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: None,
generate_audio: plan.generate_audio,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
fn to_t2v_resolution(r: FalSeedance1p5ProResolution) -> EnqueueSeedance1p5ProTextToVideoResolution {
match r {
FalSeedance1p5ProResolution::FourEightyP => EnqueueSeedance1p5ProTextToVideoResolution::FourEightyP,
FalSeedance1p5ProResolution::SevenTwentyP => EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP,
FalSeedance1p5ProResolution::TenEightyP => EnqueueSeedance1p5ProTextToVideoResolution::TenEightyP,
}
}
fn to_t2v_duration(d: FalSeedance1p5ProDuration) -> EnqueueSeedance1p5ProTextToVideoDuration {
use EnqueueSeedance1p5ProTextToVideoDuration as T;
match d {
FalSeedance1p5ProDuration::Four => T::FourSeconds,
FalSeedance1p5ProDuration::Five => T::FiveSeconds,
FalSeedance1p5ProDuration::Six => T::SixSeconds,
FalSeedance1p5ProDuration::Seven => T::SevenSeconds,
FalSeedance1p5ProDuration::Eight => T::EightSeconds,
FalSeedance1p5ProDuration::Nine => T::NineSeconds,
FalSeedance1p5ProDuration::Ten => T::TenSeconds,
FalSeedance1p5ProDuration::Eleven => T::ElevenSeconds,
FalSeedance1p5ProDuration::Twelve => T::TwelveSeconds,
}
}
@@ -0,0 +1,18 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2::PlanFalSora2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_sora_2(
plan: &PlanFalSora2,
) -> VideoGenerationCostEstimate {
// Sora 2: $0.10/second. Default duration (Fal client): 4s.
let cost_in_usd_cents = plan.duration_seconds_for_cost() * 10;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
@@ -0,0 +1,20 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2_pro::PlanFalSora2Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_sora_2_pro(
plan: &PlanFalSora2Pro,
) -> VideoGenerationCostEstimate {
// Sora 2 Pro: $0.30/second @ 720p, $0.50/second @ 1080p.
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.is_ten_eighty_p_for_cost() { 50 } else { 30 };
let cost_in_usd_cents = per_second_cents * seconds;
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}

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