Compare commits

..
Author SHA1 Message Date
Tanish Manku e7fc884d73 pagedraw: composite draw layer into reference image for default generation case
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:57:40 -07:00
1889 changed files with 119437 additions and 12901 deletions
-14
View File
@@ -10,20 +10,6 @@ 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
+1 -2
View File
@@ -36,9 +36,8 @@ script/socat_dev_database_connect.sh
# Python virtual envs (named by convention)
venv/
# LLM Testing
# Testing
/prompts/
/reports/
/requests/
# Scratch data
@@ -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_other_args = ?,\n\n creator_ip_address = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "08f52f15acf6afc7091a525e3dc88f706c71c3579e5e81aa9903807d85f469ef"
}
@@ -1,168 +0,0 @@
{
"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,289 +0,0 @@
{
"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"
}
@@ -1,12 +0,0 @@
{
"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"
}
@@ -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::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 ",
"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 ",
"describe": {
"columns": [
{
@@ -25,7 +25,7 @@
},
{
"ordinal": 2,
"name": "maybe_model_type: enums::common::generation::common_model_type::CommonModelType",
"name": "maybe_model_type: enums::common::model_type::ModelType",
"type_info": {
"type": "VarString",
"flags": "BINARY",
@@ -75,66 +75,6 @@
},
{
"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",
@@ -144,7 +84,7 @@
}
},
{
"ordinal": 14,
"ordinal": 8,
"name": "creator_ip_address",
"type_info": {
"type": "VarString",
@@ -154,7 +94,7 @@
}
},
{
"ordinal": 15,
"ordinal": 9,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -176,15 +116,9 @@
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "4a50b7bf06061f05e769a41e547cb927bd2b3a6f67c8ceb37883b744237ba34c"
"hash": "b46b0d022790f4e10f6e47126df0fdc55172bf8cda77d4e0b2afac9e6ebd1630"
}
@@ -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_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"
}
@@ -1,91 +0,0 @@
{
"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
@@ -1,62 +0,0 @@
# 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
+869 -529
View File
File diff suppressed because it is too large Load Diff
+67 -92
View File
@@ -6,28 +6,27 @@ 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",
"crates/api_clients/artcraft/artcraft_api_defs", # TODO: api_clients/{artcraft}/{api_defs,client}
"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_downloader",
"crates/api_clients/google_drive_common",
"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/rootly_client",
"crates/api_clients/muapi_client",
"crates/api_clients/seedance2pro",
"crates/api_clients/runwayml_client",
"crates/api_clients/seedance2pro_client",
"crates/api_clients/thumbnail_generator",
"crates/api_clients/worldlabs_api_client",
"crates/api_clients/worldlabs_consumer_client",
"crates/api_clients/world_labs_api",
"crates/api_clients/world_labs_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",
@@ -45,6 +44,7 @@ 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,14 +53,19 @@ 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/ffmpeg_utils",
"crates/lib/files/archives",
"crates/lib/files/file_to_base64_url",
"crates/lib/files/filesys",
"crates/lib/files/hashing",
@@ -68,41 +73,35 @@ 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/uuid_utils",
"crates/lib/web_scrapers",
#"crates/lib/api_clients/fakeyou_client",
#"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",
#"crates/lib/machine_learning/ml_models",
# 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",
@@ -117,34 +116,31 @@ 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",
@@ -216,57 +212,50 @@ 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" }
muapi_client = { path = "crates/api_clients/muapi_client" }
mysql_queries = { path = "crates/schema/database/mysql_queries" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
muapi_client = { path = "crates/api_clients/muapi_client" }
seedance2pro = { path = "crates/api_clients/seedance2pro" }
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" }
subprocess_common = { path = "crates/lib/deprecated/subprocess_common" }
test_data = { path = "crates/testing/test_data" }
test_utils = { path = "crates/testing/test_utils" }
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" }
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" }
worldlabs_api_client = { path = "crates/api_clients/worldlabs_api_client" }
worldlabs_consumer_client = { path = "crates/api_clients/worldlabs_consumer_client" }
world_labs_api = { path = "crates/api_clients/world_labs_api" }
world_labs_client = { path = "crates/api_clients/world_labs_client" }
# External libs
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
@@ -282,42 +271,32 @@ 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"
byteorder = "1.5.0"
bytes = "1.11.1"
chrono = { version = "0.4.44", features = ["serde"] }
clap = { version = "=4.5.60", features = ["derive"] }
bytes = "1.10.1"
chrono = { version = "0.4.41", 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.32" }
hex-literal = "1.1.0"
hostname = "0.4.0"
indexmap = "2.13.0"
futures = { version = "0.3.31" }
hex-literal = "1.0.0"
indexmap = "2.9.0"
log = "0.4.29"
md-5 = "0.10.6" # insecure but used for gravatar
mockall = "0.12.1"
once_cell = "1.21.4"
once_cell = "1.21.3"
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" }
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 = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.219"
serde_json = "1.0.140"
serde_yaml = "0.9.34+deprecated"
sha2 = "0.11.0-rc.5"
sha2 = "0.11.0-rc.2"
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"
@@ -325,16 +304,12 @@ tauri-plugin-fs = "2.4.2"
tauri-plugin-log = "2.6.0"
tauri-plugin-upload = "2.3.1"
tempdir = "0.3.7"
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"
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"
# NB: wreq gets past fingerprinting, but we have to deal with platform-specific build issues.
#
@@ -1,11 +0,0 @@
-- 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;
@@ -1,11 +0,0 @@
-- 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;
@@ -1,7 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
DROP COLUMN maybe_duration_seconds,
ADD COLUMN maybe_duration VARCHAR(8) DEFAULT NULL;
@@ -1,7 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
DROP COLUMN maybe_duration,
ADD COLUMN maybe_duration_seconds INT UNSIGNED DEFAULT NULL;
@@ -27,8 +27,6 @@ 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.
@@ -54,29 +52,6 @@ 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,7 +66,6 @@ CREATE TABLE users (
-- - "explore_media"
-- - "studio"
-- - "video_style_transfer"
-- - "sd_wl" (seedance whitelist)
maybe_feature_flags VARCHAR(255) DEFAULT NULL,
-- DEPRECATED
+37 -11
View File
@@ -130,20 +130,47 @@ RUN echo "Disk usage at current directory (after tests):"
RUN pwd
RUN du -hsc * | sort -hr
# 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.
# 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
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
@@ -183,7 +210,6 @@ 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/deprecated/download_job/config/download-job.common.env .
COPY crates/service/job/deprecated/download_job/config/download-job.production.env .
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/inference_job/config/inference-job.common.env .
COPY crates/service/job/deprecated/inference_job/config/inference-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 .
# # 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.workspace = true
utoipa = { version = "4", features = ["chrono", "actix_extras"] }
[dev-dependencies]
anyhow.workspace = true
@@ -1,12 +1,11 @@
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, ToSchema, Debug)]
#[derive(Serialize, Deserialize, Debug)]
pub struct CoverImageLinks {
/// Primary link to the cover image via the CDN.
pub cdn_url: Url,
@@ -1,6 +1,5 @@
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)
@@ -9,7 +8,7 @@ use utoipa::ToSchema;
/// 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, ToSchema, Debug)]
#[derive(Serialize, Deserialize, 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.")]
@@ -37,7 +36,7 @@ pub struct MediaFileCoverImageDetails {
/// The default cover is composed of an image and color pair that are
/// predefined by the frontend.
#[derive(Serialize, Deserialize, ToSchema, Debug)]
#[derive(Serialize, Deserialize, Debug)]
pub struct MediaFileDefaultCover {
pub image_index: u8,
pub color_index: u8,
@@ -1,9 +1,8 @@
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, ToSchema, Debug)]
#[derive(Serialize, Deserialize, Debug)]
pub struct UserDetailsLight {
/// The token for the user
pub user_token: UserToken,
@@ -27,7 +26,7 @@ pub struct UserDetailsLight {
// In the future, we'll also support user-uploaded avatars that we store on our servers.
}
#[derive(Serialize, Deserialize, ToSchema, Debug)]
#[derive(Serialize, Deserialize, 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_resolution::CommonResolution;
use enums::common::generation::common_video_resolution::CommonVideoResolution;
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<CommonResolution>,
pub resolution: Option<CommonVideoResolution>,
/// 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_resolution::CommonResolution;
use enums::common::generation::common_video_resolution::CommonVideoResolution;
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<CommonResolution>,
pub resolution: Option<CommonVideoResolution>,
/// Duration in seconds.
pub duration_seconds: Option<u16>,
@@ -1,6 +1,7 @@
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;
@@ -1,55 +0,0 @@
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>>,
}
@@ -1,40 +0,0 @@
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,4 +1,2 @@
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,11 +1,8 @@
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";
@@ -17,32 +14,14 @@ pub struct CreatePromptRequest {
/// OPTIONAL. The positive prompt.
pub positive_prompt: Option<String>,
/// OPTIONAL. The negative prompt.
/// OPTIONAL. The positive prompt.
pub negative_prompt: Option<String>,
/// OPTIONAL. The model type.
pub model_type: Option<CommonModelType>,
pub model_type: Option<ModelType>,
/// 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)]
@@ -1,149 +0,0 @@
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,2 +1 @@
pub mod create_prompt;
pub mod get_prompt;
pub mod create_prompt;
@@ -28,7 +28,7 @@ cloudflare_errors.workspace = true
enums.workspace = true
enums_public.workspace = true
errors.workspace = true
uuid_utils.workspace = true
idempotency.workspace = true
mimetypes.workspace = true
tokens.workspace = true
@@ -5,6 +5,7 @@ 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,9 +7,16 @@ 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 uuid_utils::uuid::generate_random_uuid;
use idempotency::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -45,6 +52,7 @@ 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,8 +7,15 @@ 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 uuid_utils::uuid::generate_random_uuid;
use idempotency::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -7,9 +7,16 @@ 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 uuid_utils::uuid::generate_random_uuid;
use idempotency::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -45,6 +52,7 @@ 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,8 +5,15 @@ 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 uuid_utils::uuid::generate_random_uuid;
use idempotency::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -5,8 +5,15 @@ 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 uuid_utils::uuid::generate_random_uuid;
use idempotency::uuid::generate_random_uuid;
use log::debug;
use reqwest::multipart::{Form, Part};
use reqwest::Client;
@@ -42,6 +49,8 @@ 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_client.workspace = true
seedance2pro.workspace = true
tokens.workspace = true
url_utils.workspace = true
worldlabs_api_client.workspace = true
world_labs_api.workspace = true
# External
anyhow.workspace = true
@@ -1,4 +1,4 @@
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro::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_client::error::seedance2pro_error::Seedance2ProError;
use seedance2pro::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 worldlabs_api_client::api::api_types::world_labs_model::WorldLabsModel;
use worldlabs_api_client::pricing::check_pricing::{calculate_cost, InputType};
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
use world_labs_api::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 worldlabs_api_client::api::api_types::world_labs_model::WorldLabsModel;
use worldlabs_api_client::pricing::check_pricing::{calculate_cost, InputType};
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
use world_labs_api::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_client::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro_client::requests::generate_video::generate_video::{BatchCount, GenerateVideoArgs, ModelType, Resolution};
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro::requests::generate_video::generate_video::{BatchCount, GenerateVideoArgs, 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,7 +22,6 @@ 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,
@@ -35,7 +34,6 @@ 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();
@@ -97,9 +95,9 @@ mod tests {
assert_eq!(estimate_usd_cents(5, 2), 160);
assert_eq!(estimate_usd_cents(15, 2), 480);
// 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);
// 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);
}
}
@@ -1,5 +1,5 @@
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro_client::requests::generate_video::generate_video::{GenerateVideoArgs, ModelType, Resolution};
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro::requests::generate_video::generate_video::{GenerateVideoArgs, 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,7 +12,6 @@ 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,
@@ -23,7 +22,6 @@ 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_client::requests::generate_video::generate_video::{
generate_video, GenerateVideoArgs, ModelType,
use seedance2pro::requests::generate_video::generate_video::{
generate_video, GenerateVideoArgs,
};
use seedance2pro_client::requests::prepare_file_upload::prepare_file_upload::{
use seedance2pro::requests::prepare_file_upload::prepare_file_upload::{
prepare_file_upload, PrepareFileUploadArgs,
};
use seedance2pro_client::requests::upload_file::upload_file::{
use seedance2pro::requests::upload_file::upload_file::{
upload_file, UploadFileArgs,
};
use url_utils::extension::extract_extension_from_url::{
@@ -34,7 +34,6 @@ 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,
@@ -45,7 +44,6 @@ 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)
@@ -60,7 +58,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_client::creds::seedance2pro_session::Seedance2ProSession,
session: &seedance2pro::creds::seedance2pro_session::Seedance2ProSession,
source_url: &str,
) -> Result<String, ArtcraftRouterError> {
let extension = extract_extension_from_url_str(source_url, &ExtractExtensions::All)
@@ -72,7 +70,6 @@ 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)))?;
@@ -80,7 +77,6 @@ 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)))?;
@@ -89,7 +85,7 @@ async fn upload_to_seedance2pro(
}
async fn upload_optional_url(
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
session: &seedance2pro::creds::seedance2pro_session::Seedance2ProSession,
url: Option<&str>,
) -> Result<Option<String>, ArtcraftRouterError> {
match url {
@@ -99,7 +95,7 @@ async fn upload_optional_url(
}
async fn upload_optional_url_list(
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
session: &seedance2pro::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_client::requests::generate_video::generate_video::{BatchCount, Resolution};
use seedance2pro::requests::generate_video::generate_video::{BatchCount, Resolution};
#[derive(Debug, Clone)]
pub struct PlanSeedance2proSeedance2p0 {
+9 -8
View File
@@ -16,13 +16,13 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# cargo-hakari workspace hack (faster compile times)
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
bucket_paths.workspace = true
bucket_paths = { path = "../../lib/bucket_paths" }
crockford.workspace = true
easyenv.workspace = true
easyenv = { path = "../../lib/easyenv" }
errors.workspace = true
filesys.workspace = true
hashing = { path = "../../lib/files/hashing" }
@@ -31,11 +31,12 @@ storyteller_root = { path = "../../lib/storyteller_root" }
# External
anyhow.workspace = true
async-trait = "0.1.75"
env_logger.workspace = true
log.workspace = true
tokio.workspace = true
env_logger = "0.11.3"
async-trait = "0.1.75"
# The "never-encode-slash" feature seems to have been removed:
# https://github.com/durch/rust-s3/pull/254
rust-s3.workspace = true
rust-s3 = { version = "0.36.0-beta.2", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
# Runtime
tokio.workspace = true
+8 -9
View File
@@ -12,7 +12,7 @@ name = "fal_client"
path = "src/lib.rs"
[dependencies]
# cargo-hakari workspace hack (faster compile times)
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
@@ -25,16 +25,15 @@ 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
[dev-dependencies]
# Internal Dev
test_utils.workspace = true
test_data.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"] }
# External Dev
# (None yet)
[dev-dependencies]
testing = { path = "../../lib/testing" }
test_data.workspace = true
@@ -1,14 +1,16 @@
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 `FalError` into a more user-friendly `FalErrorPlus`.
pub fn classify_fal_error(err: FalError) -> FalErrorPlus {
/// 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.\"}")
match err {
FalError::RequestError(_) => FalErrorPlus::FalError(err),
FalError::SerializeError(_) => FalErrorPlus::FalError(err),
FalError::Other(ref inner) => {
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) => {
if inner == BILLING_ERROR {
FalErrorPlus::FalBillingError(inner.to_string())
} else if inner.contains("billing") {
@@ -1,21 +0,0 @@
/// 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,13 +1,12 @@
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 `crate::error::fal_error::FalError`.
/// Additional errors that aren't included in `fal::FalError`.
#[derive(Debug)]
pub enum FalErrorPlus {
/// An error arising in the `fal` crate.
FalError(crate::error::fal_error::FalError),
FalError(fal::FalError),
/// The fal API key is invalid.
FalApiKeyError(String),
/// The fal account has a billing issue
@@ -38,8 +37,8 @@ impl Display for FalErrorPlus {
impl Error for FalErrorPlus {}
impl From<FalError> for FalErrorPlus {
fn from(err: FalError) -> Self {
impl From<fal::FalError> for FalErrorPlus {
fn from(err: fal::FalError) -> Self {
classify_fal_error(err)
}
}
@@ -1,3 +1,2 @@
pub mod classify_fal_error;
pub mod fal_error;
pub mod fal_error_plus;
+8 -1
View File
@@ -1,4 +1,11 @@
//! A fal wrapper
//! 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::*;
}
pub mod creds;
pub mod error;
@@ -1,9 +1,8 @@
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,5 +1,6 @@
use crate::requests::api::queue::Queue;
use fal::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";
@@ -1,135 +0,0 @@
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)
}
}
@@ -1,5 +0,0 @@
pub mod fal_request;
pub mod queue;
pub mod queue_response;
pub mod queue_status;
pub mod webhook_response;
@@ -1,86 +0,0 @@
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(())
}
}
@@ -1,9 +0,0 @@
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,
}
@@ -1,29 +0,0 @@
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,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,29 +0,0 @@
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)
}
@@ -1 +0,0 @@
pub mod http_rembg_remove_background;
@@ -1,50 +0,0 @@
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)
}
@@ -1,41 +0,0 @@
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)
}
@@ -1,32 +0,0 @@
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,46 +0,0 @@
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)
}
@@ -1,62 +0,0 @@
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)
}
@@ -1,31 +0,0 @@
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)
}
@@ -1,50 +0,0 @@
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)
}
@@ -1,48 +0,0 @@
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,11 +0,0 @@
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;
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,59 +0,0 @@
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)
}
@@ -1,46 +0,0 @@
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)
}
@@ -1,2 +0,0 @@
pub mod http_flux_dev_juggernaut_infill;
pub mod http_flux_pro_1_infill;
@@ -1,5 +1,5 @@
pub mod angle;
pub mod background;
pub mod edit;
pub mod infill;
pub mod text;
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;
@@ -1,47 +0,0 @@
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)
}
@@ -1,44 +0,0 @@
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)
}
@@ -1,50 +0,0 @@
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)
}
@@ -1,55 +0,0 @@
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,30 +0,0 @@
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,47 +0,0 @@
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)
}
@@ -1,45 +0,0 @@
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,12 +0,0 @@
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,3 +1,2 @@
pub mod image;
pub mod object;
pub mod video;
@@ -1,31 +0,0 @@
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)
}
@@ -1,31 +0,0 @@
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)
}
@@ -1,39 +0,0 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Hunyuan3dV3ImageTo3dInput {
pub input_image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub back_image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub left_image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub right_image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub face_count: Option<u32>,
/// Options: "Normal", "LowPoly", "Geometry"
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_type: Option<String>,
/// Options: "triangle", "quadrilateral"
#[serde(skip_serializing_if = "Option::is_none")]
pub polygon_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_pbr: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Hunyuan3dV3ImageTo3dOutput {}
pub fn hunyuan3d_v3_image_to_3d(
params: Hunyuan3dV3ImageTo3dInput,
) -> FalRequest<Hunyuan3dV3ImageTo3dInput, Hunyuan3dV3ImageTo3dOutput> {
FalRequest::new("fal-ai/hunyuan3d-v3/image-to-3d", params)
}
@@ -1,5 +0,0 @@
pub mod http_hunyuan3d_v2_image_to_3d;
pub mod http_hunyuan3d_v21_image_to_3d;
pub mod http_hunyuan3d_v3_image_to_3d;
pub mod http_hunyuan3d_v3_sketch_to_3d;
pub mod http_hunyuan3d_v3_text_to_3d;
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
@@ -1,4 +1,4 @@
use crate::requests::api::fal_request::FalRequest;
use fal::request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]

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