Compare commits

...
Author SHA1 Message Date
Brandon Thomas 398c56d7eb page works 2026-03-25 12:29:38 -04:00
Brandon Thomas ba21368323 don't log these 2026-03-25 10:47:31 -04:00
Brandon Thomas 0750b9fe10 that worked! 2026-03-25 10:46:57 -04:00
Brandon Thomas 05a26b124e seems to work 2026-03-25 10:41:58 -04:00
Brandon Thomas a394ee5a96 create alert api 2026-03-25 02:30:11 -04:00
Brandon Thomas c35ea620b3 add rootly stub 2026-03-25 02:18:58 -04:00
Brandon Thomas 389b1c3d17 update website download links 2026-03-25 00:39:01 -04:00
Brandon Thomas f9148d3cf6 seedance 2 fast (#1215) 2026-03-25 00:26:19 -04:00
Brandon Thomas 64d66aa9f0 Batched Seedance 2.0 Job Polling (#1214) 2026-03-24 17:20:31 -04:00
Brandon Thomas 82695725cd seedance whitelist for faster generations (#1213) 2026-03-24 15:54:52 -04:00
Brandon Thomas 53f9032603 simplify seedance 2.0 handler flow 2026-03-24 14:43:29 -04:00
Brandon Thomas 69c03045fd .gitignore 2026-03-24 14:38:09 -04:00
Brandon Thomas e82f070d1d bump artcraft 0.19 -> 0.20 (#1212) 2026-03-24 14:30:32 -04:00
BFlat 459960e64f remove confusing system prompt toggle 2026-03-24 22:23:40 +07:00
BFlat d1266b7591 reprompt/recreate updated to use prompts api (#1208) 2026-03-24 21:59:38 +07:00
BFlat 4bdb1c89a4 password reset fail message 2026-03-24 16:59:22 +07:00
Brandon Thomas 4d53d9af18 mod user signup list (#1207) 2026-03-24 05:53:44 -04:00
Brandon Thomas 2bc00d7f80 richer errors for password reset failures (#1206) 2026-03-24 04:52:25 -04:00
Brandon Thomas f48cb41f21 plumb prompt duration for video models (#1205) 2026-03-24 03:53:05 -04:00
Brandon Thomas d24508aee9 alter prompts table durations (#1204) 2026-03-24 03:04:45 -04:00
Brandon Thomas 1985d9acd7 bump artcraft 0.18 -> 0.19 (#1203) 2026-03-24 02:31:44 -04:00
Brandon Thomas 0f90981f30 fix windows via anstyle-wincon downgrade (#1202) 2026-03-24 02:30:48 -04:00
Brandon Thomas 8d1ca743db bump artcraft 0.17 -> 0.18 (#1201) 2026-03-24 02:15:46 -04:00
Brandon Thomas 72403ad4d8 record prompt info (#1200) 2026-03-24 01:48:46 -04:00
Tanish Manku 8d731f1155 Text2Image - Image batches: Arrow key support (#1198) 2026-03-23 20:57:50 -07:00
Brandon Thomas 516d740e97 include class of model in prompt info 2026-03-23 21:53:00 -04:00
Brandon Thomas 95eeafd6f0 enrich prompts (#1197) 2026-03-23 21:50:18 -04:00
Brandon Thomas 0db5646deb prompts: new database columns (#1196) 2026-03-23 15:59:07 -04:00
Brandon Thomas ac839e2c39 remove enums crates (#1195)
I tried. I'll have to make a better attempt in the future to divorce DB
schema definitions from API definitions (and worse - API clients).
2026-03-23 14:43:57 -04:00
Brandon Thomas 65334e12be More notes on repo 2026-03-23 14:23:50 -04:00
Brandon Thomas 7e644c3e98 Root CLAUDE.md 2026-03-23 14:07:24 -04:00
BFlat bb3fe2df2d reprompt (recreate) button (#1194)
<img width="1219" height="790"
alt="{A15AF77F-C555-4C85-BC15-B4B8D44D2248}"
src="https://github.com/user-attachments/assets/ec4b52a2-68c4-42b0-88fc-00ee3b322321"
/>

A "recreate" button in the media lightbox modal.
On clicked, it will prefill the image or video promptbox with the same
model, prompt and ref images that was used to generate that media the
button was clicked from.

Temporary and doesnt support seedance video/audio ref yet.
2026-03-23 11:14:01 +07:00
Brandon Thomas a92ad2eb3a remove unused enum imports (#1192) 2026-03-22 18:27:00 -04:00
Brandon Thomas 78e7d08f04 all_variants enum methods (#1191) 2026-03-22 18:10:56 -04:00
Brandon Thomas be18036bf6 enums helper methods, cleanup (#1190) 2026-03-22 17:34:32 -04:00
Brandon Thomas 83a8852cef enums split - but just the enums (#1188) 2026-03-22 12:46:29 -04:00
BFlat afed8688a5 resiable image and video prompt boxes 2026-03-20 21:00:10 +07:00
BFlat 9d380decf9 seedance multi generations at a time (#1185) 2026-03-20 20:36:45 +07:00
BFlat 4905cdcd3d angles update 2026-03-20 20:20:50 +07:00
Brandon Thomas b709b6e764 remove fal.rs and move everything into fal_client (#1183) 2026-03-19 20:11:34 -04:00
Brandon Thomas 58d2665f0e seedance2pro -> kinovi domain name migration (#1181) 2026-03-19 13:49:43 -04:00
BFlat 72bf6738e1 website library filter links, added 3d, and video thumbnails to prope… (#1174)
…rly show
2026-03-19 13:21:19 +07:00
Brandon Thomas 1178bb7ebf update utoipa and swagger doc gen (#1171) 2026-03-18 22:49:04 -04:00
Brandon Thomas fea20c417c update builds to trigger only on relevant changes (#1170) 2026-03-18 22:07:20 -04:00
Brandon Thomas 5a07903870 organize root cargo.toml 2026-03-18 21:51:06 -04:00
Brandon Thomas 2063434304 centralized dependency configuration (#1169) 2026-03-18 21:45:42 -04:00
Brandon Thomas b386fd5d88 bump artcraft 0.16.0 -> 0.17.0 (#1168) 2026-03-18 21:19:31 -04:00
Brandon Thomas 1626dda8b4 fix windows builds (#1167) 2026-03-18 21:17:54 -04:00
Brandon Thomas ebe365703b refactor testing crates (#1166) 2026-03-18 21:06:46 -04:00
Brandon Thomas 5caa578b86 Bump ArtCraft 0.15 -> 0.16 (#1165) 2026-03-18 20:34:21 -04:00
Brandon Thomas 2da5fbcc58 cleanup refactor #2 (#1164)
- move a lot of "deprecated" crates so we don't use them
- kill several dead crates
- rename crates to be more semantic/clear
- roll back broken sha3 version bump
2026-03-18 20:32:50 -04:00
Tanish Manku 588650b1b5 2D: Ordered draw instead of Z-Index + Other improvements (#1163)
Paint/Mask cursor now draws properly regardless of canvas scale and drag
offset.
Inpainting now has its own brush with add and subtract actions.
A total node array is used for ordered-rendering of nodes, which fixes
the layer forward/backward operations.
2026-03-18 15:53:32 -07:00
Brandon Thomas 761bf832a5 bump misc dep versions (#1162)
Some deps require manual upgrade - will cycle back
2026-03-18 17:45:58 -04:00
Brandon Thomas df4d46cc9d rename both worldlabs crates (#1161)
- api client crate
- consumer client crate
2026-03-18 16:49:04 -04:00
Brandon Thomas 19599e00be still broken in google ads 2026-03-18 15:44:19 -04:00
Brandon Thomas 2f2ac350bc more redirects 2026-03-18 15:41:54 -04:00
Brandon Thomas bbd0871c04 google ads broken link 2026-03-18 15:40:32 -04:00
Brandon Thomas 7d20ac3d16 grab bag of refactoring, cleanup, etc. (#1160)
- rename seedance2pro crate
- deprecate methods
- remove smtp client
- etc.
2026-03-18 13:36:29 -04:00
Brandon Thomas f9c1201fc6 add thumbnail job sharding and logging (#1158) 2026-03-18 11:42:22 -04:00
Brandon Thomas 0ac57a5448 thumbnail job (#1157) 2026-03-18 02:59:18 -04:00
bflatastic b88f5c536b please upload mp4 file toast - video ref 2026-03-18 08:51:57 +07:00
bflatastic 7edba0d9d5 failed to upload toast for video ref 2026-03-18 08:40:30 +07:00
bflatastic 76ae56a051 updated to duration slider 2026-03-18 07:46:23 +07:00
bflatastic 75a92fe204 popover/dropdown to support scroll on smaller screen height 2026-03-18 07:22:54 +07:00
Tanish Manku 31c507a404 2D: Composite draw layer into ref image for default gen (#1156)
3D nodes and drawings on the canvas were missing from the reference
image sent to the model in the default generation path. The fix mirrors
the Nano Banana Pro approach: render a composite of the base image and
the draw layer, upload it via uploadSceneSnapshot, and pass it as
scene_image_media_token rather than putting editedImageToken in
image_media_tokens.
2026-03-17 14:59:21 -07:00
1889 changed files with 12919 additions and 119440 deletions
+14
View File
@@ -10,6 +10,20 @@ on:
#- $default-branch
- main
- test
# Only build when files that affect the Docker image change.
# This avoids expensive 15-25 min builds for docs-only or config-only changes.
paths:
# Individual files
- '.github/workflows/docker-publish-cpu.yml'
- 'Cargo.lock'
- 'Cargo.toml'
- 'build/service_cpu.Dockerfile'
# Directories
- '.sqlx/**'
- '_database/**'
- 'crates/**'
- 'includes/**'
- 'test_data/**'
env:
IMAGE_NAME: storyteller-rust
+2 -1
View File
@@ -36,8 +36,9 @@ script/socat_dev_database_connect.sh
# Python virtual envs (named by convention)
venv/
# Testing
# LLM Testing
/prompts/
/reports/
/requests/
# Scratch data
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO prompts\nSET\n token = ?,\n prompt_type = ?,\n\n maybe_creator_user_token = ?,\n \n maybe_model_type = ?,\n maybe_generation_provider = ?,\n\n maybe_positive_prompt = ?,\n maybe_negative_prompt = ?,\n \n maybe_other_args = ?,\n\n creator_ip_address = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "08f52f15acf6afc7091a525e3dc88f706c71c3579e5e81aa9903807d85f469ef"
}
@@ -0,0 +1,168 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n u.id as `id: u64`,\n u.token as `token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name,\n u.username_is_not_customized as `username_is_not_customized: bool`,\n u.email_address,\n u.email_confirmed as `email_confirmed: bool`,\n u.is_without_password as `is_without_password: bool`,\n u.ip_address_creation,\n u.maybe_source as `maybe_source: enums::by_table::users::user_signup_source::UserSignupSource`,\n u.maybe_signup_method as `maybe_signup_method: enums::by_table::users::user_signup_method::UserSignupMethod`,\n u.created_at,\n u.maybe_referral_url,\n u.is_temporary as `is_temporary: bool`\nFROM users AS u\nWHERE\n u.id < ?\n AND u.user_deleted_at IS NULL\n AND u.mod_deleted_at IS NULL\nORDER BY u.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "username",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 3,
"name": "display_name",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 4,
"name": "username_is_not_customized: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 5,
"name": "email_address",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 6,
"name": "email_confirmed: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 7,
"name": "is_without_password: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 8,
"name": "ip_address_creation",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 160
}
},
{
"ordinal": 9,
"name": "maybe_source: enums::by_table::users::user_signup_source::UserSignupSource",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 10,
"name": "maybe_signup_method: enums::by_table::users::user_signup_method::UserSignupMethod",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 11,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 12,
"name": "maybe_referral_url",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 13,
"name": "is_temporary: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
true,
false,
true,
false
]
},
"hash": "2fe10ae5a2d013e1861f71dcb0afae4048e0d47c5b584c6bbc7df25dde747e87"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n p.token as `token: tokens::tokens::prompts::PromptToken`,\n\n p.prompt_type as `prompt_type: enums::by_table::prompts::prompt_type::PromptType`,\n\n p.maybe_model_type as `maybe_model_type: enums::common::model_type::ModelType`,\n p.maybe_generation_provider as `maybe_generation_provider: enums::common::generation_provider::GenerationProvider`,\n\n p.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n\n p.maybe_positive_prompt,\n p.maybe_negative_prompt,\n p.maybe_other_args,\n\n p.creator_ip_address,\n p.created_at\n\nFROM prompts as p\nWHERE\n p.token = ?\n ",
"query": "\nSELECT\n p.token as `token: tokens::tokens::prompts::PromptToken`,\n\n p.prompt_type as `prompt_type: enums::by_table::prompts::prompt_type::PromptType`,\n\n p.maybe_model_type as `maybe_model_type: enums::common::generation::common_model_type::CommonModelType`,\n p.maybe_generation_provider as `maybe_generation_provider: enums::common::generation_provider::GenerationProvider`,\n\n p.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n\n p.maybe_positive_prompt,\n p.maybe_negative_prompt,\n\n p.maybe_generation_mode as `maybe_generation_mode: enums::common::generation::common_generation_mode::CommonGenerationMode`,\n p.maybe_aspect_ratio as `maybe_aspect_ratio: enums::common::generation::common_aspect_ratio::CommonAspectRatio`,\n p.maybe_resolution as `maybe_resolution: enums::common::generation::common_resolution::CommonResolution`,\n p.maybe_batch_count as `maybe_batch_count: u8`,\n p.maybe_generate_audio as `maybe_generate_audio: bool`,\n p.maybe_duration_seconds as `maybe_duration_seconds: u32`,\n\n p.maybe_other_args,\n\n p.creator_ip_address,\n p.created_at\n\nFROM prompts as p\nWHERE\n p.token = ?\n ",
"describe": {
"columns": [
{
@@ -25,7 +25,7 @@
},
{
"ordinal": 2,
"name": "maybe_model_type: enums::common::model_type::ModelType",
"name": "maybe_model_type: enums::common::generation::common_model_type::CommonModelType",
"type_info": {
"type": "VarString",
"flags": "BINARY",
@@ -75,6 +75,66 @@
},
{
"ordinal": 7,
"name": "maybe_generation_mode: enums::common::generation::common_generation_mode::CommonGenerationMode",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 96
}
},
{
"ordinal": 8,
"name": "maybe_aspect_ratio: enums::common::generation::common_aspect_ratio::CommonAspectRatio",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 96
}
},
{
"ordinal": 9,
"name": "maybe_resolution: enums::common::generation::common_resolution::CommonResolution",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_batch_count: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 11,
"name": "maybe_generate_audio: bool",
"type_info": {
"type": "Tiny",
"flags": "",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 12,
"name": "maybe_duration_seconds: u32",
"type_info": {
"type": "Long",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 13,
"name": "maybe_other_args",
"type_info": {
"type": "Blob",
@@ -84,7 +144,7 @@
}
},
{
"ordinal": 8,
"ordinal": 14,
"name": "creator_ip_address",
"type_info": {
"type": "VarString",
@@ -94,7 +154,7 @@
}
},
{
"ordinal": 9,
"ordinal": 15,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -116,9 +176,15 @@
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "b46b0d022790f4e10f6e47126df0fdc55172bf8cda77d4e0b2afac9e6ebd1630"
"hash": "4a50b7bf06061f05e769a41e547cb927bd2b3a6f67c8ceb37883b744237ba34c"
}
@@ -0,0 +1,289 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n u.id as `id: u64`,\n u.token as `token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name,\n u.username_is_not_customized as `username_is_not_customized: bool`,\n u.email_address,\n u.email_confirmed as `email_confirmed: bool`,\n u.is_without_password as `is_without_password: bool`,\n u.ip_address_creation,\n u.maybe_source as `maybe_source: enums::by_table::users::user_signup_source::UserSignupSource`,\n u.maybe_signup_method as `maybe_signup_method: enums::by_table::users::user_signup_method::UserSignupMethod`,\n u.created_at,\n u.maybe_referral_url,\n u.is_temporary as `is_temporary: bool`,\n us.subscription_namespace,\n us.subscription_product_slug,\n us.maybe_stripe_subscription_id,\n us.maybe_stripe_customer_id,\n us.maybe_stripe_product_id,\n us.maybe_stripe_price_id,\n us.maybe_stripe_subscription_status,\n us.maybe_stripe_recurring_interval,\n us.maybe_stripe_invoice_is_paid as `maybe_stripe_invoice_is_paid: bool`,\n us.maybe_cancel_at,\n us.maybe_canceled_at\nFROM users AS u\nINNER JOIN user_subscriptions AS us ON u.token = us.user_token\nWHERE\n u.id < ?\n AND u.user_deleted_at IS NULL\n AND u.mod_deleted_at IS NULL\nORDER BY u.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "username",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 3,
"name": "display_name",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 4,
"name": "username_is_not_customized: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 5,
"name": "email_address",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 6,
"name": "email_confirmed: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 7,
"name": "is_without_password: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 8,
"name": "ip_address_creation",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 160
}
},
{
"ordinal": 9,
"name": "maybe_source: enums::by_table::users::user_signup_source::UserSignupSource",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 10,
"name": "maybe_signup_method: enums::by_table::users::user_signup_method::UserSignupMethod",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 11,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 12,
"name": "maybe_referral_url",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 13,
"name": "is_temporary: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 14,
"name": "subscription_namespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 15,
"name": "subscription_product_slug",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 16,
"name": "maybe_stripe_subscription_id",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 17,
"name": "maybe_stripe_customer_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 18,
"name": "maybe_stripe_product_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 19,
"name": "maybe_stripe_price_id",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 20,
"name": "maybe_stripe_subscription_status",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 21,
"name": "maybe_stripe_recurring_interval",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 22,
"name": "maybe_stripe_invoice_is_paid: bool",
"type_info": {
"type": "Tiny",
"flags": "",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 23,
"name": "maybe_cancel_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 24,
"name": "maybe_canceled_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
true,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true
]
},
"hash": "83840fa1a0712a326a26ae146a819f67a44fae927f294780d89892270075ebb7"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\n UPDATE media_files\n SET maybe_thumbnail_version = ?\n WHERE token = ?\n LIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "87d7233bb9a4bcae7c9b7d55bb93fab182343847f0bcf8d8ce849f3a2dcfb704"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO prompts\nSET\n token = ?,\n prompt_type = ?,\n\n maybe_creator_user_token = ?,\n \n maybe_model_type = ?,\n maybe_generation_provider = ?,\n\n maybe_positive_prompt = ?,\n maybe_negative_prompt = ?,\n\n maybe_generation_mode = ?,\n maybe_aspect_ratio = ?,\n maybe_resolution = ?,\n maybe_batch_count = ?,\n maybe_generate_audio = ?,\n maybe_duration_seconds = ?,\n\n maybe_other_args = ?,\n\n creator_ip_address = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 15
},
"nullable": []
},
"hash": "ccf92ebe0328d3019ac6b01be4b442df706a183362fa7e5c331f5a4bf1a74a00"
}
@@ -0,0 +1,91 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n id,\n token as `token: MediaFileToken`,\n created_at as `created_at: DateTime<Utc>`,\n maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n public_bucket_directory_hash,\n maybe_public_bucket_prefix,\n maybe_public_bucket_extension\nFROM media_files\nWHERE\n id >= (SELECT COALESCE(MIN(id), 0) FROM media_files WHERE created_at >= NOW() - INTERVAL ? HOUR)\n AND id < ?\n AND media_class = ?\n AND maybe_thumbnail_version IS NULL\n AND user_deleted_at IS NULL\n AND mod_deleted_at IS NULL\nORDER BY id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "created_at: DateTime<Utc>",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 3,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 4,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
}
],
"parameters": {
"Right": 4
},
"nullable": [
false,
false,
false,
true,
false,
true,
true
]
},
"hash": "daed2dcd03b3c9fbdb9057b743df078f43e2af87a2c31f418234f98da5ff979f"
}
+62
View File
@@ -0,0 +1,62 @@
# CLAUDE.md
This file provides guidance for Claude Code when working with the Artcraft monorepo.
## Project Overview
Artcraft is a web and desktop application for generating AI image and video. It is written in
Rust and TypeScript and contains desktop, server, and frontend components.
## Project Structure
```
artcraft/
├── _database/ # Schema definitions and migrations (MySQL, SQLite, Elasticsearch, etc.)
│ ├── elasticsearch/ # Elasticsearch schema and queries
│ └── sql/ # MySQL and SQLite schema definitions and migrations
│ ├── artcraft_migrations/ # ArtCraft desktop app SQLite migrations
│ ├── migrations/ # Server MySQL migrations
│ └── migrations_squashed/ # Fully materialized MySQL schema definitions for most tables
├── build/ # Dockerfile build instructions for server components
├── crates/ # Rust workspace
│ ├── api_clients/ # HTTP clients for calling internal and 3rd party services
│ ├── cli/ # Command line tools
│ ├── desktop/ # Desktop (Tauri) apps
│ │ └── artcraft/ # (Important) ArtCraft, the desktop app. This is one of our main pieces of software
│ ├── lib/ # Various utility libraries for servers, CLI tools, desktop, etc.
│ ├── schema/ # Data definition layer: MySQL, SQlite, Redis, S3/R2 buckets, etc.
│ │ ├── buckets/ # Declares R2 cloud bucket topology
│ │ ├── database/ # MySQL, SQLite, Elasticsearch, Redis, etc.
│ │ │ ├── elasticsearch_schema/ # Elasticsearch
│ │ │ ├── migration/ # (deprecated) Online schema adapters for MySQL
│ │ │ ├── mysql_queries/ # Sqlx MySQL queries for our backend monolith `storyteller-web`, jobs, etc.
│ │ │ ├── redis_common/ # Redis support
│ │ │ ├── redis_schema/ # Redis key and HKEY topology
│ │ │ ├── sqlite_queries/ # (deprecated) Sqlite queries
│ │ │ └── sqlite_tasks/ # Queries for the ArtCraft desktop app's "tasks" database.
│ │ ├── public/ # Token identifier and enum variant definitions
│ │ │ ├── composite_identifier/ # MySQL composite key system
│ │ │ ├── enums/ # MySQL "enums" stored in VARCHAR fields.
│ │ │ └── tokens/ # Primary database identifiers with Stripe-like ID prefixes, eg. "user_{entropy}"
│ │ └── service/ # Backend HTTP services and jobs
│ │ ├── job/ # Backend jobs.
│ │ │ └── video_thumbnail_job/ # Render video thumbnails
│ │ ├── plugins/ # Collections of reusable Actix-Web HTTP functions (user and billing systems)
│ │ └── web/ # HTTP web servers
│ │ └── storyteller_web/ # (Important) Our main HTTP API monolith and backend.
│ └── frontend/ # Nx typescript monorepo for our websites and Tauri desktop apps
│ ├── apps/ # Websites and Tauri desktop apps
│ │ ├── artcraft/ # ArtCraft the Tauri app's frontend. Used in conjunction with `artcraft` the Rust crate.
│ │ └── artcraft-website/ # The website for https://getartcraft.com
│ └── libs/ # Support libraries, reusable React components, etc.
└── Cargo.toml # Rust monorepo workspace
```
## Code Style
- Rust with no minimum supported version
- Actix-web for HTTP services
- SQLx for MySQL and SQLite
- A mix of wreq and reqwest for Rust HTTP clients
- TypeScript with Nx, React, Vite, Zustand, and Three.js
- Use two spaces for indentation
Generated
+529 -869
View File
File diff suppressed because it is too large Load Diff
+92 -67
View File
@@ -6,27 +6,28 @@ resolver = "2"
members = [
# API clients for external services
# These might be custom implementations or wrappers over other client libraries.
"crates/api_clients/artcraft/artcraft_api_defs", # TODO: api_clients/{artcraft}/{api_defs,client}
"crates/api_clients/artcraft/artcraft_api_defs",
"crates/api_clients/artcraft/artcraft_client",
"crates/api_clients/artcraft/artcraft_router",
"crates/api_clients/cloud_storage",
"crates/api_clients/fal_client",
"crates/api_clients/google_drive_common",
"crates/api_clients/google_drive_downloader",
"crates/api_clients/google_sign_in",
"crates/api_clients/grok_client",
"crates/api_clients/midjourney_client",
"crates/api_clients/muapi_client",
"crates/api_clients/openai_sora_client",
"crates/api_clients/openart_client",
"crates/api_clients/muapi_client",
"crates/api_clients/seedance2pro",
"crates/api_clients/rootly_client",
"crates/api_clients/runwayml_client",
"crates/api_clients/seedance2pro_client",
"crates/api_clients/thumbnail_generator",
"crates/api_clients/world_labs_api",
"crates/api_clients/world_labs_client",
"crates/api_clients/worldlabs_api_client",
"crates/api_clients/worldlabs_consumer_client",
# CLI tools for operations, etc.
"crates/cli/devenv/dev-database-seed",
"crates/cli/devenv/dev-upload-media-file",
"crates/cli/devenv/scrape-test",
"crates/cli/production/db-backfill",
"crates/cli/production/db-backup",
"crates/cli/production/db-cleanup",
@@ -44,7 +45,6 @@ members = [
"crates/lib/actix_cors_configs",
"crates/lib/actix_helpers",
"crates/lib/bootstrap",
"crates/lib/bucket_paths",
"crates/lib/build_metadata",
"crates/lib/caching/memory_caching",
"crates/lib/caching/memory_store",
@@ -53,19 +53,14 @@ members = [
"crates/lib/cloudflare_mitigation",
"crates/lib/concurrency",
"crates/lib/cookie_store",
"crates/lib/cookies",
"crates/lib/data/collections",
"crates/lib/data/crockford",
"crates/lib/data/datetimes",
"crates/lib/data/primitives",
"crates/lib/data/web_base64",
"crates/lib/deprecated/config", # TODO(bt, 2023-02-13): Deprecate or repurpose
"crates/lib/deprecated/http_server_common", # TODO(bt, 2023-03-20): Move useful pieces to actix_helpers
"crates/lib/dimensions",
"crates/lib/easyenv", # NB: Vendored version of https://github.com/echelon/easyenv.rs that we'll improve.
"crates/lib/email_sender",
"crates/lib/errors",
"crates/lib/files/archives",
"crates/lib/ffmpeg_utils",
"crates/lib/files/file_to_base64_url",
"crates/lib/files/filesys",
"crates/lib/files/hashing",
@@ -73,35 +68,41 @@ members = [
"crates/lib/files/media",
"crates/lib/files/mimetypes",
"crates/lib/files/videos",
"crates/lib/files/zip_archives",
"crates/lib/http_headers",
"crates/lib/idempotency",
"crates/api_clients/artcraft/artcraft_router",
"crates/lib/image_algorithms",
"crates/lib/jobs_common",
"crates/lib/jwt_light",
"crates/lib/jwt_signer",
"crates/lib/logging",
"crates/lib/machine_learning/ml_weights_registry",
"crates/lib/machine_learning/prompt_engineering",
"crates/lib/markdown",
"crates/lib/migration",
"crates/lib/password",
"crates/lib/server_environment",
"crates/lib/sqlx_mysql_helpers",
"crates/lib/storyteller_root",
"crates/lib/subprocess_common",
"crates/lib/test_data",
"crates/lib/testing",
"crates/lib/tts_common",
"crates/lib/url_config",
"crates/lib/url_utils",
"crates/lib/user_input_common",
"crates/lib/users",
"crates/lib/web_scrapers",
"crates/lib/uuid_utils",
#"crates/lib/api_clients/fakeyou_client",
#"crates/lib/machine_learning/ml_models",
#"crates/lib/deprecated/machine_learning/ml_models",
# Shared libraries (Deprecated)
"crates/lib/deprecated/config", # TODO(bt, 2023-02-13): Deprecate or repurpose
"crates/lib/deprecated/email_sender",
"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",
# Schema (buckets): S3/R2 topology
"crates/schema/buckets/bucket_paths",
# Schema (database): queries, database layouts, Redis keys, etc.
"crates/schema/database/elasticsearch_schema",
"crates/schema/database/migration",
"crates/schema/database/mysql_queries",
"crates/schema/database/redis_common",
"crates/schema/database/redis_schema",
@@ -116,31 +117,34 @@ members = [
"crates/schema/public/tokens",
# Services: API servers, jobs, workers, etc.
"crates/service/job/analytics_job",
"crates/service/job/download_job",
"crates/service/job/email_sender_job",
"crates/service/job/es-update-job",
"crates/service/job/inference_job",
"crates/service/job/seedance2_pro_job",
"crates/service/job/video_thumbnail_job",
"crates/service/job/worldlabs_job",
"crates/service/job/tts_download_job",
"crates/service/job/tts_inference_job",
"crates/service/job/w2l_download_job",
"crates/service/job/w2l_inference_job",
"crates/service/web/dummy_service",
"crates/service/web/storyteller_web",
# Services (Deprecated) - may still have some use
"crates/service/job/deprecated/analytics_job",
"crates/service/job/deprecated/download_job",
"crates/service/job/deprecated/email_sender_job",
"crates/service/job/deprecated/es-update-job",
"crates/service/job/deprecated/inference_job",
"crates/service/job/deprecated/tts_download_job",
"crates/service/job/deprecated/tts_inference_job",
"crates/service/job/deprecated/w2l_download_job",
"crates/service/job/deprecated/w2l_inference_job",
# Service Plugins: complete subsystems that can be plugged into services (users, billing, etc.)
"crates/service/plugins/billing",
"crates/service/plugins/billing_artcraft",
"crates/service/plugins/component_traits",
"crates/service/plugins/inference_workloads",
"crates/service/plugins/user_traits_component",
# Testing code
"crates/testing/test_data",
"crates/testing/test_utils",
# Vendored code
"crates/vendor/fal-rs",
"crates/vendor/fal-rs/fal-derive",
"crates/vendor/fal-rs/generate",
"crates/vendor/limitation",
"crates/vendor/tauri-plugin-http",
"crates/vendor/Symphonia/symphonia",
@@ -212,50 +216,57 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
# Setting paths here ensures we don't need to do so in every crate's Cargo.toml
# It makes refactoring much easier.
actix_artcraft = { path = "crates/lib/actix_artcraft" }
artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" }
actix_helpers = { path = "crates/lib/actix_helpers" }
artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" }
artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" }
bootstrap = { path = "crates/lib/bootstrap" }
bucket_paths = { path = "crates/schema/buckets/bucket_paths" }
build_metadata = { path = "crates/lib/build_metadata" }
cloud_storage = { path = "crates/api_clients/cloud_storage" }
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
cloudflare_mitigation = { path = "crates/lib/cloudflare_mitigation" }
collections = { path = "crates/lib/data/collections" }
component_traits = { path = "crates/service/plugins/component_traits" }
concurrency = { path = "crates/lib/concurrency" }
cookie_store = { path = "crates/lib/cookie_store" }
cookies = { path = "crates/lib/cookies" }
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" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
grok_client = { path = "crates/api_clients/grok_client" }
rootly_client = { path = "crates/api_clients/rootly_client" }
http_headers = { path = "crates/lib/http_headers" }
idempotency = { path = "crates/lib/idempotency" }
images = { path = "crates/lib/files/images" }
jobs_common = { path = "crates/lib/jobs_common" }
jwt_light = { path = "crates/lib/jwt_light" }
jwt_signer = { path = "crates/lib/jwt_signer" }
memory_caching = { path = "crates/lib/caching/memory_caching" }
memory_store = { path = "crates/lib/caching/memory_store" }
midjourney_client = { path = "crates/api_clients/midjourney_client" }
mimetypes = { path = "crates/lib/files/mimetypes" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
muapi_client = { path = "crates/api_clients/muapi_client" }
seedance2pro = { path = "crates/api_clients/seedance2pro" }
mysql_queries = { path = "crates/schema/database/mysql_queries" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
seedance2pro_client = { path = "crates/api_clients/seedance2pro_client" }
shared_service_components = { path = "crates/lib/shared_service_components" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
test_data = { path = "crates/lib/test_data" }
testing = { path = "crates/lib/testing" }
subprocess_common = { path = "crates/lib/deprecated/subprocess_common" }
test_data = { path = "crates/testing/test_data" }
test_utils = { path = "crates/testing/test_utils" }
tokens = { path = "crates/schema/public/tokens" }
url_utils = { path = "crates/lib/url_utils" }
users = { path = "crates/lib/users" }
uuid_utils = { path = "crates/lib/uuid_utils" }
web_base64 = { path = "crates/lib/data/web_base64" }
world_labs_api = { path = "crates/api_clients/world_labs_api" }
world_labs_client = { path = "crates/api_clients/world_labs_client" }
worldlabs_api_client = { path = "crates/api_clients/worldlabs_api_client" }
worldlabs_consumer_client = { path = "crates/api_clients/worldlabs_consumer_client" }
# External libs
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
@@ -271,32 +282,42 @@ actix-web = "4.7.0" # TODO(bt): We're behind and need to bump versions.
actix-web-actors = "4.3.1+deprecated"
actix-web-lab = "0.20.2"
anyhow = "1.0.102"
assertor = "0.0.3"
base64 = "0.22.1"
bcrypt = "0.15"
bstr = "1.12.1"
bytes = "1.10.1"
chrono = { version = "0.4.41", features = ["serde"] }
clap = { version = "4.5.60", features = ["derive"] }
byteorder = "1.5.0"
bytes = "1.11.1"
chrono = { version = "0.4.44", features = ["serde"] }
clap = { version = "=4.5.60", features = ["derive"] }
cookie = "0.18.1"
dotenv = "0.15.0"
elasticsearch = { version = "8.5.0-alpha.1", default-features = false, features = ["rustls-tls"] }
env = "1.0.1"
env_logger = "0.11.9"
futures = { version = "0.3.31" }
hex-literal = "1.0.0"
indexmap = "2.9.0"
futures = { version = "0.3.32" }
hex-literal = "1.1.0"
hostname = "0.4.0"
indexmap = "2.13.0"
log = "0.4.29"
md-5 = "0.10.6" # insecure but used for gravatar
once_cell = "1.21.3"
mockall = "0.12.1"
once_cell = "1.21.4"
r2d2 = "0.8.10"
r2d2_redis = "0.14.0"
rand = "0.9.1"
redis = { version = "1.0.2", features = ["r2d2", "tokio-rustls-comp", "tokio-comp", "cluster-async", "aio"] }
regex = "1.12.3"
reqwest = { version = "0.12.23", default-features = false, features = [ "charset", "http2", "system-proxy", "json", "cookies", "rustls-tls", "gzip" ] }
reqwest-websocket = { version = "0.5.0" }
serde = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.219"
serde_json = "1.0.140"
rust-s3 = { version = "0.36.0-beta.2", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_derive = "1.0.228"
serde_json = "1.0.149"
serde_yaml = "0.9.34+deprecated"
sha2 = "0.11.0-rc.2"
sha2 = "0.11.0-rc.5"
sha3 = "0.10.8"
speculoos = "0.11.0"
strum = { version = "0.28.0", features = ["derive"] }
tauri = { version = "2.8.3", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
tauri-plugin-dialog = "2.3.3"
@@ -304,12 +325,16 @@ tauri-plugin-fs = "2.4.2"
tauri-plugin-log = "2.6.0"
tauri-plugin-upload = "2.3.1"
tempdir = "0.3.7"
tempfile = "3.20.0"
thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["macros"] }
toml = "0.8.23"
url = { version = "2.5.4", features = ["serde"] }
uuid = "1.18.1"
tempfile = "3.27.0"
thiserror = "2.0.18"
time = "0.3.36"
tokio = { version = "1.50.0", features = ["macros"] }
tokio-util = "0.7.12"
toml = "1.0.7+spec-1.1.0"
url = { version = "2.5.8", features = ["serde"] }
utoipa = { version = "5.4.0", features = ["chrono", "actix_extras", "url"] }
uuid = "1.22.0"
walkdir = "2.4.0"
# NB: wreq gets past fingerprinting, but we have to deal with platform-specific build issues.
#
@@ -0,0 +1,11 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
DROP COLUMN maybe_generation_mode,
DROP COLUMN maybe_aspect_ratio,
DROP COLUMN maybe_resolution,
DROP COLUMN maybe_duration,
DROP COLUMN maybe_batch_count,
DROP COLUMN maybe_generate_audio;
@@ -0,0 +1,11 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
ADD COLUMN maybe_generation_mode VARCHAR(24) DEFAULT NULL,
ADD COLUMN maybe_aspect_ratio VARCHAR(24) DEFAULT NULL,
ADD COLUMN maybe_resolution VARCHAR(16) DEFAULT NULL,
ADD COLUMN maybe_duration VARCHAR(8) DEFAULT NULL,
ADD COLUMN maybe_batch_count TINYINT UNSIGNED DEFAULT NULL,
ADD COLUMN maybe_generate_audio BOOLEAN DEFAULT NULL;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
DROP COLUMN maybe_duration_seconds,
ADD COLUMN maybe_duration VARCHAR(8) DEFAULT NULL;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
DROP COLUMN maybe_duration,
ADD COLUMN maybe_duration_seconds INT UNSIGNED DEFAULT NULL;
@@ -27,6 +27,8 @@ CREATE TABLE prompts (
-- What type of model was used to generate the result.
-- NB: We're conflating prompts with outputs, but in a
-- sense the model is inextricably part of the prompt.
--
-- Represented by the `CommonModelType` enum.
maybe_model_type VARCHAR(32) DEFAULT NULL,
-- What service provided the generation.
@@ -52,6 +54,29 @@ CREATE TABLE prompts (
-- TEXT = 65,535 bytes (64 KiB), ~= 4 bytes per UTF-8 character, ~ 16383 characters.
maybe_other_args TEXT DEFAULT NULL,
-- ========== GENERATION PARAMETERS ==========
-- Represented by the enum `CommonGenerationMode`.
-- The generation mode (eg. keyframe, reference, inpaint, etc.)
maybe_generation_mode VARCHAR(24) DEFAULT NULL,
-- Represented by the enum `CommonAspectRatio`.
-- The aspect ratio for the prompt (eg. square, auto, wide_three_by_two, etc.)
maybe_aspect_ratio VARCHAR(24) DEFAULT NULL,
-- Represented by the enum `CommonResolution`.
-- The resolution for the prompt (eg. four_k, 720p, etc.)
maybe_resolution VARCHAR(16) DEFAULT NULL,
-- The duration in seconds
maybe_duration_seconds INT UNSIGNED DEFAULT NULL,
-- The number of outputs to generate (0-255)
maybe_batch_count TINYINT UNSIGNED DEFAULT NULL,
-- Whether to generate audio
maybe_generate_audio BOOLEAN DEFAULT NULL,
-- ========== ABUSE TRACKING ==========
-- For abuse tracking.
@@ -66,6 +66,7 @@ CREATE TABLE users (
-- - "explore_media"
-- - "studio"
-- - "video_style_transfer"
-- - "sd_wl" (seedance whitelist)
maybe_feature_flags VARCHAR(255) DEFAULT NULL,
-- DEPRECATED
+11 -37
View File
@@ -130,47 +130,20 @@ RUN echo "Disk usage at current directory (after tests):"
RUN pwd
RUN du -hsc * | sort -hr
# Build all the binaries.
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin storyteller-web
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin seedance2-pro-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin worldlabs-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin dummy-service
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin analytics-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin email-sender-job
# Build all the binaries in a single cargo invocation.
# This lets cargo parallelize shared dependency compilation across all targets,
# which is significantly faster than building each binary sequentially.
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin storyteller-web \
--bin seedance2-pro-job \
--bin worldlabs-job \
--bin video-thumbnail-job \
--bin dummy-service \
--bin analytics-job \
--bin email-sender-job \
--bin es-update-job
# Print a report on disk space
@@ -210,6 +183,7 @@ RUN echo -n ${GIT_SHA} > GIT_SHA
COPY --from=builder /tmp/target/release/storyteller-web /
COPY --from=builder /tmp/target/release/seedance2-pro-job /
COPY --from=builder /tmp/target/release/worldlabs-job /
COPY --from=builder /tmp/target/release/video-thumbnail-job /
COPY --from=builder /tmp/target/release/dummy-service /
COPY --from=builder /tmp/target/release/analytics-job /
COPY --from=builder /tmp/target/release/email-sender-job /
+4 -4
View File
@@ -178,11 +178,11 @@ RUN touch .env-secrets
# Some services have default env files that live under their code directories
# These should also be readable from the relative current path
COPY crates/service/job/download_job/config/download-job.common.env .
COPY crates/service/job/download_job/config/download-job.production.env .
COPY crates/service/job/deprecated/download_job/config/download-job.common.env .
COPY crates/service/job/deprecated/download_job/config/download-job.production.env .
COPY crates/service/job/inference_job/config/inference-job.common.env .
COPY crates/service/job/inference_job/config/inference-job.production.env .
COPY crates/service/job/deprecated/inference_job/config/inference-job.common.env .
COPY crates/service/job/deprecated/inference_job/config/inference-job.production.env .
# # Need python to make use of other containers' venv
# # TODO(bt,2023-04-26): This is only necessary for download-job and inference-job
@@ -33,7 +33,7 @@ serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
utoipa = { version = "4", features = ["chrono", "actix_extras"] }
utoipa.workspace = true
[dev-dependencies]
anyhow.workspace = true
@@ -1,11 +1,12 @@
use serde_derive::{Deserialize, Serialize};
use url::Url;
use utoipa::ToSchema;
// TODO(bt,2025-08-20): Replace the storyteller-web version of this.
// All it needs is the impl ported into a builder.
/// Cover image links can only be jpg, png, etc. No videos.
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct CoverImageLinks {
/// Primary link to the cover image via the CDN.
pub cdn_url: Url,
@@ -1,5 +1,6 @@
use crate::common::responses::cover_image_links::CoverImageLinks;
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
// TODO(bt,2025-08-20): Replace the storyteller-web version of this.
// We can't drop the old version just yet due to the `impl` (easy)
@@ -8,7 +9,7 @@ use serde_derive::{Deserialize, Serialize};
/// Everything we need to create a cover image.
/// Cover images are small descriptive images that can be set for any media file.
/// If a cover image is set, this is the path to the asset.
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct MediaFileCoverImageDetails {
// /// (DEPRECATED) URL path to the media file
// #[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
@@ -36,7 +37,7 @@ pub struct MediaFileCoverImageDetails {
/// The default cover is composed of an image and color pair that are
/// predefined by the frontend.
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct MediaFileDefaultCover {
pub image_index: u8,
pub color_index: u8,
@@ -1,8 +1,9 @@
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::users::UserToken;
use utoipa::ToSchema;
/// Everything we need to refer to a user on the public web interface.
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct UserDetailsLight {
/// The token for the user
pub user_token: UserToken,
@@ -26,7 +27,7 @@ pub struct UserDetailsLight {
// In the future, we'll also support user-uploaded avatars that we store on our servers.
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct UserDefaultAvatarInfo {
pub image_index: u8,
pub color_index: u8,
@@ -3,7 +3,7 @@ use utoipa::ToSchema;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_image_model::CommonImageModel;
use enums::common::generation::common_video_resolution::CommonVideoResolution;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation_provider::GenerationProvider;
pub const ESTIMATE_IMAGE_COST_PATH: &str = "/v1/generate/cost_estimate/image";
@@ -27,7 +27,7 @@ pub struct EstimateImageCostRequest {
pub aspect_ratio: Option<CommonAspectRatio>,
/// Optional resolution.
pub resolution: Option<CommonVideoResolution>,
pub resolution: Option<CommonResolution>,
/// Number of images to generate in parallel.
pub image_batch_count: Option<u16>,
@@ -3,7 +3,7 @@ use utoipa::ToSchema;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_video_model::CommonVideoModel;
use enums::common::generation::common_video_resolution::CommonVideoResolution;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation_provider::GenerationProvider;
pub const ESTIMATE_VIDEO_COST_PATH: &str = "/v1/generate/cost_estimate/video";
@@ -30,7 +30,7 @@ pub struct EstimateVideoCostRequest {
pub aspect_ratio: Option<CommonAspectRatio>,
/// Optional resolution.
pub resolution: Option<CommonVideoResolution>,
pub resolution: Option<CommonResolution>,
/// Duration in seconds.
pub duration_seconds: Option<u16>,
@@ -1,7 +1,6 @@
use crate::common::responses::job_details::{JobDetailsLipsyncRequest, JobDetailsLivePortraitRequest};
use crate::common::responses::media_links::MediaLinks;
use chrono::{DateTime, Utc};
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
use enums::common::job_status_plus::JobStatusPlus;
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
@@ -0,0 +1,55 @@
use chrono::{DateTime, Utc};
use enums::by_table::users::user_signup_method::UserSignupMethod;
use enums::by_table::users::user_signup_source::UserSignupSource;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::users::UserToken;
use utoipa::ToSchema;
pub const MODERATOR_LIST_SUBSCRIBING_USERS_BY_SIGNUP_DATE_PATH: &str = "/v1/moderation/users/list_subscribers_by_signup_date";
#[derive(Deserialize, ToSchema)]
pub struct ModeratorListSubscribingUsersBySignupDateRequest {
/// Optional cursor for pagination. Pass the `next_cursor` from a previous response.
pub id_cursor: Option<u64>,
}
#[derive(Serialize, ToSchema)]
pub struct ModeratorListSubscribingUsersBySignupDateResponse {
pub success: bool,
pub users: Vec<ModeratorListSubscribingUsersBySignupDateEntry>,
/// The cursor for the next page. `None` if there are no more results.
pub next_cursor: Option<u64>,
}
#[derive(Serialize, ToSchema)]
pub struct ModeratorListSubscribingUsersBySignupDateEntry {
// -- User fields --
pub id: u64,
pub token: UserToken,
pub username: String,
pub display_name: String,
pub username_is_not_customized: bool,
pub email_address: String,
pub email_confirmed: bool,
pub is_without_password: bool,
pub ip_address_creation: String,
pub maybe_source: Option<UserSignupSource>,
pub maybe_signup_method: Option<UserSignupMethod>,
pub created_at: DateTime<Utc>,
pub maybe_referral_url: Option<String>,
pub is_temporary: bool,
// TODO: Use enum types/strong types -
// -- Subscription fields --
pub subscription_namespace: String,
pub subscription_product_slug: String,
pub maybe_stripe_subscription_id: Option<String>,
pub maybe_stripe_customer_id: Option<String>,
pub maybe_stripe_product_id: Option<String>,
pub maybe_stripe_price_id: Option<String>,
pub maybe_stripe_subscription_status: Option<String>,
pub maybe_stripe_recurring_interval: Option<String>,
pub maybe_stripe_invoice_is_paid: Option<bool>,
pub maybe_cancel_at: Option<DateTime<Utc>>,
pub maybe_canceled_at: Option<DateTime<Utc>>,
}
@@ -0,0 +1,40 @@
use chrono::{DateTime, Utc};
use enums::by_table::users::user_signup_method::UserSignupMethod;
use enums::by_table::users::user_signup_source::UserSignupSource;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::users::UserToken;
use utoipa::ToSchema;
pub const MODERATOR_LIST_USERS_BY_SIGNUP_DATE_PATH: &str = "/v1/moderation/users/list_all_by_signup_date";
#[derive(Deserialize, ToSchema)]
pub struct ModeratorListUsersBySignupDateRequest {
/// Optional cursor for pagination. Pass the `next_cursor` from a previous response.
pub id_cursor: Option<u64>,
}
#[derive(Serialize, ToSchema)]
pub struct ModeratorListUsersBySignupDateResponse {
pub success: bool,
pub users: Vec<ModeratorListUsersBySignupDateEntry>,
/// The cursor for the next page. `None` if there are no more results.
pub next_cursor: Option<u64>,
}
#[derive(Serialize, ToSchema)]
pub struct ModeratorListUsersBySignupDateEntry {
pub id: u64,
pub token: UserToken,
pub username: String,
pub display_name: String,
pub username_is_not_customized: bool,
pub email_address: String,
pub email_confirmed: bool,
pub is_without_password: bool,
pub ip_address_creation: String,
pub maybe_source: Option<UserSignupSource>,
pub maybe_signup_method: Option<UserSignupMethod>,
pub created_at: DateTime<Utc>,
pub maybe_referral_url: Option<String>,
pub is_temporary: bool,
}
@@ -1,2 +1,4 @@
pub mod list_subscribing_users_by_signup_date;
pub mod list_users_by_signup_date;
pub mod user_lookup;
pub mod user_lookup_by_stripe_customer_id;
pub mod user_lookup_by_stripe_customer_id;
@@ -1,8 +1,11 @@
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_generation_mode::CommonGenerationMode;
use enums::common::generation::common_model_type::CommonModelType;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation_provider::GenerationProvider;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::prompts::PromptToken;
use utoipa::ToSchema;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
pub const CREATE_PROMPT_PATH: &str = "/v1/prompts/create";
@@ -14,14 +17,32 @@ pub struct CreatePromptRequest {
/// OPTIONAL. The positive prompt.
pub positive_prompt: Option<String>,
/// OPTIONAL. The positive prompt.
/// OPTIONAL. The negative prompt.
pub negative_prompt: Option<String>,
/// OPTIONAL. The model type.
pub model_type: Option<ModelType>,
pub model_type: Option<CommonModelType>,
/// OPTIONAL. The service used.
pub generation_provider: Option<GenerationProvider>,
/// OPTIONAL. The generation mode (eg. keyframe, reference, inpaint, etc.)
pub maybe_generation_mode: Option<CommonGenerationMode>,
/// OPTIONAL. The aspect ratio (eg. square, auto, wide_three_by_two, etc.)
pub maybe_aspect_ratio: Option<CommonAspectRatio>,
/// OPTIONAL. The resolution (eg. one_k, two_k, four_k, etc.)
pub maybe_resolution: Option<CommonResolution>,
/// OPTIONAL. The number of outputs to generate (0-255).
pub maybe_batch_count: Option<u8>,
/// OPTIONAL. Whether to generate audio.
pub maybe_generate_audio: Option<bool>,
/// OPTIONAL. The duration in seconds.
pub maybe_duration_seconds: Option<u32>,
}
#[derive(Serialize, Deserialize, ToSchema)]
@@ -0,0 +1,149 @@
use crate::common::responses::media_links::MediaLinks;
use chrono::{DateTime, Utc};
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
use enums::by_table::prompts::prompt_type::PromptType;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_generation_mode::CommonGenerationMode;
use enums::common::generation::common_model_type::CommonModelType;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation_provider::GenerationProvider;
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use utoipa::ToSchema;
use enums::common::generation::common_model_class::CommonModelClass;
pub const GET_PROMPT_PATH: &str = "/v1/prompts/{token}";
#[derive(Deserialize, ToSchema)]
pub struct GetPromptPathInfo {
pub token: PromptToken,
}
#[derive(Serialize, ToSchema)]
pub struct GetPromptSuccessResponse {
pub success: bool,
pub prompt: PromptInfo,
}
#[derive(Serialize, ToSchema)]
pub struct GetPromptImageContextItem {
pub media_token: MediaFileToken,
pub semantic: PromptContextSemanticType,
pub media_links: MediaLinks,
}
#[derive(Serialize, ToSchema)]
pub struct PromptInfo {
pub token: PromptToken,
/// The type of prompt.
/// Note: Prompts may or may not be compatible across systems.
pub prompt_type: PromptType,
/// The type of model used
pub maybe_model_type: Option<CommonModelType>,
/// The class of model used
pub maybe_model_class: Option<CommonModelClass>,
/// The service provider used
pub maybe_generation_provider: Option<GenerationProvider>,
/// Positive prompt (technically optional, but usually present)
pub maybe_positive_prompt: Option<String>,
/// Negative prompt (optional)
pub maybe_negative_prompt: Option<String>,
/// OPTIONAL. The generation mode (eg. keyframe, reference, inpaint, etc.)
pub maybe_generation_mode: Option<CommonGenerationMode>,
/// OPTIONAL. The aspect ratio (eg. square, auto, wide_three_by_two, etc.)
pub maybe_aspect_ratio: Option<CommonAspectRatio>,
/// OPTIONAL. The resolution (eg. one_k, two_k, four_k, etc.)
pub maybe_resolution: Option<CommonResolution>,
/// OPTIONAL. The number of outputs to generate (0-255).
pub maybe_batch_count: Option<u8>,
/// OPTIONAL. Whether to generate audio.
pub maybe_generate_audio: Option<bool>,
/// OPTIONAL. The duration in seconds.
pub maybe_duration_seconds: Option<u32>,
/// Context images (optional)
pub maybe_context_images: Option<Vec<GetPromptImageContextItem>>,
/// Scheduled / travel prompt (optional)
pub maybe_travel_prompt: Option<String>,
/// If a "style" was used, this is the name of it.
/// This might not be present for all types of inference
/// and typically only applies to video style transfer.
pub maybe_style_name: Option<StyleTransferName>,
/// How many milliseconds it took to run generation.
pub maybe_inference_duration_millis: Option<u64>,
/// If a "strength" was used.
/// Typically only for video style transfer.
pub maybe_strength: Option<f32>,
/// If a frame skip setting was used.
pub maybe_frame_skip: Option<u8>,
/// If a face detailer was used.
/// This might not be present for all types of inference
/// and typically only applies to video style transfer.
pub used_face_detailer: bool,
/// If an upscaling pass was used.
/// This might not be present for all types of inference
/// and typically only applies to video style transfer.
pub used_upscaler: bool,
/// If lipsync was enabled.
/// This might not be present for all types of inference
/// and typically only applies to video style transfer.
pub lipsync_enabled: bool,
/// If LCM was disabled.
/// Only staff can do this for now.
pub lcm_disabled: bool,
/// If the cinematic workflow was used.
/// Only staff can do this for now.
pub use_cinematic: bool,
/// If a global IP Adapter Image was used, this is the details.
/// NB: We can't easily query for this without another DB round trip,
/// so the frontend should query for it instead.
pub maybe_global_ipa_image_token: Option<MediaFileToken>,
/// Fields that only moderators should see.
pub maybe_moderator_fields: Option<PromptInfoModeratorFields>,
pub created_at: DateTime<Utc>,
}
#[derive(Serialize, ToSchema)]
pub struct PromptInfoModeratorFields {
/// How many milliseconds it took to run generation.
pub maybe_inference_duration_millis: Option<u64>,
/// Version of Yae's workflow
/// Used for logging and debugging by the art team.
pub main_ipa_workflow: Option<String>,
/// Version of Yae's face detailer workflow
/// Used for logging and debugging by the art team.
pub face_detailer_workflow: Option<String>,
/// Version of Yae's upscaler workflow
/// Used for logging and debugging by the art team.
pub upscaler_workflow: Option<String>,
}
@@ -1 +1,2 @@
pub mod create_prompt;
pub mod create_prompt;
pub mod get_prompt;
@@ -28,7 +28,7 @@ cloudflare_errors.workspace = true
enums.workspace = true
enums_public.workspace = true
errors.workspace = true
idempotency.workspace = true
uuid_utils.workspace = true
mimetypes.workspace = true
tokens.workspace = true
@@ -5,7 +5,6 @@ use crate::utils::basic_json_delete_request::basic_json_delete_request;
use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess;
use artcraft_api_defs::media_file::delete_media_file::DeleteMediaFileRequest;
use artcraft_api_defs::subscriptions::get_session_subscription::GetSessionSubscriptionResponse;
use enums::common::payments_namespace::PaymentsNamespace;
use log::debug;
use tokens::tokens::media_files::MediaFileToken;
@@ -7,16 +7,9 @@ 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::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::common::generation_provider::GenerationProvider;
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 idempotency::uuid::generate_random_uuid;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -52,7 +45,6 @@ pub struct LegacyUploadMediaFileFromFileArgs<'a, P: AsRef<Path>> {
// pub maybe_batch_token: Option<&'a BatchGenerationToken>,
}
/// Upload using the legacy endpoint (spz, etc.)
pub async fn legacy_upload_media_file_from_file<P: AsRef<Path>>(
args: LegacyUploadMediaFileFromFileArgs<'_, P>,
@@ -7,15 +7,8 @@ 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::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
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 idempotency::uuid::generate_random_uuid;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -7,16 +7,9 @@ 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::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::common::generation_provider::GenerationProvider;
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 idempotency::uuid::generate_random_uuid;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -52,7 +45,6 @@ pub struct UploadImageFromFileArgs<'a, P: AsRef<Path>> {
pub maybe_batch_token: Option<&'a BatchGenerationToken>,
}
/// Upload an image media file from a file.
pub async fn upload_image_media_file_from_file<P: AsRef<Path>>(
args: UploadImageFromFileArgs<'_, P>,
@@ -5,15 +5,8 @@ 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::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
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 idempotency::uuid::generate_random_uuid;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -5,15 +5,8 @@ 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::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
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 idempotency::uuid::generate_random_uuid;
use uuid_utils::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -49,8 +42,6 @@ pub struct UploadVideoFromFileArgs<'a, P: AsRef<Path>> {
// pub maybe_batch_token: Option<&'a BatchGenerationToken>,
}
// TODO(bt,2025-04-22): Share API definitions between client and server in common crate.
#[derive(Deserialize, Debug)]
@@ -20,10 +20,10 @@ artcraft_api_defs.workspace = true
artcraft_client.workspace = true
fal_client.workspace = true
muapi_client.workspace = true
seedance2pro.workspace = true
seedance2pro_client.workspace = true
tokens.workspace = true
url_utils.workspace = true
world_labs_api.workspace = true
worldlabs_api_client.workspace = true
# External
anyhow.workspace = true
@@ -1,4 +1,4 @@
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
pub struct RouterSeedance2ProClient {
pub(crate) session: Seedance2ProSession,
@@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};
use artcraft_client::error::storyteller_error::StorytellerError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use muapi_client::error::muapi_error::MuapiError;
use seedance2pro::error::seedance2pro_error::Seedance2ProError;
use seedance2pro_client::error::seedance2pro_error::Seedance2ProError;
#[derive(Debug)]
pub enum ProviderError {
@@ -1,7 +1,7 @@
use crate::generate::generate_splat::plan::artcraft::plan_generate_splat_artcraft_marble_0p1_mini::PlanArtcraftMarble0p1Mini;
use crate::generate::generate_splat::splat_generation_cost_estimate::SplatGenerationCostEstimate;
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
use world_labs_api::pricing::check_pricing::{calculate_cost, InputType};
use worldlabs_api_client::api::api_types::world_labs_model::WorldLabsModel;
use worldlabs_api_client::pricing::check_pricing::{calculate_cost, InputType};
pub(crate) fn estimate_splat_cost_artcraft_marble_0p1_mini(
plan: &PlanArtcraftMarble0p1Mini<'_>,
@@ -1,7 +1,7 @@
use crate::generate::generate_splat::plan::artcraft::plan_generate_splat_artcraft_marble_0p1_plus::PlanArtcraftMarble0p1Plus;
use crate::generate::generate_splat::splat_generation_cost_estimate::SplatGenerationCostEstimate;
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
use world_labs_api::pricing::check_pricing::{calculate_cost, InputType};
use worldlabs_api_client::api::api_types::world_labs_model::WorldLabsModel;
use worldlabs_api_client::pricing::check_pricing::{calculate_cost, InputType};
pub(crate) fn estimate_splat_cost_artcraft_marble_0p1_plus(
plan: &PlanArtcraftMarble0p1Plus<'_>,
@@ -1,6 +1,6 @@
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::Seedance2p0BatchCount;
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro::requests::generate_video::generate_video::{BatchCount, GenerateVideoArgs, Resolution};
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro_client::requests::generate_video::generate_video::{BatchCount, GenerateVideoArgs, ModelType, Resolution};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
@@ -22,6 +22,7 @@ pub (crate) fn estimate_video_cost_artcraft_seedance2p0(
let args = GenerateVideoArgs {
session: &dummy_session,
model_type: ModelType::Seedance2Pro,
prompt: String::new(),
resolution: Resolution::Square1x1,
duration_seconds,
@@ -34,6 +35,7 @@ pub (crate) fn estimate_video_cost_artcraft_seedance2p0(
reference_video_urls: None,
reference_audio_urls: None,
use_face_blur_hack: None,
host_override: None,
};
let cost_in_usd_cents = args.estimate_cost_in_usd_cents();
@@ -95,9 +97,9 @@ mod tests {
assert_eq!(estimate_usd_cents(5, 2), 160);
assert_eq!(estimate_usd_cents(15, 2), 480);
// Batch 4 = 3× (not 4×)
assert_eq!(estimate_usd_cents(4, 4), 192);
assert_eq!(estimate_usd_cents(5, 4), 240);
assert_eq!(estimate_usd_cents(15, 4), 720);
// Batch 4 = 4×
assert_eq!(estimate_usd_cents(4, 4), 256);
assert_eq!(estimate_usd_cents(5, 4), 320);
assert_eq!(estimate_usd_cents(15, 4), 960);
}
}
@@ -1,5 +1,5 @@
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro::requests::generate_video::generate_video::{GenerateVideoArgs, Resolution};
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro_client::requests::generate_video::generate_video::{GenerateVideoArgs, ModelType, Resolution};
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0::PlanSeedance2proSeedance2p0;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
@@ -12,6 +12,7 @@ pub(crate) fn estimate_video_cost_seedance2pro_seedance2p0(
let args = GenerateVideoArgs {
session: &dummy_session,
model_type: ModelType::Seedance2Pro,
prompt: String::new(),
resolution: Resolution::Square1x1, // Resolution does not affect cost
duration_seconds: plan.duration_seconds,
@@ -22,6 +23,7 @@ pub(crate) fn estimate_video_cost_seedance2pro_seedance2p0(
reference_video_urls: None,
reference_audio_urls: None,
use_face_blur_hack: None,
host_override: None,
};
let cost_in_credits = args.estimate_credits();
@@ -6,13 +6,13 @@ use crate::generate::generate_video::generate_video_response::{
};
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0::PlanSeedance2proSeedance2p0;
use crate::utils::download_file::download_file;
use seedance2pro::requests::generate_video::generate_video::{
generate_video, GenerateVideoArgs,
use seedance2pro_client::requests::generate_video::generate_video::{
generate_video, GenerateVideoArgs, ModelType,
};
use seedance2pro::requests::prepare_file_upload::prepare_file_upload::{
use seedance2pro_client::requests::prepare_file_upload::prepare_file_upload::{
prepare_file_upload, PrepareFileUploadArgs,
};
use seedance2pro::requests::upload_file::upload_file::{
use seedance2pro_client::requests::upload_file::upload_file::{
upload_file, UploadFileArgs,
};
use url_utils::extension::extract_extension_from_url::{
@@ -34,6 +34,7 @@ pub async fn execute_seedance2pro_seedance2p0(
let args = GenerateVideoArgs {
session,
model_type: ModelType::Seedance2Pro,
prompt: plan.prompt.clone().unwrap_or_default(),
resolution: plan.resolution,
duration_seconds: plan.duration_seconds,
@@ -44,6 +45,7 @@ pub async fn execute_seedance2pro_seedance2p0(
reference_video_urls,
reference_audio_urls,
use_face_blur_hack: None,
host_override: None,
};
let response = generate_video(args)
@@ -58,7 +60,7 @@ pub async fn execute_seedance2pro_seedance2p0(
/// Downloads a file from a source URL and re-uploads it to seedance2pro CDN.
async fn upload_to_seedance2pro(
session: &seedance2pro::creds::seedance2pro_session::Seedance2ProSession,
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
source_url: &str,
) -> Result<String, ArtcraftRouterError> {
let extension = extract_extension_from_url_str(source_url, &ExtractExtensions::All)
@@ -70,6 +72,7 @@ async fn upload_to_seedance2pro(
let prepare_response = prepare_file_upload(PrepareFileUploadArgs {
session,
extension,
host_override: None,
})
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
@@ -77,6 +80,7 @@ async fn upload_to_seedance2pro(
let upload_response = upload_file(UploadFileArgs {
upload_url: prepare_response.upload_url,
file_bytes,
host_override: None,
})
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
@@ -85,7 +89,7 @@ async fn upload_to_seedance2pro(
}
async fn upload_optional_url(
session: &seedance2pro::creds::seedance2pro_session::Seedance2ProSession,
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
url: Option<&str>,
) -> Result<Option<String>, ArtcraftRouterError> {
match url {
@@ -95,7 +99,7 @@ async fn upload_optional_url(
}
async fn upload_optional_url_list(
session: &seedance2pro::creds::seedance2pro_session::Seedance2ProSession,
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
urls: Option<&[String]>,
) -> Result<Option<Vec<String>>, ArtcraftRouterError> {
match urls {
@@ -8,7 +8,7 @@ use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_video::generate_video_request::GenerateVideoRequest;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
use seedance2pro::requests::generate_video::generate_video::{BatchCount, Resolution};
use seedance2pro_client::requests::generate_video::generate_video::{BatchCount, Resolution};
#[derive(Debug, Clone)]
pub struct PlanSeedance2proSeedance2p0 {
+8 -9
View File
@@ -16,13 +16,13 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
# cargo-hakari workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
bucket_paths = { path = "../../lib/bucket_paths" }
bucket_paths.workspace = true
crockford.workspace = true
easyenv = { path = "../../lib/easyenv" }
easyenv.workspace = true
errors.workspace = true
filesys.workspace = true
hashing = { path = "../../lib/files/hashing" }
@@ -31,12 +31,11 @@ storyteller_root = { path = "../../lib/storyteller_root" }
# External
anyhow.workspace = true
log.workspace = true
env_logger = "0.11.3"
async-trait = "0.1.75"
env_logger.workspace = true
log.workspace = true
tokio.workspace = true
# The "never-encode-slash" feature seems to have been removed:
# https://github.com/durch/rust-s3/pull/254
rust-s3 = { version = "0.36.0-beta.2", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
# Runtime
tokio.workspace = true
rust-s3.workspace = true
+8 -7
View File
@@ -12,7 +12,7 @@ name = "fal_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
# cargo-hakari workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
@@ -25,15 +25,16 @@ futures.workspace = true
log.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
strum.workspace = true
thiserror.workspace = true
tokio.workspace = true
url.workspace = true
# For now, we'll vendor in fal.
# We need to implement webhooks and the upstream doesn't support them yet.
#fal = { version = "0.3.6", features = ["endpoints"] }
fal = { path = "../../vendor/fal-rs", features = ["endpoints"] }
[dev-dependencies]
testing = { path = "../../lib/testing" }
# Internal Dev
test_utils.workspace = true
test_data.workspace = true
# External Dev
# (None yet)
@@ -1,16 +1,14 @@
use crate::error::fal_error::FalError;
use crate::error::fal_error_plus::FalErrorPlus;
const BILLING_ERROR : &str = "{\"detail\": \"User is locked. Reason: Exhausted balance. Top up your balance at fal.ai/dashboard/billing.\"}";
/// Better classify the `fal::FalError` into a more user-friendly `FalErrorPlus`.
pub fn classify_fal_error(err: fal::FalError) -> FalErrorPlus {
// Other("{\"detail\": \"User is locked. Reason: Exhausted balance. Top up your balance at fal.ai/dashboard/billing.\"}")
/// Better classify the `FalError` into a more user-friendly `FalErrorPlus`.
pub fn classify_fal_error(err: FalError) -> FalErrorPlus {
match err {
fal::FalError::RequestError(_) => FalErrorPlus::FalError(err),
fal::FalError::ImageError(_) => FalErrorPlus::FalError(err),
fal::FalError::SerializeError(_) => FalErrorPlus::FalError(err),
fal::FalError::StreamError(_) => FalErrorPlus::FalError(err),
fal::FalError::Other(ref inner) => {
FalError::RequestError(_) => FalErrorPlus::FalError(err),
FalError::SerializeError(_) => FalErrorPlus::FalError(err),
FalError::Other(ref inner) => {
if inner == BILLING_ERROR {
FalErrorPlus::FalBillingError(inner.to_string())
} else if inner.contains("billing") {
@@ -0,0 +1,21 @@
/// Error type for Fal API interactions.
///
/// Originally from the vendored `fal` crate (`fal::FalError`).
/// Copied here so `fal_client` can be independent of the vendored crate.
#[derive(Debug, thiserror::Error)]
pub enum FalError {
#[error("fal request failed: {0}")]
RequestError(#[from] reqwest::Error),
#[error("serialization error: {0}")]
SerializeError(#[from] serde_json::Error),
#[error("error: {0}")]
Other(String),
}
impl From<String> for FalError {
fn from(s: String) -> Self {
FalError::Other(s)
}
}
@@ -1,12 +1,13 @@
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error::FalError;
use std::error::Error;
use std::fmt::{Display, Formatter};
/// Additional errors that aren't included in `fal::FalError`.
/// Additional errors that aren't included in `crate::error::fal_error::FalError`.
#[derive(Debug)]
pub enum FalErrorPlus {
/// An error arising in the `fal` crate.
FalError(fal::FalError),
FalError(crate::error::fal_error::FalError),
/// The fal API key is invalid.
FalApiKeyError(String),
/// The fal account has a billing issue
@@ -37,8 +38,8 @@ impl Display for FalErrorPlus {
impl Error for FalErrorPlus {}
impl From<fal::FalError> for FalErrorPlus {
fn from(err: fal::FalError) -> Self {
impl From<FalError> for FalErrorPlus {
fn from(err: FalError) -> Self {
classify_fal_error(err)
}
}
@@ -1,2 +1,3 @@
pub mod classify_fal_error;
pub mod fal_error;
pub mod fal_error_plus;
+1 -8
View File
@@ -1,11 +1,4 @@
//! A wrapper for the "fal" crate that bakes in a few extra recipes and utilities.
extern crate fal;
// Re-export
pub mod export {
pub use fal::*;
}
//! A fal wrapper
pub mod creds;
pub mod error;
@@ -1,8 +1,9 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::model::fal_endpoint::FalEndpoint;
use crate::model::fal_request_id::FalRequestId;
use crate::requests::api::queue::Queue;
use crate::requests::api::queue_response::QueueResponse;
use errors::AnyhowResult;
use fal::queue::{Queue, QueueResponse};
use reqwest::Client;
use serde::de::DeserializeOwned;
@@ -1,6 +1,5 @@
use fal::queue::Queue;
use crate::requests::api::queue::Queue;
use serde::de::DeserializeOwned;
// TODO(bt,2025-05-26): A macro to define these + tests.
const FLUX_PRO_ULTRA_TEXT_TO_IMAGE: &str = "fal-ai/flux-pro/v1.1-ultra";
@@ -0,0 +1,135 @@
use std::marker::PhantomData;
use reqwest::IntoUrl;
use serde::{de::DeserializeOwned, Serialize};
use log::info;
use crate::error::fal_error::FalError;
use crate::requests::api::queue::Queue;
use crate::requests::api::queue_response::QueueResponse;
use crate::requests::api::webhook_response::WebhookResponse;
/// A request to the FAL API.
///
/// Originally from the vendored `fal` crate (`fal::request::FalRequest`).
/// Copied here so `fal_client` can be independent of the vendored crate.
#[derive(Debug)]
pub struct FalRequest<Params: Serialize, Response: DeserializeOwned> {
/// The Reqwest Client to use to make requests
pub client: reqwest::Client,
/// The endpoint to make the request to
pub endpoint: String,
/// The parameters to send to the endpoint
pub params: Params,
/// The API key to use to make the request.
/// If not provided, the `FAL_API_KEY` environment variable will be used.
pub api_key: Option<String>,
phantom: PhantomData<Response>,
}
impl<Params: Serialize, Response: DeserializeOwned> FalRequest<Params, Response> {
pub fn new(endpoint: impl Into<String>, params: Params) -> Self {
Self {
client: reqwest::Client::new(),
endpoint: endpoint.into(),
params,
api_key: std::env::var("FAL_API_KEY").ok(),
phantom: PhantomData,
}
}
/// Use a specific Reqwest Client to make requests
pub fn with_client(mut self, client: reqwest::Client) -> Self {
self.client = client;
self
}
/// Use a specific API key to make requests
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
/// Send the request and wait for the response
pub async fn send(self) -> Result<Response, FalError> {
let response = self
.client
.post(format!("https://fal.run/{}", self.endpoint))
.json(&self.params)
.header(
"Authorization",
format!(
"Key {}",
self.api_key.expect(
"No fal API key provided, and FAL_API_KEY environment variable is not set"
)
),
)
.header("Content-Type", "application/json")
.send()
.await?;
if response.status() != 200 {
let error = response.text().await?;
return Err(error.into());
}
Ok(response.error_for_status()?.json().await?)
}
/// Submit the request to the Fal queue system.
pub async fn queue(self) -> Result<Queue<Response>, FalError> {
let key = self
.api_key
.expect("No fal API key provided, and FAL_API_KEY environment variable is not set");
let response = self
.client
.post(format!("https://queue.fal.run/{}", self.endpoint))
.json(&self.params)
.header("Authorization", format!("Key {}", &key))
.header("Content-Type", "application/json")
.send()
.await?;
if response.status() != 200 {
let error = response.text().await?;
return Err(error.into());
}
let payload: QueueResponse = response.error_for_status()?.json().await?;
Ok(Queue::new(self.client, self.endpoint, key, payload))
}
/// Submit the request to the Fal queue with a webhook callback URL.
pub async fn queue_webhook<U: IntoUrl>(self, url: U) -> Result<WebhookResponse, FalError> {
let key = self
.api_key
.expect("No fal API key provided, and FAL_API_KEY environment variable is not set");
let url_encoded = url.into_url()?;
let request_url = format!("https://queue.fal.run/{}?fal_webhook={}", self.endpoint, url_encoded);
info!("Sending request to FAL queue webhook: {}", request_url);
let response = self
.client
.post(request_url)
.json(&self.params)
.header("Authorization", format!("Key {}", &key))
.header("Content-Type", "application/json")
.send()
.await?;
if response.status() != 200 {
let error = response.text().await?;
return Err(error.into());
}
let payload: WebhookResponse = response.error_for_status()?.json().await?;
Ok(payload)
}
}
@@ -0,0 +1,5 @@
pub mod fal_request;
pub mod queue;
pub mod queue_response;
pub mod queue_status;
pub mod webhook_response;
@@ -0,0 +1,86 @@
use std::marker::PhantomData;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::error::fal_error::FalError;
use crate::requests::api::queue_response::QueueResponse;
use crate::requests::api::queue_status::QueueStatus;
#[derive(Debug, Serialize, Deserialize)]
pub struct Queue<Response: DeserializeOwned> {
#[serde(skip)]
pub client: Option<reqwest::Client>,
pub endpoint: String,
pub api_key: String,
pub payload: QueueResponse,
phantom: PhantomData<Response>,
}
impl<Response: DeserializeOwned> Queue<Response> {
pub fn new(
client: reqwest::Client,
endpoint: impl Into<String>,
api_key: String,
payload: QueueResponse,
) -> Self {
Self {
client: Some(client),
endpoint: endpoint.into(),
api_key,
payload,
phantom: PhantomData,
}
}
/// Get the status of the Queue request
pub async fn status(&self, show_logs: bool) -> Result<QueueStatus, FalError> {
let response = self
.client
.as_ref()
.unwrap()
.get(&self.payload.status_url)
.query(&[("logs", if show_logs { "1" } else { "0" })])
.header("Authorization", format!("Key {}", self.api_key))
.header("Content-Type", "application/json")
.send()
.await?;
Ok(response.error_for_status()?.json().await?)
}
/// Get the response of the Queue request, if the request is Completed
pub async fn response(&self) -> Result<Response, FalError> {
let response = self
.client
.as_ref()
.unwrap()
.get(&self.payload.response_url)
.header("Authorization", format!("Key {}", self.api_key))
.header("Content-Type", "application/json")
.send()
.await?;
if response.status() != 200 {
let error = response.text().await?;
return Err(error.into());
}
Ok(response.error_for_status()?.json().await?)
}
/// Cancel the Queue request
pub async fn cancel(&self) -> Result<(), FalError> {
let response = self
.client
.as_ref()
.unwrap()
.put(&self.payload.cancel_url)
.header("Authorization", format!("Key {}", self.api_key))
.send()
.await?;
response.error_for_status()?;
Ok(())
}
}
@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct QueueResponse {
pub request_id: String,
pub response_url: String,
pub status_url: String,
pub cancel_url: String,
}
@@ -0,0 +1,29 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Status {
InQueue,
InProgress,
Completed,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestLog {
pub timestamp: String,
pub level: Option<String>,
pub source: Option<String>,
pub message: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QueueStatus {
/// The status of the Queue request
pub status: Status,
/// The position of the request in the queue
pub queue_position: Option<i64>,
/// The URL of the response
pub response_url: String,
/// The logs of the request, if `show_logs` is `true`
pub logs: Option<Vec<RequestLog>>,
}
@@ -1,5 +1,4 @@
use crate::prelude::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WebhookResponse {
@@ -1,4 +1,4 @@
use fal::request::FalRequest;
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use fal::request::FalRequest;
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -0,0 +1,29 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct RembgRemoveBackgroundInput {
pub image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub crop_to_bbox: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RembgRemoveBackgroundFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RembgRemoveBackgroundOutput {
pub image: RembgRemoveBackgroundFile,
}
pub fn rembg_remove_background(
params: RembgRemoveBackgroundInput,
) -> FalRequest<RembgRemoveBackgroundInput, RembgRemoveBackgroundOutput> {
FalRequest::new("fal-ai/imageutils/rembg", params)
}
@@ -0,0 +1 @@
pub mod http_rembg_remove_background;
@@ -0,0 +1,50 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct FluxProKontextMaxEditInput {
pub prompt: String,
pub image_url: String,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
/// "png" or "jpeg"
/// Default: "jpeg"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
/// Aspect ratio for the generated image.
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// The safety tolerance level for content moderation.
/// "1" (most strict) to "5" (most permissive).
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_tolerance: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxProKontextMaxEditFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxProKontextMaxEditOutput {
pub images: Vec<FluxProKontextMaxEditFile>,
}
pub fn flux_pro_kontext_max_edit(
params: FluxProKontextMaxEditInput,
) -> FalRequest<FluxProKontextMaxEditInput, FluxProKontextMaxEditOutput> {
FalRequest::new("fal-ai/flux-pro/kontext/max", params)
}
@@ -0,0 +1,41 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Gemini25FlashEditInput {
pub prompt: String,
pub image_urls: Vec<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg" or "png"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
/// Aspect ratio for the generated image.
/// Options: auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
/// Default: "auto"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Gemini25FlashEditFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Gemini25FlashEditOutput {
pub images: Vec<Gemini25FlashEditFile>,
}
pub fn gemini_25_flash_edit(
params: Gemini25FlashEditInput,
) -> FalRequest<Gemini25FlashEditInput, Gemini25FlashEditOutput> {
FalRequest::new("fal-ai/gemini-25-flash-image/edit", params)
}
@@ -0,0 +1,32 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GptImage1EditImageInput {
pub prompt: String,
pub image_urls: Vec<String>,
/// "auto", "1024x1024", "1536x1024", "1024x1536"
pub image_size: String,
/// 1 - 4
pub num_images: u8,
/// "auto", "low", "medium", "high"
pub quality: String,
/// BYOK - caller supplies their own OpenAI API key
pub openai_api_key: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage1EditImageOutput {
// Response fields parsed elsewhere
}
pub fn gpt_image_1_edit_image(
params: GptImage1EditImageInput,
) -> FalRequest<GptImage1EditImageInput, GptImage1EditImageOutput> {
FalRequest::new("fal-ai/gpt-image-1/edit-image/byok", params)
}
@@ -1,53 +1,54 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GptImage1p5ImageEditInput {
pub struct GptImage1p5EditImageInput {
pub prompt: String,
pub image_urls: Vec<String>,
/// The URL of the mask image to use for the generation. This indicates what part of the image to edit.
#[serde(skip_serializing_if = "Option::is_none")]
pub mask_image_url: Option<String>,
/// Eg. auto, 1024x1024, 1536x1024, 1024x1536
/// "auto", "1024x1024", "1536x1024", "1024x1536"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// Eg. Possible enum values: auto, transparent, opaque
/// "auto", "transparent", "opaque"
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<String>,
/// Eg. Possible enum values: low, medium, high
/// "low", "medium", "high"
#[serde(skip_serializing_if = "Option::is_none")]
pub quality: Option<String>,
/// Eg. Possible enum values: low, high
/// "low", "high"
#[serde(skip_serializing_if = "Option::is_none")]
pub input_fidelity: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// Possible enum values: jpeg, png, webp
/// "jpeg", "png", "webp"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage1p5ImageEditOutput {
pub images: Vec<ImageFile>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ImageFile {
/// The URL where the file can be downloaded from.
pub struct GptImage1p5EditImageFile {
pub url: String,
}
pub fn gpt_image_1p5_image_edit(
params: GptImage1p5ImageEditInput,
) -> FalRequest<GptImage1p5ImageEditInput, GptImage1p5ImageEditOutput> {
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage1p5EditImageOutput {
pub images: Vec<GptImage1p5EditImageFile>,
}
pub fn gpt_image_1p5_edit_image(
params: GptImage1p5EditImageInput,
) -> FalRequest<GptImage1p5EditImageInput, GptImage1p5EditImageOutput> {
FalRequest::new("fal-ai/gpt-image-1.5/edit", params)
}
@@ -1,4 +1,4 @@
use fal::request::FalRequest;
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -0,0 +1,46 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct NanoBananaProEditImageInput {
pub prompt: String,
pub image_urls: Vec<String>,
/// Eg. "auto", "16:9", "1:1", etc.
/// Options: auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
/// Default: "auto"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// Eg. "1K", "2K", "4K"
/// Default: "1K"
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg", "png", "webp"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NanoBananaProEditImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NanoBananaProEditImageOutput {
pub images: Vec<NanoBananaProEditImageFile>,
}
pub fn nano_banana_pro_edit_image(
params: NanoBananaProEditImageInput,
) -> FalRequest<NanoBananaProEditImageInput, NanoBananaProEditImageOutput> {
FalRequest::new("fal-ai/nano-banana-pro/edit", params)
}
@@ -0,0 +1,62 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct QwenImageEditInput {
pub prompt: String,
pub image_url: String,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg" or "png"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
/// Default: 30
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<u8>,
/// Default: 4.0
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
/// Default: true
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
/// Options: "none", "regular", "high"
/// Default: "none"
#[serde(skip_serializing_if = "Option::is_none")]
pub acceleration: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QwenImageEditFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QwenImageEditOutput {
pub images: Vec<QwenImageEditFile>,
}
pub fn qwen_image_edit(
params: QwenImageEditInput,
) -> FalRequest<QwenImageEditInput, QwenImageEditOutput> {
FalRequest::new("fal-ai/qwen-image-edit", params)
}
@@ -0,0 +1,31 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct SeedEditV3EditInput {
pub prompt: String,
pub image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedEditV3EditFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedEditV3EditOutput {
pub image: Vec<SeedEditV3EditFile>,
}
pub fn seededit_v3_edit(
params: SeedEditV3EditInput,
) -> FalRequest<SeedEditV3EditInput, SeedEditV3EditOutput> {
FalRequest::new("fal-ai/bytedance/seededit/v3/edit-image", params)
}
@@ -0,0 +1,50 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct SeedreamV4EditImageInput {
pub prompt: String,
/// Maximum 10 images. Excess images are ignored.
pub image_urls: Vec<String>,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto, auto_2K, auto_4K
/// Default: "auto_2K"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub max_images: Option<u8>,
/// Enum: "standard", "fast"
/// Default: "standard"
#[serde(skip_serializing_if = "Option::is_none")]
pub enhance_prompt_mode: Option<String>,
/// Default: true
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4EditImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4EditImageOutput {
pub images: Vec<SeedreamV4EditImageFile>,
}
pub fn seedream_4_edit_image(
params: SeedreamV4EditImageInput,
) -> FalRequest<SeedreamV4EditImageInput, SeedreamV4EditImageOutput> {
FalRequest::new("fal-ai/bytedance/seedream/v4/edit", params)
}
@@ -0,0 +1,48 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct SeedreamV4p5EditImageInput {
pub prompt: String,
/// Maximum 10 images. Excess images are ignored.
pub image_urls: Vec<String>,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto_2K, auto_4K
/// Default: "auto_2K"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub max_images: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
/// Default: true
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4p5EditImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4p5EditImageOutput {
pub images: Vec<SeedreamV4p5EditImageFile>,
}
pub fn seedream_4p5_edit_image(
params: SeedreamV4p5EditImageInput,
) -> FalRequest<SeedreamV4p5EditImageInput, SeedreamV4p5EditImageOutput> {
FalRequest::new("fal-ai/bytedance/seedream/v4.5/edit", params)
}
@@ -1,4 +1,4 @@
use fal::request::FalRequest;
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -0,0 +1,11 @@
pub mod http_flux_pro_kontext_max_edit;
pub mod http_gemini_25_flash_edit;
pub mod http_gpt_image_1_edit_image;
pub mod http_gpt_image_1p5_edit_image;
pub mod http_nano_banana_2_edit_image;
pub mod http_nano_banana_pro_edit_image;
pub mod http_qwen_image_edit;
pub mod http_seedream_4_edit_image;
pub mod http_seedream_4p5_edit_image;
pub mod http_seedream_5_edit_image;
pub mod http_seededit_v3_edit;
@@ -0,0 +1,59 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct FluxDevJuggernautInfillInput {
pub prompt: String,
pub image_url: String,
pub mask_url: String,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
/// "png" or "jpeg"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
/// 0.0 - 1.0. 1.0 completely remakes the image, 0.0 preserves the original.
#[serde(skip_serializing_if = "Option::is_none")]
pub strength: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<i64>,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxDevJuggernautInfillFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxDevJuggernautInfillOutput {
pub images: Vec<FluxDevJuggernautInfillFile>,
}
pub fn flux_dev_juggernaut_infill(
params: FluxDevJuggernautInfillInput,
) -> FalRequest<FluxDevJuggernautInfillInput, FluxDevJuggernautInfillOutput> {
FalRequest::new("rundiffusion-fal/juggernaut-flux-lora/inpainting", params)
}
@@ -0,0 +1,46 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct FluxPro1InfillInput {
pub prompt: String,
pub image_url: String,
pub mask_url: String,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
/// "1" (most strict) to "5" (most permissive)
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_tolerance: Option<String>,
/// "png" or "jpeg"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxPro1InfillFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxPro1InfillOutput {
pub images: Vec<FluxPro1InfillFile>,
}
pub fn flux_pro_1_infill(
params: FluxPro1InfillInput,
) -> FalRequest<FluxPro1InfillInput, FluxPro1InfillOutput> {
FalRequest::new("fal-ai/flux-pro/v1/fill", params)
}
@@ -0,0 +1,2 @@
pub mod http_flux_dev_juggernaut_infill;
pub mod http_flux_pro_1_infill;
@@ -1,5 +1,5 @@
pub mod angle;
pub mod http_nano_banana_2_edit_image;
pub mod http_nano_banana_2_text_to_image;
pub mod http_seedream_5_edit_image;
pub mod http_seedream_5_text_to_image;
pub mod background;
pub mod edit;
pub mod infill;
pub mod text;
@@ -0,0 +1,47 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Flux1DevTextToImageInput {
pub prompt: String,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flux1DevTextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flux1DevTextToImageOutput {
pub images: Vec<Flux1DevTextToImageFile>,
}
pub fn flux_1_dev_text_to_image(
params: Flux1DevTextToImageInput,
) -> FalRequest<Flux1DevTextToImageInput, Flux1DevTextToImageOutput> {
FalRequest::new("fal-ai/flux/dev", params)
}
@@ -0,0 +1,44 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Flux1SchnellTextToImageInput {
pub prompt: String,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flux1SchnellTextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flux1SchnellTextToImageOutput {
pub images: Vec<Flux1SchnellTextToImageFile>,
}
pub fn flux_1_schnell_text_to_image(
params: Flux1SchnellTextToImageInput,
) -> FalRequest<Flux1SchnellTextToImageInput, Flux1SchnellTextToImageOutput> {
FalRequest::new("fal-ai/flux/schnell", params)
}
@@ -0,0 +1,50 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct FluxPro11TextToImageInput {
pub prompt: String,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
/// "1" (most strict) to "5" (most permissive)
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_tolerance: Option<String>,
/// "png" or "jpeg"
/// Default: "jpeg"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxPro11TextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxPro11TextToImageOutput {
pub images: Vec<FluxPro11TextToImageFile>,
}
pub fn flux_pro_11_text_to_image(
params: FluxPro11TextToImageInput,
) -> FalRequest<FluxPro11TextToImageInput, FluxPro11TextToImageOutput> {
FalRequest::new("fal-ai/flux-pro/v1.1", params)
}
@@ -0,0 +1,55 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct FluxPro11UltraTextToImageInput {
pub prompt: String,
/// Options: 21:9, 16:9, 4:3, 3:2, 1:1, 2:3, 3:4, 9:16, 9:21
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
/// Generate less processed, more natural-looking images.
/// Default: false
#[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
/// "1" (most strict) to "5" (most permissive)
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_tolerance: Option<String>,
/// "png" or "jpeg"
/// Default: "jpeg"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_mode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxPro11UltraTextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FluxPro11UltraTextToImageOutput {
pub images: Vec<FluxPro11UltraTextToImageFile>,
}
pub fn flux_pro_11_ultra_text_to_image(
params: FluxPro11UltraTextToImageInput,
) -> FalRequest<FluxPro11UltraTextToImageInput, FluxPro11UltraTextToImageOutput> {
FalRequest::new("fal-ai/flux-pro/v1.1-ultra", params)
}
@@ -1,33 +1,34 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Gemini25FlashTextToImageInput {
pub prompt: String,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg" or "png"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
/// The aspect ratio of the generated image.
/// Default value: "1:1"
/// Possible enum values: 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
/// Options: 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
/// Default: "1:1"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct File {
/// The URL where the file can be downloaded from.
#[derive(Debug, Serialize, Deserialize)]
pub struct Gemini25FlashTextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Gemini25FlashTextToImageOutput {
pub images: Vec<File>,
pub images: Vec<Gemini25FlashTextToImageFile>,
}
pub fn gemini_25_flash_text_to_image(
@@ -0,0 +1,30 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GptImage1TextToImageInput {
pub prompt: String,
/// "auto", "1024x1024", "1536x1024", "1024x1536"
pub image_size: String,
/// 1 - 4
pub num_images: u8,
/// "auto", "low", "medium", "high"
pub quality: String,
/// BYOK - caller supplies their own OpenAI API key
pub openai_api_key: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage1TextToImageOutput {
// Response fields parsed elsewhere
}
pub fn gpt_image_1_text_to_image(
params: GptImage1TextToImageInput,
) -> FalRequest<GptImage1TextToImageInput, GptImage1TextToImageOutput> {
FalRequest::new("fal-ai/gpt-image-1/text-to-image/byok", params)
}
@@ -1,39 +1,41 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GptImage1p5TextToImageInput {
pub prompt: String,
/// Eg. auto, 1024x1024, 1536x1024, 1024x1536
/// "auto", "1024x1024", "1536x1024", "1024x1536"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// Eg. Possible enum values: auto, transparent, opaque
/// "auto", "transparent", "opaque"
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<String>,
/// Eg. Possible enum values: low, medium, high
/// "low", "medium", "high"
#[serde(skip_serializing_if = "Option::is_none")]
pub quality: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// Possible enum values: jpeg, png, webp
/// "jpeg", "png", "webp"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ImageFile {
/// The URL where the file can be downloaded from.
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage1p5TextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage1p5TextToImageOutput {
pub images: Vec<ImageFile>,
pub images: Vec<GptImage1p5TextToImageFile>,
}
pub fn gpt_image_1p5_text_to_image(
@@ -1,4 +1,4 @@
use fal::request::FalRequest;
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,35 +1,39 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct NanoBananaProTextToImageInput {
pub prompt: String,
/// Eg. "16:9", "1:1", and a ton of other options. So many options.
/// Options: auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
/// Default: "auto"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// Eg. "1K", "2K", "4K"
/// "1K", "2K", "4K"
/// Default: "1K"
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg", "png", "webp"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ImageFile {
/// The URL where the file can be downloaded from.
#[derive(Debug, Serialize, Deserialize)]
pub struct NanoBananaProTextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NanoBananaProTextToImageOutput {
pub images: Vec<ImageFile>,
pub images: Vec<NanoBananaProTextToImageFile>,
}
pub fn nano_banana_pro_text_to_image(
@@ -0,0 +1,47 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct SeedreamV4TextToImageInput {
pub prompt: String,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto, auto_2K, auto_4K
/// Default: "auto_2K"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub max_images: Option<u8>,
/// Enum: "standard", "fast"
/// Default: "standard"
#[serde(skip_serializing_if = "Option::is_none")]
pub enhance_prompt_mode: Option<String>,
/// Default: true
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4TextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4TextToImageOutput {
pub images: Vec<SeedreamV4TextToImageFile>,
}
pub fn seedream_4_text_to_image(
params: SeedreamV4TextToImageInput,
) -> FalRequest<SeedreamV4TextToImageInput, SeedreamV4TextToImageOutput> {
FalRequest::new("fal-ai/bytedance/seedream/v4/text-to-image", params)
}
@@ -0,0 +1,45 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct SeedreamV4p5TextToImageInput {
pub prompt: String,
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto_2K, auto_4K
/// Default: "auto_2K"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub max_images: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
/// Default: true
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4p5TextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SeedreamV4p5TextToImageOutput {
pub images: Vec<SeedreamV4p5TextToImageFile>,
}
pub fn seedream_4p5_text_to_image(
params: SeedreamV4p5TextToImageInput,
) -> FalRequest<SeedreamV4p5TextToImageInput, SeedreamV4p5TextToImageOutput> {
FalRequest::new("fal-ai/bytedance/seedream/v4.5/text-to-image", params)
}
@@ -1,4 +1,4 @@
use fal::request::FalRequest;
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -0,0 +1,12 @@
pub mod http_flux_1_dev_text_to_image;
pub mod http_flux_1_schnell_text_to_image;
pub mod http_flux_pro_11_text_to_image;
pub mod http_flux_pro_11_ultra_text_to_image;
pub mod http_gemini_25_flash_text_to_image;
pub mod http_gpt_image_1_text_to_image;
pub mod http_gpt_image_1p5_text_to_image;
pub mod http_nano_banana_2_text_to_image;
pub mod http_nano_banana_pro_text_to_image;
pub mod http_seedream_4_text_to_image;
pub mod http_seedream_4p5_text_to_image;
pub mod http_seedream_5_text_to_image;
@@ -1,2 +1,3 @@
pub mod image;
pub mod object;
pub mod video;
@@ -0,0 +1,31 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Hunyuan3dV21ImageTo3dInput {
pub input_image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub textured_mesh: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub octree_resolution: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Hunyuan3dV21ImageTo3dOutput {}
pub fn hunyuan3d_v21_image_to_3d(
params: Hunyuan3dV21ImageTo3dInput,
) -> FalRequest<Hunyuan3dV21ImageTo3dInput, Hunyuan3dV21ImageTo3dOutput> {
FalRequest::new("fal-ai/hunyuan3d-v21", params)
}
@@ -0,0 +1,31 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Hunyuan3dV2ImageTo3dInput {
pub input_image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub textured_mesh: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub octree_resolution: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Hunyuan3dV2ImageTo3dOutput {}
pub fn hunyuan3d_v2_image_to_3d(
params: Hunyuan3dV2ImageTo3dInput,
) -> FalRequest<Hunyuan3dV2ImageTo3dInput, Hunyuan3dV2ImageTo3dOutput> {
FalRequest::new("fal-ai/hunyuan3d/v2", params)
}

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