Compare commits

...
Author SHA1 Message Date
bflatastic 37ef8fd96a reduced image sizes even more 2025-05-27 15:06:10 +07:00
bflatastic b1446916bb changed icons to image for model picker 2025-05-27 14:55:57 +07:00
bflatastic c1c1d694ab more performant background gallery for image 2025-05-27 14:55:01 +07:00
Brandon Thomas 709af560a5 enqueue gpt-4o images too 2025-05-26 23:46:05 -04:00
Brandon Thomas 0f1cf93252 add fal image generation end-to-end (recraft, etc.) (#637) 2025-05-26 21:33:21 -04:00
bflatastic 4a10581e9a fix drag and drop logic for 3D and 2D tabs in PageEditor component 2025-05-26 23:31:47 +07:00
bflatastic 447a70b890 differentiate tabs to drop amd set up place for drag and drop on 2d 2025-05-26 23:26:13 +07:00
Brandon Thomas f4e998d945 cleanup deps (#635) 2025-05-26 01:38:27 -04:00
Brandon Thomas a954859ac2 cleanup dead dependencies 2025-05-25 23:55:15 -04:00
Brandon Thomas 2d8e835401 standardize actix versions in root Cargo.toml 2025-05-25 23:47:12 -04:00
Brandon Thomas c23380af90 codegen db 2025-05-25 23:22:10 -04:00
Brandon Thomas c34f75d071 cleanup hakari packages 2025-05-25 23:15:32 -04:00
Brandon Thomas 8a886e9033 cleanup scripts (2) 2025-05-25 22:57:01 -04:00
Brandon Thomas 3101df8211 cleanup scripts 2025-05-25 22:53:40 -04:00
Brandon Thomas a4c264ebbf cleanup packages, fixup storyteller-web 2025-05-25 22:50:45 -04:00
Brandon Thomas 3d807fdeb2 refactor database crates 2025-05-25 22:33:18 -04:00
Brandon Thomas b3a62cce64 refactor packages 2025-05-25 22:09:59 -04:00
Brandon Thomas 42ccc25e4d fix video enqueue 2025-05-25 21:31:56 -04:00
Brandon Thomas 6595518b35 add detailed build info in an about settings pane (#634) 2025-05-25 14:45:50 -04:00
ArEnSc 618cbc8f0e Feature/clean (#633)
Clean up the TS work unify and the app
2025-05-24 23:31:25 -04:00
BFlat b31a152c1a text to image tab (commented out) (#632) 2025-05-24 10:57:28 +07:00
BFlat 0e2169b902 updated topbar menu icons with tooltip (#631)
- animated on hover
- to accommodate more pages
2025-05-23 23:20:09 +07:00
1027 changed files with 7026 additions and 9516 deletions
@@ -48,7 +48,7 @@
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
Generated
+237 -2821
View File
File diff suppressed because it is too large Load Diff
+44 -29
View File
@@ -4,13 +4,6 @@ resolver = "2"
# This block defines the workspace members.
# When we add a library or binary crate, it needs an entry here.
members = [
# Types: common strongly-typed tokens, ids, payloads, etc.
"crates/_types/composite_identifiers",
"crates/_types/enums",
"crates/_types/enums_public",
"crates/_types/reusable_types", # TODO(bt, 2022-12-21): Deprecate
"crates/_types/tokens",
# CLI tools for operations, etc.
"crates/cli/devenv/dev-database-seed",
"crates/cli/devenv/dev-upload-media-file",
@@ -39,6 +32,7 @@ members = [
"crates/lib/api_clients/thumbnail_generator",
"crates/lib/bootstrap",
"crates/lib/bucket_paths",
"crates/lib/build_metadata",
"crates/lib/caching/memory_caching",
"crates/lib/caching/memory_store",
"crates/lib/caching/redis_caching",
@@ -52,7 +46,6 @@ members = [
"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/elasticsearch_schema",
"crates/lib/email_sender",
"crates/lib/errors",
"crates/lib/files/archives",
@@ -68,15 +61,11 @@ members = [
"crates/lib/jobs_common",
"crates/lib/jwt_light",
"crates/lib/logging",
"crates/lib/machine_learning/ml_models",
"crates/lib/machine_learning/ml_weights_registry",
"crates/lib/machine_learning/prompt_engineering",
"crates/lib/markdown",
"crates/lib/migration",
"crates/lib/mysql_queries",
"crates/lib/password",
"crates/lib/redis_common",
"crates/lib/redis_schema",
"crates/lib/server_environment",
"crates/lib/shared_service_components",
"crates/lib/sqlx_mysql_helpers",
@@ -88,7 +77,21 @@ members = [
"crates/lib/user_input_common",
"crates/lib/web_scrapers",
#"crates/lib/api_clients/fakeyou_client",
#"crates/lib/sqlite_queries",
#"crates/lib/machine_learning/ml_models",
# Schema (database): queries, database layouts, Redis keys, etc.
"crates/schema/database/elasticsearch_schema",
"crates/schema/database/mysql_queries",
"crates/schema/database/redis_common",
"crates/schema/database/redis_schema",
#"crates/schema/database/sqlite_queries",
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
"crates/schema/public/composite_identifiers",
"crates/schema/public/enums",
"crates/schema/public/enums_public",
"crates/schema/public/reusable_types", # TODO(bt, 2022-12-21): Deprecate
"crates/schema/public/tokens",
# Services: API servers, jobs, workers, etc.
"crates/service/job/analytics_job",
@@ -109,19 +112,20 @@ members = [
"crates/service/plugins/user_traits_component",
# Vendored code
"crates/vendor/candle/candle-core",
"crates/vendor/candle/candle-datasets",
"crates/vendor/candle/candle-examples",
"crates/vendor/candle/candle-flash-attn",
"crates/vendor/candle/candle-kernels",
"crates/vendor/candle/candle-metal-kernels",
"crates/vendor/candle/candle-nn",
"crates/vendor/candle/candle-onnx",
"crates/vendor/candle/candle-pyo3",
"crates/vendor/candle/candle-transformers",
"crates/vendor/candle/tensor-tools",
"crates/vendor/limitation",
"crates/vendor/tauri-plugin-http",
# NB(bt,2025-05-25): Too many failures on Mac. Disable for now.
#"crates/vendor/candle/candle-core",
#"crates/vendor/candle/candle-datasets",
#"crates/vendor/candle/candle-examples",
#"crates/vendor/candle/candle-flash-attn",
#"crates/vendor/candle/candle-kernels",
#"crates/vendor/candle/candle-metal-kernels",
#"crates/vendor/candle/candle-nn",
#"crates/vendor/candle/candle-onnx",
#"crates/vendor/candle/candle-pyo3",
#"crates/vendor/candle/candle-transformers",
#"crates/vendor/candle/tensor-tools",
# NB(bt,2023-12-12): Newrelic keeps breaking with upgrades. Kill for now.
#"crates/vendor/newrelic-telemetry-sdk-rust",
@@ -161,6 +165,16 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
# External libs
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
# If a crate needs to use an older/newer version, the dependency should be managed in that crate's own Cargo.toml
actix = "0.13.5"
actix-cors = "0.6.4" # TODO(bt): Carefully upgrade this. CORS broke last time.
actix-http = "3.7.0"
actix-multipart = "0.6.2"
actix-rt = "2.10.0"
actix-service = "2.0.3"
actix-utils = "3.0.1"
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.98"
base64 = "0.22.1"
chrono = { version = "0.4.41", features = ["serde"] }
@@ -176,18 +190,19 @@ reqwest = { version = "0.12.15", features = ["json", "cookies"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.219"
serde_json = "1.0.140"
serde_yaml = "0.9.1"
serde_yaml = "0.9.34+deprecated"
sha3 = "0.10.8"
tauri = { version = "2.4.1", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution
thiserror = "2.0.12"
tokio = { version = "1.45.0", features = ["macros"] }
tokio = { version = "1.45.1", features = ["macros"] }
toml = "0.8.22"
url = { version = "2.5.4", features = ["serde"] }
# Internal libs
build_metadata = { path = "crates/lib/build_metadata" }
crockford = { path = "crates/lib/data/crockford" }
enums = { path = "crates/_types/enums" }
enums_public = { path = "crates/_types/enums_public" }
enums = { path = "crates/schema/public/enums" }
enums_public = { path = "crates/schema/public/enums_public" }
errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/lib/api_clients/fal_client" }
filesys = { path = "crates/lib/files/filesys" }
@@ -202,4 +217,4 @@ primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
storyteller_client = { path = "crates/lib/api_clients/storyteller_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
tokens = { path = "crates/_types/tokens" }
tokens = { path = "crates/schema/public/tokens" }
@@ -16,23 +16,23 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
hashing = { path = "../../../lib/files/hashing" }
idempotency = { path = "../../../lib/idempotency" }
mimetypes = { path = "../../../lib/files/mimetypes" }
mysql_queries = { path = "../../../lib/mysql_queries" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
storyteller_root = { path = "../../../lib/storyteller_root" }
tokens = { path = "../../../_types/tokens" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
tokens = { path = "../../../schema/public/tokens" }
# External
log = "0.4.14"
@@ -16,20 +16,20 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
mimetypes = { path = "../../../lib/files/mimetypes" }
mysql_queries = { path = "../../../lib/mysql_queries" }
tokens = { path = "../../../_types/tokens" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
tokens = { path = "../../../schema/public/tokens" }
# External
clap = "4.4.8"
+2 -3
View File
@@ -16,14 +16,13 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
errors = { path = "../../../lib/errors" }
web_scrapers = { path = "../../../lib/web_scrapers" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
# External
clap = "4.4.8"
tokio = { version = "1.38.0", features = ["macros"] }
+4 -4
View File
@@ -17,15 +17,15 @@ normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
workspace-build-acceleration = { workspace = true }
# Internal
datetimes = { path = "../../../lib/data/datetimes" }
easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
mysql_queries = { path = "../../../lib/mysql_queries" }
tokens = { path = "../../../_types/tokens" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
tokens = { path = "../../../schema/public/tokens" }
# External
anyhow = "1.0.86"
+3 -7
View File
@@ -16,20 +16,16 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
easyenv = { path = "../../../lib/easyenv" }
errors = { path = "../../../lib/errors" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
# External
anyhow = "1.0.86"
# TODO/NB: Commenting out clap4 as it prevents windows builds
# NB: clap4 = { package = "clap", version = "4.0" }
dotenv = "0.15.0"
log = "0.4.14"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
+6 -6
View File
@@ -16,17 +16,17 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
mysql_queries = { path = "../../../lib/mysql_queries" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
storyteller_root = { path = "../../../lib/storyteller_root" }
tokens = { path = "../../../_types/tokens" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
tokens = { path = "../../../schema/public/tokens" }
# External
clap = "4.4.8"
@@ -16,20 +16,20 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
elasticsearch_schema = { path = "../../../lib/elasticsearch_schema" }
enums = { path = "../../../_types/enums" }
elasticsearch_schema = { path = "../../../schema/database/elasticsearch_schema" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
mysql_queries = { path = "../../../lib/mysql_queries" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
primitives = { path = "../../../lib/data/primitives" }
storyteller_root = { path = "../../../lib/storyteller_root" }
tokens = { path = "../../../_types/tokens" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
tokens = { path = "../../../schema/public/tokens" }
# External
clap = "4.4.8"
@@ -16,14 +16,14 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile yytimes)
workspace-build-acceleration = { workspace = true }
# Internal
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
errors = { path = "../../../lib/errors" }
# Workspace hack (faster compile yytimes)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
# External
log = "0.4.20"
once_cell = "1.18.0"
@@ -16,13 +16,13 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
errors = { path = "../../../lib/errors" }
logging = { path = "../../../lib/logging" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
# External
clap = { version = "4.4.8", features = ["derive"] }
walkdir = "2.4.0"
@@ -16,21 +16,21 @@ path = "src/main.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
hashing = { path = "../../../lib/files/hashing" }
mysql_queries = { path = "../../../lib/mysql_queries" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
storyteller_root = { path = "../../../lib/storyteller_root" }
tokens = { path = "../../../_types/tokens" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
tokens = { path = "../../../schema/public/tokens" }
# External
clap = "4.4.8"
+1 -7
View File
@@ -23,17 +23,15 @@ tauri-build = { version = "2.1.1", features = [] }
workspace-build-acceleration = { workspace = true }
# Internal
crockford = { workspace = true }
build_metadata = { workspace = true }
errors = { workspace = true }
fal_client = { workspace = true }
filesys = { workspace = true }
memory_caching = { workspace = true }
memory_store = { workspace = true }
mimetypes = { workspace = true }
openai_sora_client = { workspace = true }
primitives = { workspace = true }
storyteller_client = { workspace = true }
subprocess_common = { workspace = true }
tokens = { workspace = true }
# External
@@ -41,7 +39,6 @@ anyhow = { workspace = true }
base64 = { workspace = true }
bytes = "1.10"
chrono = { workspace = true }
const_format = "0.2.34"
directories = "6.0.0"
dirs = "6.0.0"
futures = "0.3"
@@ -54,7 +51,6 @@ reqwest = { workspace = true, features = ["multipart", "stream"], default-featur
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
tauri = { workspace = true }
tauri-plugin-log = "2.3.1"
tauri-plugin-upload = "2.2.1"
@@ -73,9 +69,7 @@ discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence
tauri-plugin-dialog = "2"
[dev-dependencies]
libloading = "0.8.6"
serial_test = "3.2.0"
assertor = "0.0.3"
[profile.dev]
incremental = true # Compile your binary in smaller steps.
@@ -6,6 +6,9 @@ use std::str::FromStr;
pub type CommandResult<SuccessPayload, ErrType, ErrPayload> =
Result<CommandSuccessResponseWrapper<SuccessPayload>, CommandErrorResponseWrapper<ErrType, ErrPayload>>;
/// Easy to use infallible response.
pub type InfallibleResponse<SuccessPayload> = CommandSuccessResponseWrapper<SuccessPayload>;
/// No inner payloads for this type. Just strings as messages.
pub type SimpleCommandResult = CommandResult<(), (), ()>;
@@ -0,0 +1,196 @@
use crate::core::commands::command_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus, CommandResult, SerializeMarker};
use crate::core::commands::enqueue::image::handle_fal::handle_fal;
use crate::core::commands::enqueue::image::handle_sora::handle_sora;
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
use crate::core::events::sendable_event_trait::SendableEvent;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
use crate::core::utils::get_url_file_extension::get_url_file_extension;
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
use crate::core::utils::simple_http_download::simple_http_download;
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
use crate::services::sora::events::sora_image_enqueue_failure_event::SoraImageEnqueueFailureEvent;
use crate::services::sora::events::sora_image_enqueue_success_event::SoraImageEnqueueSuccessEvent;
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use anyhow::anyhow;
use base64::prelude::BASE64_STANDARD;
use base64::{DecodeError, Engine};
use errors::{AnyhowError, AnyhowResult};
use fal_client::error::fal_error_plus::FalErrorPlus;
use fal_client::requests::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
use fal_client::requests::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
use fal_client::requests::remove_background_rembg::remove_background_rembg;
use filesys::file_read_bytes::file_read_bytes;
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
use image::{DynamicImage, EncodableLayout, ImageReader};
use log::{debug, error, info, warn};
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
use openai_sora_client::credentials::SoraCredentials;
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
use openai_sora_client::sora_error::SoraError;
use reqwest::Url;
use serde_derive::{Deserialize, Serialize};
use std::fs::read_to_string;
use std::io::{Cursor, Read, Write};
use std::ops::Add;
use std::path::PathBuf;
use std::time::Duration;
use storyteller_client::error::api_error::ApiError;
use storyteller_client::error::api_error::ApiError::InternalServerError;
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
use storyteller_client::utils::api_host::ApiHost;
use tauri::{AppHandle, Emitter, Manager, State};
use tempfile::NamedTempFile;
use tokens::tokens::media_files::MediaFileToken;
#[derive(Deserialize)]
pub struct EnqueueTextToImageRequest {
/// Text prompt for the image generation. Required.
pub prompt: Option<String>,
/// The model to use.
pub model: Option<EnqueueTextToImageModel>,
}
#[derive(Deserialize, Debug, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum EnqueueTextToImageModel {
#[serde(rename = "flux_pro_ultra")]
FluxProUltra,
#[serde(rename = "gpt_image_1")]
GptImage1,
#[serde(rename = "recraft_3")]
Recraft3,
}
#[derive(Serialize)]
pub struct EnqueueTextToImageSuccessResponse {
}
impl SerializeMarker for EnqueueTextToImageSuccessResponse {}
#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum EnqueueTextToImageErrorType {
/// Caller didn't specify a model
ModelNotSpecified,
/// Generic server error
ServerError,
/// No Fal API key available
NeedsFalApiKey,
/// Fal had an API error
FalError,
}
#[tauri::command]
pub async fn enqueue_text_to_image_command(
request: EnqueueTextToImageRequest,
app_data_root: State<'_, AppDataRoot>,
fal_creds_manager: State<'_, FalCredentialManager>,
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
fal_task_queue: State<'_, FalTaskQueue>,
sora_creds_manager: State<'_, SoraCredentialManager>,
sora_task_queue: State<'_, SoraTaskQueue>,
) -> CommandResult<EnqueueTextToImageSuccessResponse, EnqueueTextToImageErrorType, ()> {
info!("enqueue_text_to_image called");
let result = handle_request(
request,
&app_data_root,
&fal_creds_manager,
&storyteller_creds_manager,
&fal_task_queue,
&sora_creds_manager,
&sora_task_queue,
).await;
match result {
Err(err) => {
error!("error: {:?}", err);
//let event = SoraImageEnqueueFailureEvent {};
//let result = event.send(&app);
//if let Err(err) = result {
// error!("Failed to emit event: {:?}", err);
//}
let mut status = CommandErrorStatus::ServerError;
let mut error_type = EnqueueTextToImageErrorType::ServerError;
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
match err {
InternalImageError::NoModelSpecified => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueTextToImageErrorType::ModelNotSpecified;
error_message = "No model specified for image generation";
}
InternalImageError::NeedsFalApiKey => {
status = CommandErrorStatus::Unauthorized;
error_type = EnqueueTextToImageErrorType::NeedsFalApiKey;
error_message = "You need to set a FAL api key";
},
_ => {}, // Fall-through
}
Err(CommandErrorResponseWrapper {
status,
error_message: Some(error_message.to_string()),
error_type: Some(error_type),
error_details: None,
})
}
Ok(result) => {
//let event = SoraImageEnqueueSuccessEvent {};
//let result = event.send(&app);
//if let Err(err) = result {
// error!("Failed to emit event: {:?}", err);
//}
Ok(EnqueueTextToImageSuccessResponse {
}.into())
}
}
}
pub async fn handle_request(
request: EnqueueTextToImageRequest,
app_data_root: &AppDataRoot,
fal_creds_manager: &FalCredentialManager,
storyteller_creds_manager: &StorytellerCredentialManager,
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<(), InternalImageError> {
match request.model {
None => {
return Err(InternalImageError::NoModelSpecified);
}
Some(EnqueueTextToImageModel::FluxProUltra) | Some(EnqueueTextToImageModel::Recraft3) => {
handle_fal(request, fal_creds_manager, fal_task_queue).await?;
},
Some(EnqueueTextToImageModel::GptImage1) => {
handle_sora(request, sora_creds_manager, sora_task_queue).await?;
}
};
Ok(())
}
@@ -0,0 +1,67 @@
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use anyhow::anyhow;
use fal_client::requests::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
use fal_client::requests::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
use log::{error, info, warn};
pub async fn handle_fal(
request: EnqueueTextToImageRequest,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<(), InternalImageError> {
let api_key = fal_creds_manager.get_key_required()
.map_err(|err| {
error!("EnqueueTextToImage FAL api key required: {:?}", err);
InternalImageError::NeedsFalApiKey
})?;
let prompt = request.prompt.as_deref().unwrap_or("");
let result = match request.model {
None => {
return Err(InternalImageError::NoModelSpecified);
}
Some(EnqueueTextToImageModel::GptImage1) => {
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: fal is handling sora images")));
}
Some(EnqueueTextToImageModel::FluxProUltra) => {
info!("enqueue Flux Pro Ultra text-to-image with prompt: {}", prompt);
enqueue_flux_pro_ultra_text_to_image(FluxProUltraTextToImageArgs {
prompt,
api_key: &api_key,
}).await
}
Some(EnqueueTextToImageModel::Recraft3) => {
info!("enqueue Recraft v3 text-to-image with prompt: {}", prompt);
enqueue_recraft3_text_to_image(Recraft3TextToImageArgs {
prompt,
api_key: &api_key,
}).await
}
};
match result {
Ok(enqueued) => {
info!("Successfully enqueued text to image");
if let Err(err) = fal_task_queue.insert(&enqueued) {
error!("Failed to enqueue task: {:?}", err);
return Err(InternalImageError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
}
}
Err(err) => {
error!("Failed to enqueue text to image: {:?}", err);
return Err(InternalImageError::FalError(err));
}
}
Ok(())
}
@@ -0,0 +1,77 @@
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use anyhow::anyhow;
use fal_client::requests::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
use fal_client::requests::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
use log::{error, info, warn};
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
use openai_sora_client::sora_error::SoraError;
use std::time::Duration;
const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds
pub async fn handle_sora(
request: EnqueueTextToImageRequest,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<(), InternalImageError> {
let mut creds = sora_creds_manager
.get_credentials_required()
.map_err(|err| {
error!("EnqueueTextToImage Sora creds required: {:?}", err);
InternalImageError::NeedsFalApiKey
})?;
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
.await
.map_err(|err| {
error!("Failed to upgrade or renew session: {:?}", err);
err
})?;
if credential_updated {
info!("Storing updated credentials");
sora_creds_manager.set_credentials(&creds)?;
}
let prompt = request.prompt.as_deref().unwrap_or("");
let result =
simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest {
prompt: prompt.to_string(),
num_images: NumImages::One,
image_size: ImageSize::Square,
credentials: &creds,
request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT),
}).await;
let (response, maybe_new_creds) =
match result {
Ok((response, maybe_new_creds)) => (response, maybe_new_creds),
Err(err) => {
return Err(InternalImageError::SoraError(err));
}
};
if let Some(new_creds) = maybe_new_creds {
info!("Storing updated credentials.");
sora_creds_manager.set_credentials(&new_creds)?;
}
info!("New Sora Task ID: {:?} ", response.task_id);
sora_task_queue.insert(&response.task_id)?;
Ok(())
}
@@ -0,0 +1,54 @@
use base64::DecodeError;
use errors::AnyhowError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use openai_sora_client::sora_error::SoraError;
use storyteller_client::error::api_error::ApiError;
#[derive(Debug)]
pub enum InternalImageError {
NoModelSpecified,
NeedsFalApiKey,
NeedsSoraCredentials,
FalError(FalErrorPlus),
SoraError(SoraError),
AnyhowError(AnyhowError),
StorytellerApiError(ApiError),
DecodeError(DecodeError),
IoError(std::io::Error),
}
impl From<AnyhowError> for InternalImageError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<FalErrorPlus> for InternalImageError {
fn from(value: FalErrorPlus) -> Self {
Self::FalError(value)
}
}
impl From<SoraError> for InternalImageError {
fn from(value: SoraError) -> Self {
Self::SoraError(value)
}
}
impl From<ApiError> for InternalImageError {
fn from(value: ApiError) -> Self {
Self::StorytellerApiError(value)
}
}
impl From<DecodeError> for InternalImageError {
fn from(value: DecodeError) -> Self {
Self::DecodeError(value)
}
}
impl From<std::io::Error> for InternalImageError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
@@ -0,0 +1,4 @@
pub mod enqueue_text_to_image_command;
pub mod handle_fal;
pub mod handle_sora;
pub mod internal_image_error;
@@ -0,0 +1 @@
pub mod image;
@@ -0,0 +1,39 @@
use crate::core::commands::command_response_wrapper::{CommandResult, InfallibleResponse, SerializeMarker};
use crate::core::state::os_platform::OsPlatform;
use crate::services::fal::commands::fal_background_removal_command::FalBackgroundRemovalSuccessResponse;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use chrono::{DateTime, Utc};
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
use image::ImageReader;
use log::info;
use serde_derive::Serialize;
use std::io::Cursor;
#[derive(Debug, Serialize)]
pub struct BuildInfoResponse {
pub build_timestamp: DateTime<Utc>,
pub git_commit_id: Option<String>,
pub git_commit_short_id: Option<String>,
pub git_commit_timestamp: Option<DateTime<Utc>>,
}
impl SerializeMarker for BuildInfoResponse {}
#[tauri::command]
pub fn get_build_info_command() -> InfallibleResponse<BuildInfoResponse> {
info!("get_build_info_command called...");
let build_timestamp = build_metadata::build_timestamp();
let git_commit_id = build_metadata::git_commit_id();
let git_commit_short_id = build_metadata::git_commit_short_id();
let git_commit_timestamp = build_metadata::git_commit_timestamp();
BuildInfoResponse {
build_timestamp,
git_commit_id: git_commit_id.map(|s| s.to_string()),
git_commit_short_id: git_commit_short_id.map(|s| s.to_string()),
git_commit_timestamp,
}.into()
}
@@ -1,4 +1,6 @@
pub mod app_preferences;
pub mod command_response_wrapper;
pub mod enqueue;
pub mod flip_image;
pub mod get_build_info_command;
pub mod platform_info_command;
+4
View File
@@ -5,7 +5,9 @@ use tauri::Manager;
use crate::core::commands::app_preferences::get_app_preferences_command::get_app_preferences_command;
use crate::core::commands::app_preferences::update_app_preference_command::update_app_preferences_command;
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::enqueue_text_to_image_command;
use crate::core::commands::flip_image::flip_image;
use crate::core::commands::get_build_info_command::get_build_info_command;
use crate::core::commands::platform_info_command::platform_info_command;
use crate::core::state::app_preferences::app_preferences_manager::load_app_preferences_or_default;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
@@ -159,11 +161,13 @@ pub fn run() {
.manage(storyteller_creds_manager_3)
.invoke_handler(tauri::generate_handler![
check_sora_session_command,
enqueue_text_to_image_command,
fal_background_removal_command,
fal_hunyuan_image_to_3d_command,
fal_kling_image_to_video_command,
flip_image,
get_app_preferences_command,
get_build_info_command,
get_fal_api_key_command,
open_sora_login_command,
platform_info_command,
@@ -19,7 +19,7 @@ use anyhow::anyhow;
use base64::prelude::BASE64_STANDARD;
use base64::{DecodeError, Engine};
use errors::{AnyhowError, AnyhowResult};
use fal_client::fal_error_plus::FalErrorPlus;
use fal_client::error::fal_error_plus::FalErrorPlus;
use fal_client::requests::remove_background_rembg::remove_background_rembg;
use filesys::file_read_bytes::file_read_bytes;
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
@@ -20,9 +20,8 @@ use anyhow::anyhow;
use base64::prelude::BASE64_STANDARD;
use base64::{DecodeError, Engine};
use errors::{AnyhowError, AnyhowResult};
use fal_client::fal_error_plus::FalErrorPlus;
use fal_client::error::fal_error_plus::FalErrorPlus;
use fal_client::requests::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
use fal_client::requests::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
use fal_client::requests::remove_background_rembg::remove_background_rembg;
use filesys::file_read_bytes::file_read_bytes;
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
@@ -20,8 +20,8 @@ use anyhow::anyhow;
use base64::prelude::BASE64_STANDARD;
use base64::{DecodeError, Engine};
use errors::{AnyhowError, AnyhowResult};
use fal_client::fal_error_plus::FalErrorPlus;
use fal_client::requests::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
use fal_client::error::fal_error_plus::FalErrorPlus;
use fal_client::requests::video_gen::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
use fal_client::requests::remove_background_rembg::remove_background_rembg;
use filesys::file_read_bytes::file_read_bytes;
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
@@ -57,10 +57,15 @@ use tokens::tokens::media_files::MediaFileToken;
#[derive(Deserialize)]
pub struct FalKlingImageToVideoRequest {
/// Image media file; the image to remove the background from.
/// This must be supplied **OR** the `base64_image`.
pub image_media_token: Option<MediaFileToken>,
/// Base64-encoded image
/// This must be supplied **OR** the `image_media_token`.
pub base64_image: Option<String>,
/// Optional: Text prompt.
pub prompt: Option<String>,
}
#[derive(Serialize, Debug)]
@@ -209,6 +214,7 @@ pub async fn image_to_video(
image_path: filename,
api_key: &api_key,
duration: Kling16Duration::Default,
prompt: request.prompt.as_deref().unwrap_or(""),
}).await?;
if let Err(err) = fal_task_queue.insert(&enqueued) {
@@ -9,8 +9,8 @@ use crate::services::sora::events::sora_image_generation_failed_event::SoraImage
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use errors::AnyhowResult;
use fal_client::error::fal_error_plus::FalErrorPlus;
use fal_client::export::queue::Status;
use fal_client::fal_error_plus::FalErrorPlus;
use fal_client::model::fal_request_id::FalRequestId;
use fal_client::utils::queue_status_checker::QueueStatusChecker;
use log::{error, info, warn};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"default":{"identifier":"default","description":"enables the default permissions","local":true,"windows":["main"],"permissions":["core:default","log:default"]}}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+10 -12
View File
@@ -9,7 +9,6 @@ authors = [
publish = false
[lib]
# NB: Public "errors" crate is name-squatted, so we're fine to use this internally.
name = "actix_cors_configs"
path = "src/lib.rs"
@@ -17,22 +16,21 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Internal
#errors = { path = "../../lib/errors" }
reusable_types = { path = "../../_types/reusable_types" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
workspace-build-acceleration = { workspace = true }
# Internal
reusable_types = { path = "../../schema/public/reusable_types" }
# External
log = "0.4.14"
log = { workspace = true }
# Actix
actix-cors = "0.6.4"
actix-http = "3.7.0"
actix-web = "4.7.0"
url = { version = "2.5.0", features = [] }
actix-cors = { workspace = true }
actix-http = { workspace = true }
actix-web = { workspace = true }
url = { workspace = true }
[dev-dependencies]
actix-rt = "2.10.0"
actix-rt = { workspace = true }
speculoos = "0.11.0" # Fluent assertions
+9 -11
View File
@@ -17,18 +17,16 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Internal
errors = { path = "../../lib/errors" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
workspace-build-acceleration = { workspace = true }
# Internal
errors = { workspace = true }
# Actix
actix-http = "3.7.0"
actix-rt = "2.10.0"
actix-service = "2.0.2"
actix-utils = "3.0.1"
actix-web = "4.7.0"
actix-http = { workspace = true }
actix-service = { workspace = true }
actix-web = { workspace = true }
# IPv4 and v6 CIDRs (crate has few dependencies)
cidr-utils = "0.5.11"
@@ -38,8 +36,8 @@ num-traits = "0.2.17" # for cidr-utils
futures-util = "0.3.29"
# Serialization
serde = "1.0.125"
serde_json = "1.0.108"
serde = { workspace = true }
serde_json = { workspace = true }
[dev-dependencies]
# None yet
@@ -14,9 +14,9 @@ path = "src/lib.rs"
[dependencies]
# Internal
enums = { path = "../../../_types/enums" }
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../errors" }
tokens = { path = "../../../_types/tokens" }
tokens = { path = "../../../schema/public/tokens" }
# External
bytes = "1.3"
@@ -22,7 +22,6 @@ errors = { workspace = true }
# External
anyhow = { workspace = true }
chrono = { workspace = true }
fal = { version = "0.3.6", features = ["endpoints"] }
futures = { workspace = true }
reqwest = { workspace = true }
@@ -32,4 +31,3 @@ url = { workspace = true }
[dev-dependencies]
testing = { path = "../../testing" }
filesys = { path = "../../files/filesys" }
@@ -0,0 +1,20 @@
use crate::error::fal_error_plus::FalErrorPlus;
/// Better classify the `fal::FalError` into a more user-friendly `FalErrorPlus`.
pub fn classify_fal_error(err: fal::FalError) -> FalErrorPlus {
match err {
fal::FalError::RequestError(_) => FalErrorPlus::FalError(err),
fal::FalError::ImageError(_) => FalErrorPlus::FalError(err),
fal::FalError::SerializeError(_) => FalErrorPlus::FalError(err),
fal::FalError::StreamError(_) => FalErrorPlus::FalError(err),
fal::FalError::Other(ref inner) => {
if inner.contains("Invalid Key Authorization header format") {
FalErrorPlus::FalApiKeyError(inner.to_string())
} else if inner.contains("No user found for Key ID and Secret") {
FalErrorPlus::FalApiKeyError(inner.to_string())
} else {
FalErrorPlus::FalError(err)
}
}
}
}
@@ -1,3 +1,4 @@
use crate::error::classify_fal_error::classify_fal_error;
use std::error::Error;
use std::fmt::{Display, Formatter};
@@ -6,6 +7,8 @@ use std::fmt::{Display, Formatter};
pub enum FalErrorPlus {
/// An error arising in the `fal` crate.
FalError(fal::FalError),
/// The fal API key is invalid.
FalApiKeyError(String),
/// Another error we didn't handle.
AnyhowError(anyhow::Error),
/// URL parse errors.
@@ -18,6 +21,7 @@ impl Display for FalErrorPlus {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::FalError(err) => write!(f, "FalErrorPlus::FalError: {:?}", err),
Self::FalApiKeyError(reason) => write!(f, "FalErrorPlus::FalApiKeyError: {}", reason),
Self::AnyhowError(err) => write!(f, "FalErrorPlus::AnyhowError: {:?}", err),
Self::UrlParseError(err) => write!(f, "FalErrorPlus::UrlParseError: {:?}", err),
Self::UnhandledEndpoint(endpoint) => write!(f, "FalErrorPlus::UnhandledEndpoint: {:?}", endpoint),
@@ -29,7 +33,7 @@ impl Error for FalErrorPlus {}
impl From<fal::FalError> for FalErrorPlus {
fn from(err: fal::FalError) -> Self {
FalErrorPlus::FalError(err)
classify_fal_error(err)
}
}
@@ -0,0 +1,2 @@
pub mod classify_fal_error;
pub mod fal_error_plus;
+1 -1
View File
@@ -8,7 +8,7 @@ pub mod export {
}
pub mod creds;
pub mod fal_error_plus;
pub mod error;
pub mod model;
pub mod requests;
pub mod utils;
@@ -1,29 +1,46 @@
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";
const HUNYUAN_3D_2_BASE: &str = "fal-ai/hunyuan3d/v2";
const KLING16_IMAGE_TO_VIDEO: &str = "fal-ai/kling-video/v1.6/pro/image-to-video";
const MINIMAX_IMAGE_TO_VIDEO: &str = "fal-ai/minimax/image-to-video";
const RECRAFT_V3_TEXT_TO_IMAGE: &str = "fal-ai/recraft-v3";
#[derive(Clone, Debug)]
pub enum FalEndpoint {
Kling16ImageToVideo,
FluxProUltraTextToImage,
Hunyuan3d2Base,
Kling16ImageToVideo,
Minimax01ImageToVideo,
RecraftV3TextToImage,
Other(String),
}
impl FalEndpoint {
pub fn from_queue_response<R: DeserializeOwned>(queue: &Queue<R>) -> Self {
match queue.endpoint.as_str() {
FLUX_PRO_ULTRA_TEXT_TO_IMAGE => FalEndpoint::FluxProUltraTextToImage,
HUNYUAN_3D_2_BASE => FalEndpoint::Hunyuan3d2Base,
KLING16_IMAGE_TO_VIDEO => FalEndpoint::Kling16ImageToVideo,
MINIMAX_IMAGE_TO_VIDEO => FalEndpoint::Minimax01ImageToVideo,
RECRAFT_V3_TEXT_TO_IMAGE => FalEndpoint::RecraftV3TextToImage,
_ => FalEndpoint::Other(queue.endpoint.clone()),
}
}
pub fn url(&self) -> &str {
match self {
Self::FluxProUltraTextToImage => FLUX_PRO_ULTRA_TEXT_TO_IMAGE,
Self::Hunyuan3d2Base => HUNYUAN_3D_2_BASE,
Self::Kling16ImageToVideo => KLING16_IMAGE_TO_VIDEO,
Self::Minimax01ImageToVideo => MINIMAX_IMAGE_TO_VIDEO,
Self::RecraftV3TextToImage => RECRAFT_V3_TEXT_TO_IMAGE,
Self::Other(url) => url,
}
}
@@ -1,5 +1,6 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::fal_error_plus::FalErrorPlus;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::hunyuan3d::v2::v2;
use fal::endpoints::fal_ai::hunyuan3d::v2::Hunyuan3DInput;
@@ -16,12 +17,6 @@ pub struct Hunyuan2Args<'a, P: AsRef<Path>> {
pub async fn enqueue_hunyuan2_image_to_3d<P: AsRef<Path>>(args: Hunyuan2Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
/*
TODO: Handle error messages -
FalError(FalError(Other("{\"detail\": \"Invalid Key Authorization header format. Expected '<key_id>:<key_secret>'.\"}")))
FalError(FalError(Other("{\"detail\": \"No user found for Key ID and Secret\"}")))
*/
let request = Hunyuan3DInput {
input_image_url: image_url,
textured_mesh: Some(true),
@@ -34,7 +29,12 @@ pub async fn enqueue_hunyuan2_image_to_3d<P: AsRef<Path>>(args: Hunyuan2Args<'_,
let result = v2(request)
.with_api_key(&args.api_key.0)
.queue()
.await?;
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
@@ -0,0 +1,66 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::v1_1_ultra;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::FluxProUltraTextToImageInput;
use futures::StreamExt;
use std::io::Write;
pub struct FluxProUltraTextToImageArgs<'a> {
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
// TODO: Size parameter
}
pub async fn enqueue_flux_pro_ultra_text_to_image(args: FluxProUltraTextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
let request = FluxProUltraTextToImageInput {
prompt: args.prompt.to_string(),
safety_tolerance: Some("5".to_string()), // 1 is most strict, 5 is most permissive
enable_safety_checker: Some(false),
num_images: Some(1), // Default is 1
output_format: Some("png".to_string()),
raw: Some(true), // Generate less processed, more natural-looking images. Default is false.
aspect_ratio: None, // Default is "16:9"
seed: None,
sync_mode: None, // Synchronous / slow
};
let result = v1_1_ultra(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_flux_pro() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = FluxProUltraTextToImageArgs {
prompt: "a corgi smiles at the camera, inside retro video game store, lots of sci-fi props from Cowboy Bebop, photorealistic",
api_key: &api_key,
};
let result = enqueue_flux_pro_ultra_text_to_image(args).await?;
Ok(())
}
}
@@ -0,0 +1,63 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::recraft_v3::recraft_v3;
use fal::endpoints::fal_ai::recraft_v3::ImageSizeProperty;
use fal::endpoints::fal_ai::recraft_v3::TextToImageInput;
use futures::StreamExt;
use std::io::Write;
pub struct Recraft3TextToImageArgs<'a> {
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
// TODO: Size parameter
}
pub async fn enqueue_recraft3_text_to_image(args: Recraft3TextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
let request = TextToImageInput {
image_size: Some(ImageSizeProperty::SquareHd),
prompt: args.prompt.to_string(),
style: None, // eg. "realistic_image"
style_id: None, // LoRA.
colors: None, // Preferable colors. We can offer this later.
};
let result = recraft_v3(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_recraft3() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Recraft3TextToImageArgs {
prompt: "a corgi looks out over the water",
api_key: &api_key,
};
let result = enqueue_recraft3_text_to_image(args).await?;
Ok(())
}
}
@@ -0,0 +1,2 @@
pub mod enqueue_flux_pro_ultra_text_to_image;
pub mod enqueue_recraft3_text_to_image;
@@ -1,3 +1,4 @@
pub mod enqueue_hunyuan2_image_to_3d;
pub mod enqueue_kling_16_image_to_video;
pub mod remove_background_rembg;
pub mod remove_background_rembg;
pub mod video_gen;
pub mod image_gen;
@@ -1,5 +1,6 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::fal_error_plus::FalErrorPlus;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::RemoveBackgroundInput;
use fal::prelude::fal_ai::imageutils::rembg::rembg;
use fal::prelude::*;
@@ -21,12 +22,6 @@ pub struct RemBgResponse {
pub async fn remove_background_rembg<P: AsRef<Path>>(image_path: P, api_key: &FalApiKey) -> Result<RemBgResponse, FalErrorPlus> {
let image_url = file_to_base64_url(image_path)?;
/*
TODO: Handle error messages -
FalError(FalError(Other("{\"detail\": \"Invalid Key Authorization header format. Expected '<key_id>:<key_secret>'.\"}")))
FalError(FalError(Other("{\"detail\": \"No user found for Key ID and Secret\"}")))
*/
let request = RemoveBackgroundInput {
image_url,
crop_to_bbox: None,
@@ -36,7 +31,12 @@ pub async fn remove_background_rembg<P: AsRef<Path>>(image_path: P, api_key: &Fa
let result = rembg(request)
.with_api_key(&api_key.0)
.queue()
.await?;
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
let mut stream = result.stream(true).await?;
@@ -1,15 +1,16 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::fal_error_plus::FalErrorPlus;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::kling_video::v1_6::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
use url::Url;
pub struct Kling16Args<'a, P: AsRef<Path>> {
pub image_path: P,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling16Duration,
}
@@ -24,12 +25,6 @@ pub enum Kling16Duration {
pub async fn enqueue_kling_16_image_to_video<P: AsRef<Path>>(args: Kling16Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
/*
TODO: Handle error messages -
FalError(FalError(Other("{\"detail\": \"Invalid Key Authorization header format. Expected '<key_id>:<key_secret>'.\"}")))
FalError(FalError(Other("{\"detail\": \"No user found for Key ID and Secret\"}")))
*/
let duration = match args.duration{
Kling16Duration::Default => None,
Kling16Duration::FiveSeconds => Some("5".to_string()), // Gross...
@@ -38,7 +33,7 @@ pub async fn enqueue_kling_16_image_to_video<P: AsRef<Path>>(args: Kling16Args<'
let request = ProImageToVideoRequest {
image_url,
prompt: "".to_string(),
prompt: args.prompt.to_string(),
aspect_ratio: None,
cfg_scale: None,
duration,
@@ -49,7 +44,12 @@ pub async fn enqueue_kling_16_image_to_video<P: AsRef<Path>>(args: Kling16Args<'
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue()
.await?;
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
@@ -57,7 +57,7 @@ pub async fn enqueue_kling_16_image_to_video<P: AsRef<Path>>(args: Kling16Args<'
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
use crate::requests::video_gen::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
use errors::AnyhowResult;
use std::fs::read_to_string;
use testing::test_file_path::test_file_path;
@@ -74,6 +74,7 @@ mod tests {
let args = Kling16Args {
image_path: image,
prompt: "a corgi looks out over the water",
api_key: &api_key,
duration: Kling16Duration::Default,
};
@@ -0,0 +1,71 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::minimax::image_to_video::image_to_video;
use fal::endpoints::fal_ai::minimax::image_to_video::VideoOutput;
use fal::endpoints::fal_ai::minimax::image_to_video::ImageToVideoRequest;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Minimax01Args<'a, P: AsRef<Path>> {
pub image_path: P,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
}
pub async fn enqueue_minimax_01_image_to_video<P: AsRef<Path>>(args: Minimax01Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let request = ImageToVideoRequest {
image_url,
prompt: args.prompt.to_string(),
prompt_optimizer: None,
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use errors::AnyhowResult;
use std::fs::read_to_string;
use testing::test_file_path::test_file_path;
use crate::requests::video_gen::enqueue_minimax_01_image_to_video::{enqueue_minimax_01_image_to_video, Minimax01Args};
#[tokio::test]
#[ignore]
async fn test_minimax_video() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Minimax01Args {
image_path: image,
prompt: "a dog jumps into the water",
api_key: &api_key,
};
let result = enqueue_minimax_01_image_to_video(args).await?;
println!("Request ID: {:?}", result.request_id);
Ok(())
}
}
@@ -0,0 +1,2 @@
pub mod enqueue_kling_16_image_to_video;
pub mod enqueue_minimax_01_image_to_video;
@@ -1,9 +1,13 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::fal_error_plus::FalErrorPlus;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use crate::model::fal_endpoint::FalEndpoint;
use anyhow::anyhow;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::Output;
use fal::endpoints::fal_ai::hunyuan3d::v2::ObjectOutput;
use fal::endpoints::fal_ai::kling_video::v1_6::pro::effects::I2VOutput;
use fal::endpoints::fal_ai::minimax::image_to_video::VideoOutput;
use fal::endpoints::fal_ai::recraft_v3::TextToImageOutput;
use fal::prelude::QueueResponse;
use fal::queue::{Queue, QueueStatus};
use reqwest::Client;
@@ -20,6 +24,11 @@ impl QueueStatusChecker {
pub async fn check_status(&self, request: &EnqueuedRequest) -> Result<QueueStatus, FalErrorPlus> {
match &request.fal_endpoint {
FalEndpoint::FluxProUltraTextToImage => {
let queue = self.make_queue::<Output>(&request);
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Hunyuan3d2Base => {
let queue = self.make_queue::<ObjectOutput>(&request);
let status = queue.status(false).await?;
@@ -30,6 +39,16 @@ impl QueueStatusChecker {
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Minimax01ImageToVideo => {
let queue = self.make_queue::<VideoOutput>(&request); // Assuming Minimax uses the same output type
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::RecraftV3TextToImage => {
let queue = self.make_queue::<TextToImageOutput>(&request);
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Other(endpoint) => {
Err(FalErrorPlus::UnhandledEndpoint(format!("Unsupported endpoint: {}", endpoint)))
}
@@ -38,15 +57,35 @@ impl QueueStatusChecker {
pub async fn get_download_url(&self, request: &EnqueuedRequest) -> Result<String, FalErrorPlus> {
match &request.fal_endpoint {
FalEndpoint::FluxProUltraTextToImage => {
let queue = self.make_queue::<Output>(&request);
let response = queue.response().await?;
// TODO(bt,2025-05-26): Handle multiple images
Ok(response.images.first()
.ok_or_else(|| FalErrorPlus::AnyhowError(anyhow!("No images returned")))?
.url.clone())
}
FalEndpoint::Hunyuan3d2Base => {
let queue = self.make_queue::<ObjectOutput>(&request);
let status = queue.response().await?;
Ok(status.model_mesh.url)
let response = queue.response().await?;
Ok(response.model_mesh.url)
}
FalEndpoint::Kling16ImageToVideo => {
let queue = self.make_queue::<I2VOutput>(&request);
let status = queue.response().await?;
Ok(status.video.url)
let response = queue.response().await?;
Ok(response.video.url)
}
FalEndpoint::Minimax01ImageToVideo => {
let queue = self.make_queue::<VideoOutput>(&request); // Assuming Minimax uses the same output type
let response = queue.response().await?;
Ok(response.video.url)
}
FalEndpoint::RecraftV3TextToImage => {
let queue = self.make_queue::<TextToImageOutput>(&request);
let response = queue.response().await?;
Ok(response.images.first()
.ok_or_else(|| FalErrorPlus::AnyhowError(anyhow!("No images returned")))?
.url.clone())
}
FalEndpoint::Other(endpoint) => {
Err(FalErrorPlus::UnhandledEndpoint(format!("Unsupported endpoint: {}", endpoint)))
@@ -17,17 +17,14 @@ path = "src/lib.rs"
workspace-build-acceleration = { workspace = true }
# Internal
crockford = { path = "../../data/crockford" }
errors = { path = "../../errors" }
idempotency = { path = "../../idempotency" }
subprocess_common = { path = "../../subprocess_common" }
# External
anyhow = { workspace = true }
base64 = { workspace = true }
chrono = { workspace = true}
cookie = { workspace = true }
hex-literal = { workspace = true }
log = { workspace = true }
once_cell= { workspace = true }
rand = { workspace = true }
@@ -42,4 +39,3 @@ toml = { workspace = true }
[dev-dependencies]
testing = { path = "../../testing" }
filesys = { path = "../../files/filesys" }
@@ -4,4 +4,5 @@ pub mod image_upload_from_file_with_session_auto_renew;
pub mod list_sora_task_status_with_session_auto_renew;
pub mod maybe_refresh_credentials_on_sora_error;
pub mod maybe_upgrade_or_renew_session;
pub mod simple_image_gen_with_session_auto_renew;
pub mod wait_for_image_gen_status;
@@ -0,0 +1,146 @@
use crate::creds::credential_migration::CredentialMigrationRef;
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::creds::sora_sentinel::SoraSentinel;
use crate::requests::bearer::generate::generate_bearer_with_cookie;
use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
use crate::requests::image_gen::sora_image_gen_simple::{sora_image_gen_simple, SoraImageGenSimpleRequest};
use crate::requests::image_gen::{image_gen_http_request, SoraImageGenError};
use crate::requests::sentinel_refresh::generate::token::generate_token;
use crate::sora_error::SoraError;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{info, warn};
use std::time::Duration;
pub struct SimpleImageGenAutoRenewRequest<'a> {
pub prompt: String,
pub num_images: NumImages,
pub image_size: ImageSize,
pub credentials: &'a SoraCredentialSet,
/// This function can try several different requests. This is the timeout for *individual* requests.
pub request_timeout: Option<Duration>,
}
/// Image request with retry and session auto-renewal.
/// If a new sora credential is returned, replace the old one with the new one.
pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAutoRenewRequest<'_>) -> Result<(SoraImageGenResponse, Option<SoraCredentialSet>), SoraError> {
let result = sora_image_gen_simple(SoraImageGenSimpleRequest {
prompt: request.prompt.clone(), // NB: Clone because used again
num_images: request.num_images,
image_size: request.image_size,
credentials: CredentialMigrationRef::New(request.credentials),
request_timeout: request.request_timeout,
}).await;
let err = match result {
Ok(response) => return Ok((response, None)),
Err(err) => err,
};
warn!("Image generation failed: {:?}", err);
let mut refresh_jwt = false;
let mut refresh_sentinel = false;
match err {
// We'll fail these requests...
SoraImageGenError::TooManyConcurrentTasks(_) => {
return Err(SoraError::TooManyConcurrentTasks);
}
SoraImageGenError::GenericError(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("image gen failed with GenericError: {:?}", err)))
}
SoraImageGenError::NetworkError(err) => {
// TODO: The underlying type should be a reqwest::Error.
return Err(SoraError::OtherBadStatus(anyhow!("network error: {:?}", err)))
}
SoraImageGenError::UsernameRequired(_err) => {
return Err(SoraError::SoraUsernameNotYetCreated)
}
// We'll retry these requests...
SoraImageGenError::SentinelBlock(err) => {
warn!("Image generation failed due to sentinel block error: {:?}", err);
refresh_sentinel = true;
}
SoraImageGenError::TokenExpired(err) => {
warn!("Image generation failed due to token expired error: {:?}", err);
refresh_sentinel = true; // TODO: Not sure what this error is, actually.
refresh_jwt = true;
}
SoraImageGenError::InvalidJwt(err) => {
warn!("Image generation failed due to invalid jwt error: {:?}", err);
refresh_jwt = true;
}
}
let mut new_bearer = None;
if refresh_jwt {
info!("Generating new JWT bearer token...");
let cookies = request.credentials.cookies.as_str();
let response = generate_bearer_with_cookie(cookies).await;
match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new JWT bearer token: {:?}", err)))
}
Ok(bearer) => {
match SoraJwtBearerToken::new(bearer) {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("Failed to parse new JWT bearer token: {:?}", err)));
}
Ok(bearer) => new_bearer = Some(bearer),
}
},
}
}
let mut new_sentinel = None;
if refresh_sentinel {
info!("Generating new sentinel...");
let response = generate_token().await;
match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new sentinel: {:?}", err)))
}
Ok(sentinel) => new_sentinel = Some(SoraSentinel::new(sentinel)),
}
}
let mut new_creds = request.credentials.clone();
if let Some(bearer) = new_bearer {
new_creds.jwt_bearer_token = Some(bearer);
}
if let Some(sentinel) = new_sentinel {
new_creds.sora_sentinel = Some(sentinel);
}
// Now try again...
let result = sora_image_gen_simple(SoraImageGenSimpleRequest {
prompt: request.prompt,
num_images: request.num_images,
image_size: request.image_size,
credentials: CredentialMigrationRef::New(&new_creds),
request_timeout: request.request_timeout,
}).await;
match result {
Ok(response) => Ok((response, Some(new_creds))),
Err(err) => match err {
image_gen_http_request::SoraImageGenError::TooManyConcurrentTasks(err) => {
Err(SoraError::TooManyConcurrentTasks)
}
_ => {
warn!("Image gen failed again: {:?}", err);
Err(SoraError::OtherBadStatus(anyhow!("image gen failed again: {:?}", err)))
}
}
}
}
@@ -1,7 +1,7 @@
use crate::creds::credential_migration::CredentialMigrationRef;
use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
use crate::requests::image_gen::image_gen_http_request::{image_gen_http_request, OperationType, RawSoraImageGenRequest, VideoGenType};
use errors::AnyhowResult;
use crate::requests::image_gen::SoraImageGenError;
use std::time::Duration;
pub struct SoraImageGenSimpleRequest<'a> {
@@ -12,7 +12,7 @@ pub struct SoraImageGenSimpleRequest<'a> {
pub request_timeout: Option<Duration>,
}
pub async fn sora_image_gen_simple(args: SoraImageGenSimpleRequest<'_>) -> AnyhowResult<SoraImageGenResponse> {
pub async fn sora_image_gen_simple(args: SoraImageGenSimpleRequest<'_>) -> Result<SoraImageGenResponse, SoraImageGenError> {
let sora_request = RawSoraImageGenRequest {
r#type: VideoGenType::ImageGen,
operation: OperationType::SimpleCompose,
+1 -1
View File
@@ -17,7 +17,7 @@ normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
workspace-build-acceleration = { workspace = true }
# Internal
crockford = { path = "../data/crockford" }
+34
View File
@@ -0,0 +1,34 @@
[package]
name = "build_metadata"
resolver = "2" # NB: Required by `build-info`
edition = "2024"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "build_metadata"
path = "src/lib.rs"
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
# (None yet)
# External
build-info = "0.0.40"
[dev-dependencies]
# None yet
[build-dependencies]
build-info-build = "0.0.40"
+9
View File
@@ -0,0 +1,9 @@
fn main() {
// Tell Cargo that if the given file changes, to rerun this build script.
// TODO: Use this to test the GIT_SHA, etc. files that CI dumps into the working directory
// println!("cargo::rerun-if-changed=Cargo.toml");
// Make build info available in the main crate sources.
build_info_build::build_script();
}
+43
View File
@@ -0,0 +1,43 @@
use std::ops::Deref;
use build_info::chrono::{DateTime, Utc};
build_info::build_info!(fn version);
pub fn build_timestamp() -> DateTime<Utc> {
version().timestamp
}
pub fn git_commit_id() -> Option<&'static str> {
version().version_control
.as_ref()
.map(|vc| vc.git())
.flatten()
.map(|git| git.commit_id.deref())
}
pub fn git_commit_short_id() -> Option<&'static str> {
version().version_control
.as_ref()
.map(|vc| vc.git())
.flatten()
.map(|git| git.commit_short_id.deref())
}
pub fn git_commit_timestamp() -> Option<DateTime<Utc>> {
version().version_control
.as_ref()
.map(|vc| vc.git())
.flatten()
.map(|git| git.commit_timestamp)
}
#[cfg(test)]
mod tests {
use crate::git_commit_id;
#[test]
#[ignore] // Run manually for testing, not in CI
fn test_git_commit_id() {
assert_eq!(git_commit_id(), None);
}
}
@@ -16,32 +16,28 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
errors = { path = "../../errors" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
errors = { workspace = true }
# External
anyhow = "1.0.86"
base64 = "0.22.1"
log = "0.4.14"
once_cell = "1.18.0"
log = { workspace = true }
once_cell = { workspace = true }
regex = "1.5.4"
# Web
actix-http = "3.7.0"
actix-web = "4.7.0"
actix-http = { workspace = true }
actix-web = { workspace = true }
# Language
language-tags = "0.3.2" # Parsing single tags richly
#accept-language = "2.0.0" # Parsing many tags poorly
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.108"
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true }
[dev-dependencies]
actix-rt = "2.10.0"
# (None yet)
+2 -2
View File
@@ -18,10 +18,10 @@ normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
workspace-build-acceleration = { workspace = true }
# Internal
errors = { path = "../errors" }
# (None Yet)
# External
# (None Yet)
+4 -4
View File
@@ -17,13 +17,13 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# cargo-hakari workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# NB: This is one of the most central crates in the monorepo and everything depends on it.
# Consider the dependencies here wisely.
# Ideally we have almost nothing as a dependency of this crate.
anyhow = "1.0.86"
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
anyhow = { workspace = true }
[dev-dependencies]
# None yet
@@ -26,4 +26,3 @@ mimetypes = { workspace = true }
[dev-dependencies]
testing = { path = "../../testing" }
filesys = { path = "../../files/filesys" }
+5 -6
View File
@@ -19,16 +19,15 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
errors = { path = "../errors" }
redis_common = { path = "../redis_common" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
errors = { workspace = true }
redis_common = { path = "../../schema/database/redis_common" }
# External
anyhow = "1.0.86"
anyhow = { workspace = true }
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
r2d2_redis = "0.14.0"
@@ -32,8 +32,8 @@ primitives = { path = "../../data/primitives" }
cudarc = "0.13.7"
# External
anyhow = "1.0"
base64 = "0.22.1"
anyhow = { workspace = true }
base64 = { workspace = true }
bytes = "1.10"
candle-core = { path = "../../../vendor/candle/candle-core", features = ["cuda"] }
candle-nn = { path = "../../../vendor/candle/candle-nn", features = ["cuda"] }
+3 -3
View File
@@ -20,11 +20,11 @@ normal = ["workspace-build-acceleration"]
# Internal
bucket_paths = { path = "../../lib/bucket_paths" }
cloud_storage = { path = "../../lib/api_clients/cloud_storage" }
enums = { path = "../../_types/enums" }
enums = { path = "../../schema/public/enums" }
errors = { path = "../../lib/errors" }
jobs_common = { path = "../../lib/jobs_common" }
mysql_queries = { path = "../../lib/mysql_queries" }
tokens = { path = "../../_types/tokens" }
mysql_queries = { path = "../../schema/database/mysql_queries" }
tokens = { path = "../../schema/public/tokens" }
# External
anyhow = "1.0.86"
+3 -3
View File
@@ -16,12 +16,12 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# cargo-hakari workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
# (None yet)
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
# External
# (None yet)
+5 -6
View File
@@ -16,14 +16,13 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration = { workspace = true }
# Internal
actix_helpers = { path = "../actix_helpers" }
reusable_types = { path = "../../_types/reusable_types" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
reusable_types = { path = "../../schema/public/reusable_types" }
# External
actix-web = "4.7.0"
anyhow = "1.0.86"
actix-web = { workspace = true }
anyhow = { workspace = true }
+1 -1
View File
@@ -17,7 +17,7 @@ normal = ["workspace-build-acceleration"]
[dependencies]
# Internal
enums = { path = "../../_types/enums" }
enums = { path = "../../schema/public/enums" }
errors = { path = "../../lib/errors" }
# Workspace hack (faster compile times)
@@ -16,13 +16,13 @@ path = "src/lib.rs"
normal = ["workspace-build-acceleration"]
[dependencies]
# Internal
enums = { path = "../../_types/enums" }
errors = { path = "../errors" }
tokens = { path = "../../_types/tokens" }
# Workspace hack (faster compile times)
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
workspace-build-acceleration = { workspace = true }
# Internal
enums = { workspace = true }
errors = { workspace = true }
tokens = { workspace = true }
# External
elasticsearch = "8.5.0-alpha.1"
@@ -32,7 +32,6 @@ once_cell = "1.19.0"
# Serialization
chrono = { version = "0.4.22", features = ["serde"] }
regex = "1.10.2"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.108"
@@ -13,7 +13,7 @@ use tokens::tokens::users::UserToken;
use crate::documents::media_file_document::{MEDIA_FILE_INDEX, MediaFileDocument};
static JSON_QUERY : Lazy<Value> = Lazy::new(|| {
const QUERY_TEMPLATE : &str = include_str!("../../../../../_elasticsearch/searches/media_files/search.json");
const QUERY_TEMPLATE : &str = include_str!("../../../../../../_elasticsearch/searches/media_files/search.json");
let json : Value = serde_json::from_str(QUERY_TEMPLATE)
.expect("json should parse");
@@ -16,7 +16,7 @@ use crate::searches::search_model_weights::predicates::{creator_user_token_predi
use crate::searches::search_model_weights::sort::add_sort;
static JSON_QUERY : Lazy<Value> = Lazy::new(|| {
const QUERY_TEMPLATE : &str = include_str!("../../../../../../_elasticsearch/searches/model_weights/search.json");
const QUERY_TEMPLATE : &str = include_str!("../../../../../../../_elasticsearch/searches/model_weights/search.json");
let json : Value = serde_json::from_str(QUERY_TEMPLATE)
.expect("json should parse");
@@ -20,18 +20,17 @@ normal = ["workspace-build-acceleration"]
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../bucket_paths" }
composite_identifiers = { path = "../../_types/composite_identifiers" }
enums = { path = "../../_types/enums" }
bucket_paths = { path = "../../../lib/bucket_paths" }
composite_identifiers = { path = "../../../schema/public/composite_identifiers" }
enums = { workspace = true }
errors = { workspace = true }
idempotency = { path = "../idempotency" }
primitives = { path = "../data/primitives" }
reusable_types = { path = "../../_types/reusable_types" }
tokens = { path = "../../_types/tokens" }
idempotency = { workspace = true }
reusable_types = { path = "../../../schema/public/reusable_types" }
tokens = { workspace = true }
# External
anyhow = { workspace = true }
chrono = { version = "0.4.22", features = ["serde"] }
chrono = { workspace = true }
log = { workspace = true }
# Needed to safety check some queries we can't use escaping for
@@ -42,13 +41,13 @@ regex = { workspace = true }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true }
serial_test = "3.0.0"
# BigNum
num-traits = "0.2.19"
tokio = { version = "1.38.0", features = ["full"] }
rand = "0.8"
rand = { workspace = true }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono", "bigdecimal" ] }
[dev-dependencies]
serial_test = "3.0.0"

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