mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 21:38:07 -05:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d86c4bd3ba | ||
|
|
f48eea98d2 | ||
|
|
57f799a271 | ||
|
|
1fc5593dd9 | ||
|
|
6848d0df60 | ||
|
|
902c5c11b6 | ||
|
|
d6b44e51ac | ||
|
|
fa6bb23695 | ||
|
|
e75e5624ed |
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0fa824822cd94024dce21243f41e69e4a3e8685c4de676834a7c0ee14982c976"
|
||||
}
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n \n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "status: enums::common::job_status_plus::JobStatusPlus",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "external_third_party_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "creator_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "64dfacc1f0552d539f009fea91e902a2fe296c06ad539808596bbf2c8fc9eaa0"
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n \n maybe_engine_category = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 39
|
||||
"Right": 38
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b4470e607c005421dd8504b9f1d695080a581936ac6ce00b6f437b2764e741be"
|
||||
"hash": "c58f414b5e4d0f0420d1894e2f40311e5613919f49f3ac91f81f5fc2bef1bea8"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE generic_inference_jobs\nSET\n status = ?,\n on_success_result_entity_type = ?,\n on_success_result_entity_token = ?,\n failure_reason = NULL,\n internal_debugging_failure_reason = NULL,\n success_execution_millis = NULL,\n success_inference_execution_millis = NULL,\n retry_at = NULL,\n successfully_completed_at = NOW()\nWHERE token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f68a8917e639418a0bb952bef606c96299001e143c745ee30df99b72d1235d35"
|
||||
}
|
||||
Generated
+13
-159
@@ -648,7 +648,6 @@ name = "artcraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"base64 0.22.1",
|
||||
"build_metadata",
|
||||
"bytes 1.10.1",
|
||||
@@ -660,7 +659,6 @@ dependencies = [
|
||||
"fal_client",
|
||||
"filesys",
|
||||
"futures 0.3.31",
|
||||
"idempotency",
|
||||
"image",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
@@ -691,19 +689,6 @@ dependencies = [
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "artcraft_api_defs"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"enums",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tokens",
|
||||
"utoipa",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "0.9.3"
|
||||
@@ -1681,17 +1666,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-manifest"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d8af896b707212cd0e99c112a78c9497dd32994192a463ed2f7419d29bd8c6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.8.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.9"
|
||||
@@ -3563,25 +3537,26 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "fal"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd7dd8565317310f05569296166e2cbdd2bc870173b2b24b77ed681166268d0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"eventsource-stream",
|
||||
"fal-derive",
|
||||
"futures 0.3.31",
|
||||
"image",
|
||||
"log",
|
||||
"percent-encoding-rfc3986",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smart-default 0.7.1",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fal-derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f377f2f18a6bf107f907aa687df6bda2874bd38b25b1531f2a6fb38a9ca643"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4062,21 +4037,6 @@ dependencies = [
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generate-endpoints"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cargo-manifest",
|
||||
"percent-encoding",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml 0.8.22",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -6424,16 +6384,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
@@ -6884,35 +6834,6 @@ dependencies = [
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openart_client"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"cookie 0.18.1",
|
||||
"crockford",
|
||||
"errors",
|
||||
"filesys",
|
||||
"hex-literal",
|
||||
"idempotency",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rand 0.9.1",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"subprocess_common",
|
||||
"testing",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"toml 0.8.22",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.71"
|
||||
@@ -7068,12 +6989,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
@@ -7247,12 +7162,6 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding-rfc3986"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3637c05577168127568a64e9dc5a6887da720efef07b3d9472d45f63ab191166"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.8.0"
|
||||
@@ -9404,15 +9313,6 @@ dependencies = [
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_service_components"
|
||||
version = "0.0.1"
|
||||
@@ -9899,14 +9799,12 @@ dependencies = [
|
||||
"actix_cors_configs",
|
||||
"actix_helpers",
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"async-stripe",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"billing_component",
|
||||
"bootstrap",
|
||||
"bucket_paths",
|
||||
"bytes 1.10.1",
|
||||
"chrono",
|
||||
"cloud_storage",
|
||||
"collections",
|
||||
@@ -9924,7 +9822,6 @@ dependencies = [
|
||||
"enums",
|
||||
"enums_public",
|
||||
"errors",
|
||||
"fal_client",
|
||||
"filesys",
|
||||
"futures 0.1.31",
|
||||
"futures 0.3.31",
|
||||
@@ -9996,7 +9893,6 @@ name = "storyteller_client"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"backoff",
|
||||
"chrono",
|
||||
"cookie 0.18.1",
|
||||
@@ -10823,10 +10719,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
@@ -10905,16 +10802,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thumbnail_generator"
|
||||
version = "0.1.0"
|
||||
@@ -11240,7 +11127,6 @@ version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||
dependencies = [
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -11387,9 +11273,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -11399,9 +11285,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11410,37 +11296,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec 1.13.2",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11852,12 +11712,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.10.0"
|
||||
|
||||
-10
@@ -27,7 +27,6 @@ members = [
|
||||
"crates/lib/api_clients/google_drive_common",
|
||||
"crates/lib/api_clients/google_sign_in",
|
||||
"crates/lib/api_clients/openai_sora_client",
|
||||
"crates/lib/api_clients/openart_client",
|
||||
"crates/lib/api_clients/runwayml_client",
|
||||
"crates/lib/api_clients/storyteller_client",
|
||||
"crates/lib/api_clients/thumbnail_generator",
|
||||
@@ -88,7 +87,6 @@ members = [
|
||||
#"crates/schema/database/sqlite_queries",
|
||||
|
||||
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
|
||||
"crates/schema/public/api/artcraft_api_defs",
|
||||
"crates/schema/public/composite_identifiers",
|
||||
"crates/schema/public/enums",
|
||||
"crates/schema/public/enums_public",
|
||||
@@ -114,9 +112,6 @@ members = [
|
||||
"crates/service/plugins/user_traits_component",
|
||||
|
||||
# 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",
|
||||
# NB(bt,2025-05-25): Too many failures on Mac. Disable for now.
|
||||
@@ -182,7 +177,6 @@ actix-web-actors = "4.3.1+deprecated"
|
||||
actix-web-lab = "0.20.2"
|
||||
anyhow = "1.0.98"
|
||||
base64 = "0.22.1"
|
||||
bytes = "1.10.1"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
futures = { version = "0.3.31" }
|
||||
@@ -199,22 +193,18 @@ serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.34+deprecated"
|
||||
sha3 = "0.10.8"
|
||||
tauri = { version = "2.4.1", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution
|
||||
tempdir = "0.3.7"
|
||||
tempfile = "3.20.0"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.1", features = ["macros"] }
|
||||
toml = "0.8.22"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
|
||||
# Internal libs
|
||||
artcraft_api_defs = { path = "crates/schema/public/api/artcraft_api_defs" }
|
||||
build_metadata = { path = "crates/lib/build_metadata" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
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" }
|
||||
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
|
||||
filesys = { path = "crates/lib/files/filesys" }
|
||||
idempotency = { path = "crates/lib/idempotency" }
|
||||
images = { path = "crates/lib/files/images" }
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_external_third_party_id;
|
||||
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_external_third_party;
|
||||
@@ -1,9 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_external_third_party VARCHAR(16) DEFAULT NULL;
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_external_third_party_id VARCHAR(64) DEFAULT NULL;
|
||||
@@ -1,9 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP INDEX `index_maybe_external_third_party_id`
|
||||
ON `generic_inference_jobs`;
|
||||
|
||||
DROP INDEX `index_maybe_external_third_party_and_id`
|
||||
ON `generic_inference_jobs`;
|
||||
@@ -1,11 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD UNIQUE INDEX index_maybe_external_third_party_and_id (maybe_external_third_party, maybe_external_third_party_id),
|
||||
ALGORITHM=INPLACE, LOCK=NONE;
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD INDEX index_maybe_external_third_party_id (maybe_external_third_party_id),
|
||||
ALGORITHM=INPLACE, LOCK=NONE;
|
||||
@@ -39,20 +39,8 @@ CREATE TABLE generic_inference_jobs (
|
||||
-- Examples (may not be up to date):
|
||||
-- * text_to_speech
|
||||
-- * voice_conversion
|
||||
-- * image_generation
|
||||
-- * video_generation
|
||||
-- * background_removal
|
||||
inference_category VARCHAR(32) NOT NULL,
|
||||
|
||||
-- If the job is externally enqueued, this is the third-party service
|
||||
-- responsible for the job. Typically, we won't run an inference job for
|
||||
-- jobs being run on a third party.
|
||||
maybe_external_third_party VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- If the job is externally enqueued, this is the ID the third-party
|
||||
-- service uses.
|
||||
maybe_external_third_party_id VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- There is an index on this column.
|
||||
-- NB: See notes on inference_category. This is becoming a problematic field. We're using
|
||||
-- this to load the container spin-up arguments and dependencies, but the former is being
|
||||
@@ -299,13 +287,11 @@ CREATE TABLE generic_inference_jobs (
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (uuid_idempotency_token),
|
||||
UNIQUE KEY (maybe_external_third_party, maybe_external_third_party_id),
|
||||
KEY index_job_type (job_type),
|
||||
KEY index_product_category (product_category),
|
||||
KEY index_inference_category (inference_category),
|
||||
KEY index_maybe_model_type_and_maybe_model_token (maybe_model_type, maybe_model_token),
|
||||
KEY index_maybe_model_type (maybe_model_type),
|
||||
KEY index_maybe_external_third_party_id (maybe_external_third_party_id),
|
||||
KEY fk_maybe_model_token (maybe_model_token),
|
||||
KEY fk_maybe_input_source_token (maybe_input_source_token),
|
||||
KEY fk_maybe_input_source_token_and_type (maybe_input_source_token, maybe_input_source_token_type),
|
||||
|
||||
@@ -23,12 +23,10 @@ tauri-build = { version = "2.1.1", features = [] }
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs = { workspace = true }
|
||||
build_metadata = { workspace = true }
|
||||
errors = { workspace = true }
|
||||
fal_client = { workspace = true }
|
||||
filesys = { workspace = true }
|
||||
idempotency = { workspace = true }
|
||||
memory_store = { workspace = true }
|
||||
mimetypes = { workspace = true }
|
||||
openai_sora_client = { workspace = true }
|
||||
@@ -56,8 +54,8 @@ serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-log = "2.3.1"
|
||||
tauri-plugin-upload = "2.2.1"
|
||||
tempdir = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tempdir = "0.3.7"
|
||||
tempfile = "3.18.0"
|
||||
tokio = { version = "1.42", features = ["rt", "rt-multi-thread", "fs"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
url = { workspace = true }
|
||||
|
||||
+3
-2
@@ -24,8 +24,9 @@ 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::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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};
|
||||
|
||||
@@ -11,9 +11,8 @@ 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::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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 tauri::AppHandle;
|
||||
|
||||
@@ -24,21 +23,6 @@ pub async fn handle_fal(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::GenerateImage);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalImageError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
let api_key = fal_creds_manager.get_key_required()
|
||||
.map_err(|err| {
|
||||
error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
|
||||
@@ -11,8 +11,8 @@ 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::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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};
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod image;
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
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::object::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
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::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::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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;
|
||||
use crate::core::commands::enqueue::object::handle_object_artcraft::handle_object_artcraft;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueImageTo3dObjectModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectModel {
|
||||
#[serde(rename = "hunyuan_3d_2")]
|
||||
Hunyuan3d2,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageTo3dObjectSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageTo3dObjectSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_3d_object_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageTo3dObjectSuccessResponse, EnqueueImageTo3dObjectErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_3d_object_command called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageTo3dObjectErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
InternalObjectError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalObjectError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
InternalObjectError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(EnqueueImageTo3dObjectSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_object_fal(&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_object_artcraft(request, &app, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
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 artcraft_api_defs::generate::object::generate_hunyuan_2_image_to_3d::GenerateHunyuan2ImageTo3dRequest;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
use storyteller_client::generate::object::generate_hunyuan2_image_to_3d::generate_hunyuan2_image_to_3d;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
pub async fn handle_object_artcraft(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
|
||||
info!("Calling Artcraft image to 3d ...");
|
||||
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2) => {
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
let request = GenerateHunyuan2ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
};
|
||||
let result = generate_hunyuan2_image_to_3d(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.0");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.0: {:?}", err);
|
||||
return Err(InternalObjectError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued 3D generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalObjectError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
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::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_fal(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::GenerateImage);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else {
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("No image media token provided")));
|
||||
}
|
||||
|
||||
info!("Calling FAL image to 3d ...");
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2) => {
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
enqueue_hunyuan2_image_to_3d(Hunyuan2Args {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalObjectError {
|
||||
NoModelSpecified,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
StorytellerApiError(ApiError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalObjectError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for InternalObjectError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::FalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InternalObjectError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for InternalObjectError {
|
||||
fn from(value: DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for InternalObjectError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod enqueue_image_to_3d_object_command;
|
||||
pub mod handle_object_artcraft;
|
||||
pub mod handle_object_fal;
|
||||
pub mod internal_object_error;
|
||||
@@ -3,9 +3,8 @@ use serde::Serialize;
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerationServiceProvider {
|
||||
Artcraft,
|
||||
Fal,
|
||||
Sora,
|
||||
Fal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
|
||||
-20
@@ -17,23 +17,3 @@ impl BasicSendableEvent for GenerationEnqueueFailureEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-failure-event";
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure;
|
||||
}
|
||||
|
||||
impl GenerationEnqueueFailureEvent {
|
||||
pub fn no_artcraft_credentials(action: GenerationAction) -> Self {
|
||||
Self {
|
||||
action,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
reason: Some("Please log into Artcraft.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_fal_api_key(action: GenerationAction) -> Self {
|
||||
Self {
|
||||
action,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: Some("No FAL API key is set. Configure this in your settings.".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,6 @@ pub async fn main_window_thread(
|
||||
let window_pos_slot: CloneSlot<MainWindowPosition> = CloneSlot::empty();
|
||||
let app_startup_time = AppStartupTime::new();
|
||||
|
||||
// Emit debugging information to the logs (do not remove this!)
|
||||
info!("git commit id: {:?}", build_metadata::git_commit_id()
|
||||
.unwrap_or_else(|| "unknown"));
|
||||
info!("git commit timestamp: {:?}", build_metadata::git_commit_timestamp()
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string()));
|
||||
info!("build timestamp: {:?}", build_metadata::build_timestamp()
|
||||
.to_string());
|
||||
|
||||
loop {
|
||||
for (window_name, window) in app.windows() {
|
||||
if window_name == MAIN_WINDOW_NAME {
|
||||
|
||||
@@ -6,7 +6,6 @@ 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::enqueue::object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_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;
|
||||
@@ -162,7 +161,6 @@ pub fn run() {
|
||||
.manage(storyteller_creds_manager_3)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
check_sora_session_command,
|
||||
enqueue_image_to_3d_object_command,
|
||||
enqueue_text_to_image_command,
|
||||
fal_background_removal_command,
|
||||
fal_hunyuan_image_to_3d_command,
|
||||
|
||||
@@ -24,7 +24,7 @@ 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::queue::remove_background_rembg::remove_background_rembg;
|
||||
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};
|
||||
@@ -104,13 +104,6 @@ pub async fn fal_background_removal_command(
|
||||
|
||||
if !has_credentials {
|
||||
warn!("No API key found");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::RemoveBackground);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(CommandErrorResponseWrapper {
|
||||
status: CommandErrorStatus::Unauthorized,
|
||||
error_message: Some("You need to set a FAL api key".to_string()),
|
||||
|
||||
+3
-1
@@ -24,7 +24,8 @@ 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::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
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};
|
||||
@@ -196,6 +197,7 @@ pub async fn image_to_3d(
|
||||
) -> Result<(), InnerError> {
|
||||
|
||||
let api_key = fal_creds_manager.get_key_required()?;
|
||||
let creds = storyteller_creds_manager.get_credentials_required()?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
|
||||
+2
-1
@@ -24,7 +24,8 @@ 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::queue::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 fal_client::requests::video_gen::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use openai_sora_client::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
use openai_sora_client::requests::bearer::generate::generate_bearer_with_cookie;
|
||||
|
||||
/// Loading the JWT bearer token for the first time.
|
||||
pub async fn initialize_sora_jwt_bearer_token(app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
|
||||
@@ -14,5 +14,4 @@ pub fn add_artcraft_3d(cors: Cors, _is_production: bool) -> Cors {
|
||||
.allowed_origin("http://localhost:5173")
|
||||
.allowed_origin("http://localhost:5174") // If already started
|
||||
.allowed_origin("http://localhost:5175") // If already started
|
||||
.allowed_origin("https://macaroni-1.tailce84f.ts.net") // TODO
|
||||
}
|
||||
|
||||
@@ -17,21 +17,17 @@ path = "src/lib.rs"
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
file_to_base64_url = { workspace = true }
|
||||
file_to_base64_url = { path = "../../files/file_to_base64_url" }
|
||||
errors = { workspace = true }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
fal = { version = "0.3.6", features = ["endpoints"] }
|
||||
futures = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
# For now, we'll vendor in fal.
|
||||
# We need to implement webhooks and the upstream doesn't support them yet.
|
||||
#fal = { version = "0.3.6", features = ["endpoints"] }
|
||||
fal = { path = "../../../vendor/fal-rs", features = ["endpoints"] }
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../testing" }
|
||||
|
||||
@@ -15,8 +15,6 @@ pub enum FalErrorPlus {
|
||||
UrlParseError(url::ParseError),
|
||||
/// An endpoint we don't support yet.
|
||||
UnhandledEndpoint(String),
|
||||
/// Error from the `reqwest` crate.
|
||||
ReqwestError(reqwest::Error),
|
||||
}
|
||||
|
||||
impl Display for FalErrorPlus {
|
||||
@@ -27,7 +25,6 @@ impl Display for FalErrorPlus {
|
||||
Self::AnyhowError(err) => write!(f, "FalErrorPlus::AnyhowError: {:?}", err),
|
||||
Self::UrlParseError(err) => write!(f, "FalErrorPlus::UrlParseError: {:?}", err),
|
||||
Self::UnhandledEndpoint(endpoint) => write!(f, "FalErrorPlus::UnhandledEndpoint: {:?}", endpoint),
|
||||
Self::ReqwestError(err) => write!(f, "FalErrorPlus::ReqwestError: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +48,3 @@ impl From<url::ParseError> for FalErrorPlus {
|
||||
FalErrorPlus::UrlParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for FalErrorPlus {
|
||||
fn from(err: reqwest::Error) -> Self {
|
||||
FalErrorPlus::ReqwestError(err)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ pub async fn enqueue_hunyuan2_image_to_3d<P: AsRef<Path>>(args: Hunyuan2Args<'_,
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use crate::requests::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
+1
-1
@@ -42,7 +42,7 @@ pub async fn enqueue_flux_pro_ultra_text_to_image(args: FluxProUltraTextToImageA
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
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;
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ pub async fn enqueue_recraft3_text_to_image(args: Recraft3TextToImageArgs<'_>) -
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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;
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod queue;
|
||||
pub mod webhook;
|
||||
pub mod enqueue_hunyuan2_image_to_3d;
|
||||
pub mod remove_background_rembg;
|
||||
pub mod video_gen;
|
||||
pub mod image_gen;
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod enqueue_hunyuan2_image_to_3d;
|
||||
pub mod image_gen;
|
||||
pub mod remove_background_rembg;
|
||||
pub mod video_gen;
|
||||
+6
-1
@@ -9,6 +9,11 @@ use futures::StreamExt;
|
||||
use std::path::Path;
|
||||
use url::Url;
|
||||
|
||||
pub struct RemBgArgs<'a, P: AsRef<Path>> {
|
||||
pub image_path: P,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
pub struct RemBgResponse {
|
||||
pub image_url: Url,
|
||||
}
|
||||
@@ -54,7 +59,7 @@ pub async fn remove_background_rembg<P: AsRef<Path>>(image_path: P, api_key: &Fa
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::remove_background_rembg::remove_background_rembg;
|
||||
use crate::requests::remove_background_rembg::remove_background_rembg;
|
||||
use errors::AnyhowResult;
|
||||
use testing::test_file_path::test_file_path;
|
||||
|
||||
+1
-1
@@ -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::queue::video_gen::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;
|
||||
+2
-1
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -40,10 +41,10 @@ pub async fn enqueue_minimax_01_image_to_video<P: AsRef<Path>>(args: Minimax01Ar
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::video_gen::enqueue_minimax_01_image_to_video::{enqueue_minimax_01_image_to_video, Minimax01Args};
|
||||
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]
|
||||
@@ -1 +0,0 @@
|
||||
pub mod remove_background_rembg_webhook;
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct RemoveBackgroundRembgWebhookArgs<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub api_key: &'a FalApiKey
|
||||
}
|
||||
|
||||
pub async fn remove_background_rembg_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: RemoveBackgroundRembgWebhookArgs<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let image_url = args.image_url.into_url()?;
|
||||
|
||||
let request = RemoveBackgroundInput {
|
||||
image_url: image_url.to_string(),
|
||||
crop_to_bbox: None,
|
||||
sync_mode: None
|
||||
};
|
||||
|
||||
let result = rembg(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::image::remove_background_rembg_webhook::remove_background_rembg_webhook;
|
||||
use crate::requests::webhook::image::remove_background_rembg_webhook::RemoveBackgroundRembgWebhookArgs;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // NB: Manually test, don't run in CI!
|
||||
async fn test_rembg_request() -> anyhow::Result<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&api_key);
|
||||
|
||||
let args = RemoveBackgroundRembgWebhookArgs {
|
||||
image_url: "https://cdn-2.fakeyou.com/media/b/2/p/b/0/b2pb09bcbxdqjzgbnsapvfs1t897wdnx/image_b2pb09bcbxdqjzgbnsapvfs1t897wdnx.png",
|
||||
webhook_url: "https://api.storyteller.ai/webhook",
|
||||
api_key: &api_key,
|
||||
};
|
||||
|
||||
let response = remove_background_rembg_webhook(args).await?;
|
||||
|
||||
println!("{:?}", response);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod video;
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use fal::endpoints::fal_ai::hunyuan3d::v2::{v2, Hunyuan3DInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Hunyuan2Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling16Duration {
|
||||
Default,
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
pub async fn enqueue_hunyuan2_image_to_3d_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Hunyuan2Args<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let image_url = args.image_url.as_str().to_string();
|
||||
|
||||
let request = Hunyuan3DInput {
|
||||
input_image_url: image_url,
|
||||
textured_mesh: Some(true),
|
||||
guidance_scale: None,
|
||||
num_inference_steps: None,
|
||||
octree_resolution: None,
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = v2(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod enqueue_hunyuan2_image_to_3d_webhook;
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use fal::endpoints::fal_ai::kling_video::v1_6::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Kling16Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub duration: Kling16Duration,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling16Duration {
|
||||
Default,
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_16_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Kling16Args<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
let duration = match args.duration {
|
||||
Kling16Duration::Default => None,
|
||||
Kling16Duration::FiveSeconds => Some("5".to_string()), // Gross...
|
||||
Kling16Duration::TenSeconds => Some("10".to_string()),
|
||||
};
|
||||
|
||||
let image_url = args.image_url.as_str().to_string();
|
||||
|
||||
let request = ProImageToVideoRequest {
|
||||
image_url,
|
||||
prompt: args.prompt.to_string(),
|
||||
aspect_ratio: None,
|
||||
cfg_scale: None,
|
||||
duration,
|
||||
negative_prompt: None,
|
||||
tail_image_url: None,
|
||||
};
|
||||
|
||||
let result = image_to_video(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::enqueue_kling_16_image_to_video_webhook::{enqueue_kling_16_image_to_video_webhook, Kling16Args, Kling16Duration};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_kling16_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 = Kling16Args {
|
||||
image_path: image,
|
||||
prompt: "a corgi looks out over the water",
|
||||
api_key: &api_key,
|
||||
duration: Kling16Duration::Default,
|
||||
};
|
||||
|
||||
let result = enqueue_kling_16_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1 +0,0 @@
|
||||
pub mod enqueue_kling_16_image_to_video_webhook;
|
||||
+1
-1
@@ -6,7 +6,7 @@ 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_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
use crate::requests::bearer::generate::generate_bearer_with_cookie;
|
||||
use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
|
||||
use crate::requests::image_gen::{image_gen_http_request, SoraImageGenError};
|
||||
use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
|
||||
use crate::creds::sora_sentinel::SoraSentinel;
|
||||
use crate::recipes::maybe_refresh_credentials_on_sora_error::maybe_refresh_credentials_on_sora_error;
|
||||
use crate::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
use crate::requests::bearer::generate::generate_bearer_with_cookie;
|
||||
use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
|
||||
use crate::requests::image_gen::image_gen_http_request;
|
||||
use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
|
||||
use crate::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
use crate::requests::bearer::generate::generate_bearer_with_cookie;
|
||||
use crate::sora_error::SoraError;
|
||||
use anyhow::anyhow;
|
||||
use log::{error, info, warn};
|
||||
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
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_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
use crate::requests::bearer::generate::generate_bearer_with_cookie;
|
||||
use crate::requests::sentinel_refresh::generate::token::generate_token;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use errors::AnyhowResult;
|
||||
@@ -34,6 +34,8 @@ pub async fn maybe_upgrade_or_renew_session(sora_credentials: &mut SoraCredentia
|
||||
|
||||
if refresh_jwt {
|
||||
let cookies = sora_credentials.cookies.as_str();
|
||||
|
||||
info!("Generating new JWT bearer token...");
|
||||
let token = generate_bearer_with_cookie(cookies).await?;
|
||||
|
||||
info!("Parsing JWT bearer token.");
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ 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_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
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};
|
||||
|
||||
+8
-17
@@ -1,7 +1,5 @@
|
||||
use crate::requests::image_gen::SoraImageGenError;
|
||||
use crate::sora_error::SoraError;
|
||||
use crate::utils::classify_general_http_error::classify_general_http_error;
|
||||
use crate::utils::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
|
||||
use errors::AnyhowResult;
|
||||
use log::{debug, error, info};
|
||||
use reqwest::Client;
|
||||
@@ -36,8 +34,6 @@ pub async fn generate_bearer_with_cookie(cookie: &str) -> Result<String, SoraErr
|
||||
.gzip(true)
|
||||
.build()?;
|
||||
|
||||
info!("Generating new JWT bearer token from session cookies... (cookie payload length: {})", cookie.len());
|
||||
|
||||
let response = client.get(SORA_BEARER_GENERATE_URL)
|
||||
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0")
|
||||
.header("Accept", "*/*")
|
||||
@@ -47,25 +43,20 @@ pub async fn generate_bearer_with_cookie(cookie: &str) -> Result<String, SoraErr
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|err| {
|
||||
error!("Error reading body while attempting to generate bearer token: {:?}", err);
|
||||
SoraError::ReqwestError(err)
|
||||
})?;
|
||||
|
||||
if !status.is_success() {
|
||||
error!("Failed to generate bearer token with session cookies: {} ; body = {}", status, response_body);
|
||||
let error = classify_general_http_status_code_and_body(status, &response_body).await;
|
||||
if !response.status().is_success() {
|
||||
error!("Failed to generate bearer token: {}", response.status());
|
||||
let error = classify_general_http_error(response).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
debug!("Bearer token generation response was 200.");
|
||||
|
||||
let response_body = response.text().await?;
|
||||
|
||||
debug!("Auth Response: {}", response_body);
|
||||
|
||||
if response_body == "null" {
|
||||
error!("Failed to generate bearer token. Response was the string `null`.");
|
||||
if &response_body == "null" {
|
||||
error!("Failed to generate bearer token. Response was `null`.");
|
||||
return Err(SoraError::FailedToGenerateBearer)
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
pub mod generate_bearer_with_cookie;
|
||||
pub mod generate;
|
||||
+4
-6
@@ -6,10 +6,9 @@ use reqwest::Url;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::{fs::File, io::Write, path::Path};
|
||||
use log::{debug, error, warn};
|
||||
use log::{debug, warn};
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
|
||||
use crate::utils::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
|
||||
|
||||
const SORA_STATUS_URL: &str = "https://sora.com/backend/video_gen";
|
||||
|
||||
@@ -242,10 +241,9 @@ pub async fn get_image_gen_status(status_request: &StatusRequest, credentials: &
|
||||
|
||||
debug!("response_body: {}", response_body);
|
||||
|
||||
if !status.is_success() {
|
||||
error!("The sora status request failed; status = {:?} ; response body = {}", status, response_body);
|
||||
let error = classify_general_http_status_code_and_body(status, &response_body).await;
|
||||
return Err(error);
|
||||
if status != reqwest::StatusCode::OK {
|
||||
// TODO: Handle non-200 status codes, esp. JWT errors and credential errors
|
||||
return Err(SoraError::AnyhowError(anyhow::anyhow!("The physical status request failed; status = {:?}, message = {:?}", status, response_body)));
|
||||
}
|
||||
|
||||
let maybe_response = serde_json::from_str::<VideoGenStatusResponse>(response_body);
|
||||
|
||||
@@ -43,10 +43,6 @@ pub enum SoraError {
|
||||
/// We preserve the message for debugging.
|
||||
BadGateway(String),
|
||||
|
||||
/// HTTP 403 + Cloudflare Challenge Interstitial
|
||||
/// Cloudflare decided to block the request and present a challenge.
|
||||
CloudFlareChallenge,
|
||||
|
||||
/// HTTP 524 (Cloudflare-specific)
|
||||
/// Cloudflare formed a TCP connection to Sora, but no payload was delivered before timeout
|
||||
/// We preserve the message for debugging
|
||||
@@ -98,9 +94,6 @@ impl Display for SoraError {
|
||||
Self::BadGateway(message) => {
|
||||
write!(f, "Bad Gateway; Sora is likely having issues: {:?}", message)
|
||||
}
|
||||
Self::CloudFlareChallenge => {
|
||||
write!(f, "Cloudflare Challenge (403); Cloudflare wants to verify the request with a CAPTCHA challenge.")
|
||||
}
|
||||
Self::CloudFlareTimeout(message) => {
|
||||
write!(f, "Cloudflare Timeout (524); Sora is likely having issues: {:?}", message)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::sora_error::SoraError;
|
||||
use crate::utils::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
|
||||
|
||||
/// This assumes the request failed and returned a non-200.
|
||||
/// The caller should check.
|
||||
@@ -10,5 +9,67 @@ pub async fn classify_general_http_error(response: reqwest::Response) -> SoraErr
|
||||
Err(err) => return SoraError::ReqwestError(err),
|
||||
};
|
||||
|
||||
classify_general_http_status_code_and_body(status, &message).await
|
||||
/*
|
||||
|
||||
Old cookie -
|
||||
|
||||
Message: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
|
||||
No cookie -
|
||||
|
||||
Message: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: I *think* this is just the bearer token, not the cookie.
|
||||
let cookie_expired =
|
||||
message.contains("Your authentication token has expired. Please try signing in again.")
|
||||
|| message.contains("token_expired");
|
||||
|
||||
if cookie_expired {
|
||||
return SoraError::UnauthorizedCookieOrBearerExpired;
|
||||
}
|
||||
|
||||
let needs_onboarding =
|
||||
message.contains("You must onboard before using this service")
|
||||
|| message.contains("onboarding_required");
|
||||
|
||||
if needs_onboarding {
|
||||
return SoraError::SoraUsernameNotYetCreated;
|
||||
}
|
||||
|
||||
let status_code = status.as_u16();
|
||||
|
||||
match status_code {
|
||||
502 => {
|
||||
return SoraError::BadGateway(message);
|
||||
}
|
||||
504 => {
|
||||
if message.contains("cloudflare") || message.contains("Cloudflare") {
|
||||
return SoraError::CloudFlareTimeout(message);
|
||||
}
|
||||
}
|
||||
524 => {
|
||||
// NB: 524 is a cloudflare specific gateway timeout error with slightly different semantics than 504.
|
||||
if message.contains("cloudflare") || message.contains("Cloudflare") {
|
||||
return SoraError::CloudFlareTimeout(message);
|
||||
}
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
SoraError::OtherBadStatus(anyhow::anyhow!("Upload failed with status {}: {}", status, message))
|
||||
}
|
||||
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
use crate::sora_error::SoraError;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
/*
|
||||
|
||||
Old cookie -
|
||||
|
||||
Message: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
|
||||
No cookie -
|
||||
|
||||
Message: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/// Classify the type of error that occurred.
|
||||
/// This first assumes the request failed and returned a non-200.
|
||||
pub async fn classify_general_http_status_code_and_body(status: StatusCode, response_body: &str) -> SoraError {
|
||||
let message = response_body.to_string();
|
||||
|
||||
// TODO: I *think* this is just the bearer token, not the cookie.
|
||||
let cookie_expired =
|
||||
message.contains("Your authentication token has expired. Please try signing in again.")
|
||||
|| message.contains("token_expired");
|
||||
|
||||
if cookie_expired {
|
||||
return SoraError::UnauthorizedCookieOrBearerExpired;
|
||||
}
|
||||
|
||||
let needs_onboarding =
|
||||
message.contains("You must onboard before using this service")
|
||||
|| message.contains("onboarding_required");
|
||||
|
||||
if needs_onboarding {
|
||||
return SoraError::SoraUsernameNotYetCreated;
|
||||
}
|
||||
|
||||
let status_code = status.as_u16();
|
||||
|
||||
match status_code {
|
||||
403 => {
|
||||
if message.contains("challenge-platform")
|
||||
|| message.contains("challenge-error-text")
|
||||
|| message.contains("cType: 'managed'")
|
||||
|| message.contains("Just a moment...") {
|
||||
return SoraError::CloudFlareChallenge;
|
||||
}
|
||||
}
|
||||
502 => {
|
||||
return SoraError::BadGateway(message);
|
||||
}
|
||||
504 => {
|
||||
if message.contains("cloudflare")
|
||||
|| message.contains("Cloudflare") {
|
||||
return SoraError::CloudFlareTimeout(message);
|
||||
}
|
||||
}
|
||||
524 => {
|
||||
// NB: 524 is a cloudflare specific gateway timeout error with slightly different semantics than 504.
|
||||
if message.contains("cloudflare")
|
||||
|| message.contains("Cloudflare") {
|
||||
return SoraError::CloudFlareTimeout(message);
|
||||
}
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
SoraError::OtherBadStatus(anyhow::anyhow!("Upload failed with status {}: {}", status, message))
|
||||
}
|
||||
@@ -54,7 +54,7 @@ pub fn has_session_cookie(cookies_header: &str) -> AnyhowResult<SessionCookiePre
|
||||
let cookie_name = cookie.name();
|
||||
if cookie_name == SESSION_COOKIE_NAME_NEW {
|
||||
return Ok(SessionCookiePresence::Present);
|
||||
} else if cookie_name == SESSION_COOKIE_NAME_OLD {
|
||||
} else if cookie_name == SESSION_COOKIE_NAME_NEW {
|
||||
return Ok(SessionCookiePresence::Present);
|
||||
} else if !IRRELEVANT_COOKIE_NAMES.contains(cookie_name) {
|
||||
unknown_cookie_count += 1;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod classify_general_http_error;
|
||||
pub mod classify_general_http_status_code_and_body;
|
||||
pub mod has_session_cookie;
|
||||
pub mod lightweight_sora_jwt_parse;
|
||||
@@ -1,45 +0,0 @@
|
||||
[package]
|
||||
name = "openart_client"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
name = "openart_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
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 }
|
||||
reqwest = { workspace = true, features = ["multipart", "gzip"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha3 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
toml = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../testing" }
|
||||
filesys = { path = "../../files/filesys" }
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod openart_cookies;
|
||||
pub mod openart_credentials;
|
||||
pub mod openart_session_info;
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
/// The cookies payload is the entire cookie header (potentially many cookies).
|
||||
#[derive(Clone)]
|
||||
pub struct OpenArtCookies {
|
||||
cookies: String,
|
||||
}
|
||||
|
||||
impl OpenArtCookies {
|
||||
pub fn new(cookies: String) -> Self {
|
||||
OpenArtCookies { cookies }
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.cookies
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.cookies.as_bytes()
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.cookies.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use crate::creds::openart_cookies::OpenArtCookies;
|
||||
use crate::creds::openart_session_info::OpenArtSessionInfo;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenArtCredentials {
|
||||
pub cookies: Option<OpenArtCookies>,
|
||||
pub session_info: Option<OpenArtSessionInfo>,
|
||||
}
|
||||
|
||||
impl OpenArtCredentials {
|
||||
pub fn from_cookies(cookies: &str) -> Self {
|
||||
let cookies = OpenArtCookies::new(cookies.to_string());
|
||||
Self {
|
||||
cookies: Some(cookies),
|
||||
session_info: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpenArtSessionInfo {
|
||||
/// This is either a session ID, user ID, or subscription ID.
|
||||
/// It is passed as the header `X-USER-ID` in other requests.
|
||||
pub sub: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub image: Option<String>,
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use reqwest::StatusCode;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
/// Session endpoint did not return a valid session id.
|
||||
/// Specifically for calls to https://openart.ai/api/auth/session
|
||||
InvalidSession,
|
||||
CouldNotParseSession { error: serde_json::Error, body: String },
|
||||
|
||||
/// An error that occurred as or after the request was sent.
|
||||
ReqwestError(reqwest::Error),
|
||||
|
||||
/// Some other failure response.
|
||||
UncategorizedBadResponse{
|
||||
status_code: StatusCode,
|
||||
message : String,
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApiError::InvalidSession => write!(f, "Invalid session: no session id returned from the server"),
|
||||
ApiError::CouldNotParseSession { error, body } => {
|
||||
write!(f, "Could not parse session: {}. Body: {}", error, body)
|
||||
}
|
||||
ApiError::ReqwestError(err) => write!(f, "ReqwestError: {}", err),
|
||||
ApiError::UncategorizedBadResponse { status_code, message } => {
|
||||
write!(f, "UncategorizedBadResponse: {:?} : {}", status_code, message)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ApiError {}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::classify_http_error_status_code_and_body::classify_http_error_status_code_and_body;
|
||||
use crate::error::openart_error::OpenArtError;
|
||||
|
||||
/// This assumes the request failed and returned a non-200.
|
||||
/// The caller should check.
|
||||
pub async fn classify_http_error_response(response: reqwest::Response) -> OpenArtError {
|
||||
let status = response.status();
|
||||
let message = match response.text().await {
|
||||
Ok(text) => text,
|
||||
Err(err) => return OpenArtError::Api(ApiError::ReqwestError(err)),
|
||||
};
|
||||
|
||||
classify_http_error_status_code_and_body(status, &message).await
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::openart_error::OpenArtError;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
/// This assumes the request failed and returned a non-200.
|
||||
/// The caller should check.
|
||||
pub async fn classify_http_error_status_code_and_body(status: StatusCode, body: &str) -> OpenArtError {
|
||||
|
||||
let message = body.to_string();
|
||||
|
||||
// TODO: Handle various error messages from the API.
|
||||
|
||||
OpenArtError::Api(ApiError::UncategorizedBadResponse { status_code: status, message })
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientError {
|
||||
/// No cookies in credentials
|
||||
NoCookiesInCredentials,
|
||||
|
||||
/// No session in the credentials
|
||||
NoSessionInfoInCredentials,
|
||||
|
||||
/// An error that occurred clientside before the request was sent.
|
||||
ReqwestError(reqwest::Error),
|
||||
|
||||
/// An I/O error, e.g. reading from a file or network stream.
|
||||
IoError(std::io::Error),
|
||||
|
||||
/// Another error that does not fit the above categories.
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Display for ClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NoCookiesInCredentials => write!(f, "No cookies are present in the credentials"),
|
||||
Self::NoSessionInfoInCredentials => write!(f, "No session in the credentials"),
|
||||
Self::ReqwestError(err) => write!(f, "Reqwest error: {}", err),
|
||||
Self::IoError(err) => write!(f, "I/O error: {}", err),
|
||||
Self::Other(message) => write!(f, "Other client error: {}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ClientError {}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod api_error;
|
||||
pub mod classify_http_error_response;
|
||||
pub mod client_error;
|
||||
pub mod openart_error;
|
||||
pub mod classify_http_error_status_code_and_body;
|
||||
@@ -1,33 +0,0 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpenArtError {
|
||||
Api(ApiError),
|
||||
Client(ClientError),
|
||||
}
|
||||
|
||||
impl Display for OpenArtError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Api(err) => write!(f, "ApiError: {}", err),
|
||||
Self::Client(err) => write!(f, "ClientError: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for OpenArtError {}
|
||||
|
||||
impl From<ApiError> for OpenArtError {
|
||||
fn from(err: ApiError) -> Self {
|
||||
OpenArtError::Api(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientError> for OpenArtError {
|
||||
fn from(err: ClientError) -> Self {
|
||||
OpenArtError::Client(err)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod requests;
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
curl 'https://openart.ai/api/auth/session' --compressed
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0'
|
||||
-H 'Accept: * / *'
|
||||
-H 'Accept-Language: en-US,en;q=0.5'
|
||||
-H 'Accept-Encoding: gzip, deflate, br, zstd'
|
||||
-H 'Referer: https://openart.ai/create?mode=edit&imageAction=changeBackground&action_mode=background_manual_remove'
|
||||
-H 'Content-Type: application/json'
|
||||
-H 'Connection: keep-alive'
|
||||
-H 'Cookie: AMP_3e2fda7a5c=JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJmZmRkZTQ2MS1jMmVlLTRmNmUtYmQwMi0zNDcwYjY1OWNkMTklMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjI2YWJ1ejg1Y1JvMWlWQW1rNlJqZiUyMiUyQyUyMnNlc3Npb25JZCUyMiUzQTE3NDg1ODQyODY0NzclMkMlMjJvcHRPdXQlMjIlM0FmYWxzZSUyQyUyMmxhc3RFdmVudFRpbWUlMjIlM0ExNzQ4NTg0NTU5NzEyJTJDJTIybGFzdEV2ZW50SWQlMjIlM0EzNjglMkMlMjJwYWdlQ291bnRlciUyMiUzQTklN0Q=; AMP_MKTG_3e2fda7a5c=JTdCJTIycmVmZXJyZXIlMjIlM0ElMjJodHRwcyUzQSUyRiUyRnd3dy5nb29nbGUuY29tJTJGJTIyJTJDJTIycmVmZXJyaW5nX2RvbWFpbiUyMiUzQSUyMnd3dy5nb29nbGUuY29tJTIyJTdE; __client_uat=0; __client_uat_Xt7g_-Hi=0; utm_params={%22utm_source%22:%22organic%22%2C%22utm_medium%22:%22Google%22%2C%22utm_campaign%22:%22oa_unknown%22%2C%22utm_term%22:%22oa_unknown%22%2C%22utm_content%22:%22oa_unknown%22}; themeMode=dark; themeDirection=ltr; themeColorPresets=default; themeLayout=horizontal; themeContrast=default; themeStretch=false; unique_device_id=f1f0b393-815f-4f95-aaa2-65a3b94530f6; __Host-next-auth.csrf-token=ea06d49af6d70cbe0d15774565708d6c394790c5ab2610276bb0bf29a4afb47b%7Cbdbf15d7f2bf44c83586c7ab1ec47ea6c114b26ccc8e20ae45f40f06c0377389; __Secure-next-auth.callback-url=https%3A%2F%2Fopenart.ai%2Fcreate; __Secure-next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..mzdySrH34fIj41NP.ky2ZVPIugA1EWSKL29EEEKvSxfG4LCo-R7rN-yLzLo-2LmCrFzVe15BCH2MYg90cwkdIdm1Hi-7U4BcnxG0x662UrU9RDw2yX_ZTZge6Kz70-pg1TaVvKWOS_Gibv8ERSK6MHTfqlx4WNvHccOOfDIWhN87zbLXHCbWnexgmBOB3XfMA96Hby55JNgDM3-_JPcg1lFNkT8oAW562FUZxM9EMzKD4-A4Ee1ZpVd0Z51k4lqfS4XED-0xT6xagsCd6CdwEHEHc0paIAj34Kb_lCO2nyrxGBYvx1XlVHjvXdtakrTqe6jrOuV5rQ0iEO6Xk6cibcZydMV4GssKPEwUDS718AJCxNUK9yHi4RRyfYus.bhvOrrfLdszMcHfJ3Ex2wA'
|
||||
-H 'Sec-Fetch-Dest: empty'
|
||||
-H 'Sec-Fetch-Mode: cors'
|
||||
-H 'Sec-Fetch-Site: same-origin'
|
||||
-H 'Priority: u=4'
|
||||
-H 'Pragma: no-cache'
|
||||
-H 'Cache-Control: no-cache'
|
||||
-H 'TE: trailers'
|
||||
*/
|
||||
|
||||
use crate::creds::openart_credentials::OpenArtCredentials;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::classify_http_error_status_code_and_body::classify_http_error_status_code_and_body;
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::openart_error::OpenArtError;
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::{debug, error, info};
|
||||
use reqwest::Client;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
const SESSION_URL : &str = "https://openart.ai/api/auth/session";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionDetails {
|
||||
/// This is either a session ID, user ID, or subscription ID.
|
||||
/// It is passed as the header `X-USER-ID` in other requests.
|
||||
pub sub: Option<String>,
|
||||
|
||||
pub email: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub image: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct RawSession {
|
||||
user: Option<RawUser>,
|
||||
expires: Option<DateTime<Utc>>,
|
||||
/// This is either a session ID, user ID, or subscription ID.
|
||||
/// It is passed as the header `X-USER-ID` in other requests.
|
||||
sub: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct RawUser {
|
||||
name: Option<String>,
|
||||
email: Option<String>,
|
||||
image: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_session_request(creds: &OpenArtCredentials) -> Result<SessionDetails, OpenArtError> {
|
||||
|
||||
let cookies = match creds.cookies.as_ref() {
|
||||
Some(cookies) => cookies,
|
||||
None => {
|
||||
error!("Failed to request session. No cookies in credentials.");
|
||||
return Err(ClientError::NoCookiesInCredentials.into());
|
||||
}
|
||||
};
|
||||
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
error!("Failed to create HTTP client: {}", err);
|
||||
OpenArtError::Client(ClientError::ReqwestError(err))
|
||||
})?;
|
||||
|
||||
info!("Getting session details from cookies... (cookie payload length: {})", cookies.as_str().len());
|
||||
|
||||
let mut http_request= client.get(SESSION_URL)
|
||||
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0")
|
||||
.header("Accept", "*/*")
|
||||
.header("Accept-Encoding", "gzip, deflate, br")
|
||||
.header("Accept-Language", "en-US,en;q=0.5")
|
||||
.header("Cookie", cookies.as_str());
|
||||
|
||||
let http_request = http_request.build()
|
||||
.map_err(|err| ApiError::ReqwestError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| ApiError::ReqwestError(err))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error reading body while attempting to read session details: {:?}", err);
|
||||
ApiError::ReqwestError(err)
|
||||
})?;
|
||||
|
||||
if !status.is_success() {
|
||||
error!("Failed to get session details: {} ; body = {}", status, response_body);
|
||||
let error = classify_http_error_status_code_and_body(status, &response_body).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
debug!("Session info response was 200. Body: {}", response_body);
|
||||
|
||||
if response_body.is_empty() || response_body == "{}" {
|
||||
error!("Received empty response body when requesting session details.");
|
||||
return Err(OpenArtError::Api(ApiError::InvalidSession));
|
||||
}
|
||||
|
||||
let session : RawSession = serde_json::from_str(response_body)
|
||||
.map_err(|err| {
|
||||
error!("Failed to parse session details: {} body: {}", err, response_body);
|
||||
OpenArtError::Api(ApiError::CouldNotParseSession { error: err, body: response_body.to_string() } )
|
||||
})?;
|
||||
|
||||
Ok(SessionDetails {
|
||||
sub: session.sub,
|
||||
email: session.user.as_ref().and_then(|u| u.email.clone()),
|
||||
name: session.user.as_ref().and_then(|u| u.name.clone()),
|
||||
image: session.user.as_ref().and_then(|u| u.image.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::creds::openart_cookies::OpenArtCookies;
|
||||
use crate::creds::openart_credentials::OpenArtCredentials;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Do not run in CI. Run manully to test session retrieval.
|
||||
async fn invalid_session() {
|
||||
let creds = OpenArtCredentials {
|
||||
cookies: Some(OpenArtCookies::new("".to_string())),
|
||||
session_info: None,
|
||||
};
|
||||
|
||||
let result = get_session_request(&creds).await;
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Do not run in CI. Run manully to test session retrieval.
|
||||
async fn valid_session() {
|
||||
let cookie = "AMP_3e2fda7a5c=JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJmZmRkZTQ2MS1jMmVlLTRmNmUtYmQwMi0zNDcwYjY1OWNkMTklMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjI2YWJ1ejg1Y1JvMWlWQW1rNlJqZiUyMiUyQyUyMnNlc3Npb25JZCUyMiUzQTE3NDg1ODQyODY0NzclMkMlMjJvcHRPdXQlMjIlM0FmYWxzZSUyQyUyMmxhc3RFdmVudFRpbWUlMjIlM0ExNzQ4NTg0NTU5NzEyJTJDJTIybGFzdEV2ZW50SWQlMjIlM0EzNjglMkMlMjJwYWdlQ291bnRlciUyMiUzQTklN0Q=; AMP_MKTG_3e2fda7a5c=JTdCJTIycmVmZXJyZXIlMjIlM0ElMjJodHRwcyUzQSUyRiUyRnd3dy5nb29nbGUuY29tJTJGJTIyJTJDJTIycmVmZXJyaW5nX2RvbWFpbiUyMiUzQSUyMnd3dy5nb29nbGUuY29tJTIyJTdE; __client_uat=0; __client_uat_Xt7g_-Hi=0; utm_params={%22utm_source%22:%22organic%22%2C%22utm_medium%22:%22Google%22%2C%22utm_campaign%22:%22oa_unknown%22%2C%22utm_term%22:%22oa_unknown%22%2C%22utm_content%22:%22oa_unknown%22}; themeMode=dark; themeDirection=ltr; themeColorPresets=default; themeLayout=horizontal; themeContrast=default; themeStretch=false; unique_device_id=f1f0b393-815f-4f95-aaa2-65a3b94530f6; __Host-next-auth.csrf-token=ea06d49af6d70cbe0d15774565708d6c394790c5ab2610276bb0bf29a4afb47b%7Cbdbf15d7f2bf44c83586c7ab1ec47ea6c114b26ccc8e20ae45f40f06c0377389; __Secure-next-auth.callback-url=https%3A%2F%2Fopenart.ai%2Fcreate; __Secure-next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..mzdySrH34fIj41NP.ky2ZVPIugA1EWSKL29EEEKvSxfG4LCo-R7rN-yLzLo-2LmCrFzVe15BCH2MYg90cwkdIdm1Hi-7U4BcnxG0x662UrU9RDw2yX_ZTZge6Kz70-pg1TaVvKWOS_Gibv8ERSK6MHTfqlx4WNvHccOOfDIWhN87zbLXHCbWnexgmBOB3XfMA96Hby55JNgDM3-_JPcg1lFNkT8oAW562FUZxM9EMzKD4-A4Ee1ZpVd0Z51k4lqfS4XED-0xT6xagsCd6CdwEHEHc0paIAj34Kb_lCO2nyrxGBYvx1XlVHjvXdtakrTqe6jrOuV5rQ0iEO6Xk6cibcZydMV4GssKPEwUDS718AJCxNUK9yHi4RRyfYus.bhvOrrfLdszMcHfJ3Ex2wA";
|
||||
let creds = OpenArtCredentials {
|
||||
cookies: Some(OpenArtCookies::new(cookie.to_string())),
|
||||
session_info: None,
|
||||
};
|
||||
|
||||
let result = get_session_request(&creds).await;
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod get_session_request;
|
||||
pub mod upload_image_from_file_request;
|
||||
@@ -1,174 +0,0 @@
|
||||
|
||||
/*
|
||||
curl 'https://openart.ai/api/media/upload_raw_image' --compressed
|
||||
-X POST
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0'
|
||||
-H 'Accept: application/json, text/plain, * / *'
|
||||
-H 'Accept-Language: en-US,en;q=0.5'
|
||||
-H 'Accept-Encoding: gzip, deflate, br, zstd'
|
||||
-H 'X-USER-ID: 6abuz85cRo1iVAmk6Rjf'
|
||||
-H 'Content-Type: multipart/form-data; boundary=----geckoformboundary1bbe4d86f5d33a00827b7d293784b7ec'
|
||||
-H 'Origin: https://openart.ai'
|
||||
-H 'Connection: keep-alive'
|
||||
-H 'Referer: https://openart.ai/create?mode=edit&imageAction=changeBackground&action_mode=background_manual_remove'
|
||||
-H 'Cookie: AMP_3e2fda7a5c=JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJmZmRkZTQ2MS1jMmVlLTRmNmUtYmQwMi0zNDcwYjY1OWNkMTklMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjI2YWJ1ejg1Y1JvMWlWQW1rNlJqZiUyMiUyQyUyMnNlc3Npb25JZCUyMiUzQTE3NDg2MTI1MjI4NjglMkMlMjJvcHRPdXQlMjIlM0FmYWxzZSUyQyUyMmxhc3RFdmVudFRpbWUlMjIlM0ExNzQ4NjEyNTIzMDI4JTJDJTIybGFzdEV2ZW50SWQlMjIlM0EzNzAlMkMlMjJwYWdlQ291bnRlciUyMiUzQTElN0Q=; AMP_MKTG_3e2fda7a5c=JTdCJTIycmVmZXJyZXIlMjIlM0ElMjJodHRwcyUzQSUyRiUyRnd3dy5nb29nbGUuY29tJTJGJTIyJTJDJTIycmVmZXJyaW5nX2RvbWFpbiUyMiUzQSUyMnd3dy5nb29nbGUuY29tJTIyJTdE; __client_uat=0; __client_uat_Xt7g_-Hi=0; utm_params={%22utm_source%22:%22organic%22%2C%22utm_medium%22:%22Google%22%2C%22utm_campaign%22:%22oa_unknown%22%2C%22utm_term%22:%22oa_unknown%22%2C%22utm_content%22:%22oa_unknown%22}; themeMode=dark; themeDirection=ltr; themeColorPresets=default; themeLayout=horizontal; themeContrast=default; themeStretch=false; unique_device_id=f1f0b393-815f-4f95-aaa2-65a3b94530f6; __Host-next-auth.csrf-token=ea06d49af6d70cbe0d15774565708d6c394790c5ab2610276bb0bf29a4afb47b%7Cbdbf15d7f2bf44c83586c7ab1ec47ea6c114b26ccc8e20ae45f40f06c0377389; __Secure-next-auth.callback-url=https%3A%2F%2Fopenart.ai%2Fcreate; __Secure-next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..3_1LkfPK4sWQd3GW.KZ7yqoe7fdPEDoTrekIxFIxs5hZVn9Rg8WfsS4tD4Ana6MQmn_LnFhvF-cKAd187Yh3ULWozsFpZAOIQbnndPPsZ5sYu5pYXbQypN5NGC_movTNVQOvzp6nv5r4l0Of1vMslik_wa-LPZZc-blTNGeW-E9Zy35IpK2gHruTAXV_HPfw9uA9OA_RlI3BDxveqnooBdJ1PiU7fJ_MQe6A8Q1XyJ1OtyatXlIBnvrCVuHDPS0ChZbnUA7OTD_d0O8OVyy5yAo-qGer9j_kLg8aC0e-96DUOHq7hwjQyfMDssPVfv1vuArwsZXUThfsb60DGYjDnTIsZlzru-eTghxjrJJuUo7PYRi1A71E4Nu69Ds0.dKUcrC_sRmBDIzp_lew8EQ'
|
||||
-H 'Sec-Fetch-Dest: empty'
|
||||
-H 'Sec-Fetch-Mode: cors'
|
||||
-H 'Sec-Fetch-Site: same-origin'
|
||||
-H 'Pragma: no-cache'
|
||||
-H 'Cache-Control: no-cache'
|
||||
-H 'TE: trailers' --data-binary $'------geckoformboundary1bbe4d86f5d33a00827b7d293784b7ec\r\n
|
||||
//
|
||||
// Content-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: image/png\r\n\r\n------geckoformboundary1bbe4d86f5d33a00827b7d293784b7ec--\r\n'
|
||||
|
||||
{"imageUrl":"https://cdn.openart.ai/production/2025-05/uploads/anonymous/image_A-0v2jOD_1536x1024_1748612534709.png","width":1536,"height":1024}
|
||||
*/
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct RawUploadResponse {
|
||||
#[serde(rename = "imageUrl")]
|
||||
image_url: Option<String>,
|
||||
width: Option<i64>,
|
||||
height: Option<i64>,
|
||||
}
|
||||
|
||||
use crate::creds::openart_credentials::OpenArtCredentials;
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::openart_error::OpenArtError;
|
||||
use log::info;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use reqwest::Client;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::classify_http_error_response::classify_http_error_response;
|
||||
|
||||
/// Try to prevent buffer reallocations.
|
||||
/// There's a better way to implement this.
|
||||
const INITIAL_BUFFER_SIZE : usize = 1024*1024;
|
||||
|
||||
const UPLOAD_URL: &str = "https://openart.ai/api/media/upload_raw_image";
|
||||
|
||||
pub async fn upload_image_from_file_request<P: AsRef<Path>>(
|
||||
file_path: P,
|
||||
creds: &OpenArtCredentials,
|
||||
maybe_timeout: Option<Duration>,
|
||||
) -> Result<(), OpenArtError> {
|
||||
|
||||
let filename = file_path.as_ref().file_name()
|
||||
.ok_or_else(|| OpenArtError::Client(ClientError::Other("Could not determine filename from path".to_string())))?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let mut file = File::open(&file_path)
|
||||
.await
|
||||
.map_err(|err| OpenArtError::Client(ClientError::IoError(err)))?;
|
||||
|
||||
let mut file_bytes = Vec::with_capacity(INITIAL_BUFFER_SIZE);
|
||||
|
||||
file.read_to_end(&mut file_bytes)
|
||||
.await
|
||||
.map_err(|err| OpenArtError::Client(ClientError::IoError(err)))?;
|
||||
|
||||
// TODO: Read file magic bytes first, then fall back to this.
|
||||
let mime_type = match file_path.as_ref().extension().and_then(|e| e.to_str()) {
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("png") => "image/png",
|
||||
// Some("webp") => "image/webp",
|
||||
// Some("gif") => "image/gif",
|
||||
// Some("mp4") => "video/mp4",
|
||||
// Some("mov") => "video/quicktime",
|
||||
// Some("webm") => "video/webm",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
// Create multipart form
|
||||
let part = Part::bytes(file_bytes) // NB: Reqwest needs to own the bytes.
|
||||
.file_name(filename) // NB: Reqwest needs to own the bytes
|
||||
.mime_str(mime_type)
|
||||
.map_err(|err| OpenArtError::Client(ClientError::ReqwestError(err)))?;
|
||||
|
||||
let form = Form::new().part("file", part);
|
||||
|
||||
let cookie = creds.cookies.as_ref()
|
||||
.map(|cookies| cookies.to_string())
|
||||
.ok_or_else(|| OpenArtError::Client(ClientError::NoCookiesInCredentials))?;
|
||||
|
||||
let session_id = creds.session_info.as_ref()
|
||||
.map(|info| info.sub.clone())
|
||||
.flatten()
|
||||
.ok_or_else(|| OpenArtError::Client(ClientError::NoSessionInfoInCredentials))?;
|
||||
|
||||
// Make API request
|
||||
let client = Client::new();
|
||||
let mut request_builder = client.post(UPLOAD_URL)
|
||||
.multipart(form)
|
||||
.header("X-USER-ID", session_id)
|
||||
.header("Cookie", &cookie);
|
||||
|
||||
if let Some(timeout) = maybe_timeout {
|
||||
request_builder = request_builder.timeout(timeout);
|
||||
}
|
||||
|
||||
let response = request_builder
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| OpenArtError::Api(ApiError::ReqwestError(err)))?;
|
||||
|
||||
// Check response status
|
||||
if !response.status().is_success() {
|
||||
info!("Error uploading image: {:?}", response.status());
|
||||
let error = classify_http_error_response(response).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
let upload_response = response.json::<RawUploadResponse>()
|
||||
.await
|
||||
.map_err(|err| OpenArtError::Api(ApiError::ReqwestError(err)))?;
|
||||
|
||||
println!("Uploaded data: {:?}", upload_response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use errors::AnyhowResult;
|
||||
use testing::test_file_path::test_file_path;
|
||||
use crate::creds::openart_cookies::OpenArtCookies;
|
||||
use crate::creds::openart_credentials::OpenArtCredentials;
|
||||
use crate::creds::openart_session_info::OpenArtSessionInfo;
|
||||
use crate::requests::get_session_request::{get_session_request, SessionDetails};
|
||||
use crate::requests::upload_image_from_file_request::upload_image_from_file_request;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Do not run in CI. Run manully to test session retrieval.
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie = "AMP_3e2fda7a5c=JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJmZmRkZTQ2MS1jMmVlLTRmNmUtYmQwMi0zNDcwYjY1OWNkMTklMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjI2YWJ1ejg1Y1JvMWlWQW1rNlJqZiUyMiUyQyUyMnNlc3Npb25JZCUyMiUzQTE3NDg1ODQyODY0NzclMkMlMjJvcHRPdXQlMjIlM0FmYWxzZSUyQyUyMmxhc3RFdmVudFRpbWUlMjIlM0ExNzQ4NTg0NTU5NzEyJTJDJTIybGFzdEV2ZW50SWQlMjIlM0EzNjglMkMlMjJwYWdlQ291bnRlciUyMiUzQTklN0Q=; AMP_MKTG_3e2fda7a5c=JTdCJTIycmVmZXJyZXIlMjIlM0ElMjJodHRwcyUzQSUyRiUyRnd3dy5nb29nbGUuY29tJTJGJTIyJTJDJTIycmVmZXJyaW5nX2RvbWFpbiUyMiUzQSUyMnd3dy5nb29nbGUuY29tJTIyJTdE; __client_uat=0; __client_uat_Xt7g_-Hi=0; utm_params={%22utm_source%22:%22organic%22%2C%22utm_medium%22:%22Google%22%2C%22utm_campaign%22:%22oa_unknown%22%2C%22utm_term%22:%22oa_unknown%22%2C%22utm_content%22:%22oa_unknown%22}; themeMode=dark; themeDirection=ltr; themeColorPresets=default; themeLayout=horizontal; themeContrast=default; themeStretch=false; unique_device_id=f1f0b393-815f-4f95-aaa2-65a3b94530f6; __Host-next-auth.csrf-token=ea06d49af6d70cbe0d15774565708d6c394790c5ab2610276bb0bf29a4afb47b%7Cbdbf15d7f2bf44c83586c7ab1ec47ea6c114b26ccc8e20ae45f40f06c0377389; __Secure-next-auth.callback-url=https%3A%2F%2Fopenart.ai%2Fcreate; __Secure-next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..mzdySrH34fIj41NP.ky2ZVPIugA1EWSKL29EEEKvSxfG4LCo-R7rN-yLzLo-2LmCrFzVe15BCH2MYg90cwkdIdm1Hi-7U4BcnxG0x662UrU9RDw2yX_ZTZge6Kz70-pg1TaVvKWOS_Gibv8ERSK6MHTfqlx4WNvHccOOfDIWhN87zbLXHCbWnexgmBOB3XfMA96Hby55JNgDM3-_JPcg1lFNkT8oAW562FUZxM9EMzKD4-A4Ee1ZpVd0Z51k4lqfS4XED-0xT6xagsCd6CdwEHEHc0paIAj34Kb_lCO2nyrxGBYvx1XlVHjvXdtakrTqe6jrOuV5rQ0iEO6Xk6cibcZydMV4GssKPEwUDS718AJCxNUK9yHi4RRyfYus.bhvOrrfLdszMcHfJ3Ex2wA";
|
||||
|
||||
let mut creds = OpenArtCredentials {
|
||||
cookies: Some(OpenArtCookies::new(cookie.to_string())),
|
||||
session_info: None,
|
||||
};
|
||||
|
||||
let session_details = get_session_request(&creds).await.unwrap();
|
||||
|
||||
creds.session_info = Some(OpenArtSessionInfo {
|
||||
sub: session_details.sub.clone(),
|
||||
email: None,
|
||||
name: None,
|
||||
image: None,
|
||||
});
|
||||
|
||||
let image_path = test_file_path("test_data/image/juno.jpg")?; // media_01jqyqgqpwf40tkcapq5bmaz5d
|
||||
|
||||
let result = upload_image_from_file_request(image_path, &creds, None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ path = "src/lib.rs"
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs = { workspace = true }
|
||||
enums = { workspace = true }
|
||||
enums_public = { workspace = true }
|
||||
errors = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use crate::error::api_error::ApiError;
|
||||
|
||||
/// An error arising from the client, either before or after the request is sent.
|
||||
#[derive(Debug)]
|
||||
@@ -15,12 +14,6 @@ pub enum ClientError {
|
||||
|
||||
/// An error doing file I/O (on our side)
|
||||
IoError(io::Error),
|
||||
|
||||
/// An error setting up the client
|
||||
ReqwestError(reqwest::Error),
|
||||
|
||||
/// A serialization error with the request.
|
||||
SerializationError(serde_json::Error),
|
||||
}
|
||||
|
||||
impl Error for ClientError {}
|
||||
@@ -31,8 +24,6 @@ impl Display for ClientError {
|
||||
Self::FileTypeNotKnown(path) => write!(f, "Could not determine the filetype for the file: {:?}", path),
|
||||
Self::FileTypeNotHandled(path) => write!(f, "Could not handle the file type for the file: {:?}", path),
|
||||
Self::IoError(err) => write!(f, "IO error: {}", err),
|
||||
Self::ReqwestError(err) => write!(f, "Reqwest client error: {}", err),
|
||||
Self::SerializationError(err) => write!(f, "Request serialization error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,15 +33,3 @@ impl From<io::Error> for ClientError {
|
||||
ClientError::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ClientError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
ClientError::SerializationError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for ClientError {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
ClientError::ReqwestError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod object;
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_image_to_3d::{GenerateHunyuan2ImageTo3dRequest, GenerateHunyuan2ImageTo3dResponse, GENERATE_HUNYUAN_2_IMAGE_TO_3D_URL_PATH};
|
||||
use log::{debug, info};
|
||||
use reqwest::Client;
|
||||
|
||||
/// Upload an image media file from a file.
|
||||
pub async fn generate_hunyuan2_image_to_3d(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateHunyuan2ImageTo3dRequest,
|
||||
) -> Result<GenerateHunyuan2ImageTo3dResponse, StorytellerError> {
|
||||
|
||||
let url = get_route(api_host);
|
||||
|
||||
info!("Requesting {:?}", &url);
|
||||
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
|
||||
|
||||
let mut request_builder = client.post(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", APPLICATION_JSON)
|
||||
.header("Content-Type", APPLICATION_JSON);
|
||||
|
||||
if let Some(creds) = maybe_creds {
|
||||
if let Some(header) = &creds.maybe_as_cookie_header() {
|
||||
request_builder = request_builder.header("Cookie", header);
|
||||
}
|
||||
}
|
||||
|
||||
let request_body = serde_json::to_string(&request)
|
||||
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
|
||||
|
||||
debug!("Request body: {:?}", request_body);
|
||||
|
||||
let response = request_builder
|
||||
.body(request_body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
let response = filter_bad_response(response)
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
let response_body = &response.text()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
debug!("Response body: {:?}", response_body);
|
||||
|
||||
let response = serde_json::from_str(&response_body)
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_route(api_host: &ApiHost) -> String {
|
||||
let api_hostname = api_host.to_api_hostname();
|
||||
format!("https://{}{}", api_hostname, GENERATE_HUNYUAN_2_IMAGE_TO_3D_URL_PATH)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod generate_hunyuan2_image_to_3d;
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
pub mod credentials;
|
||||
pub mod error;
|
||||
pub mod generate;
|
||||
pub mod media_files;
|
||||
pub mod recipes;
|
||||
pub mod shared_response_types;
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::web::Json;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SimpleGenericJsonSuccess {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
impl SimpleGenericJsonSuccess {
|
||||
pub fn wrapped(success: bool) -> Json<Self> {
|
||||
Json(SimpleGenericJsonSuccess { success })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simple_json_success() -> HttpResponse {
|
||||
let response = SimpleGenericJsonSuccess {
|
||||
success: true,
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
|
||||
use log::warn;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{MySql, MySqlPool};
|
||||
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::helpers::boolean_converters::i8_to_bool;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::{GenericInferenceArgs, PolymorphicInferenceArgs};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FalJobDetails {
|
||||
pub job_token: InferenceJobToken,
|
||||
|
||||
pub status: JobStatusPlus,
|
||||
|
||||
pub external_third_party: InferenceJobExternalThirdParty,
|
||||
pub external_third_party_id: String,
|
||||
|
||||
pub maybe_creator_user_token: Option<UserToken>,
|
||||
pub maybe_creator_anonymous_visitor_token: Option<AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: String,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Returns Ok(None) when the record cannot be found.
|
||||
pub async fn get_inference_job_by_fal_id(fal_id: &str, mysql_pool: &MySqlPool)
|
||||
-> AnyhowResult<Option<FalJobDetails>>
|
||||
{
|
||||
let mut connection = mysql_pool.acquire().await?;
|
||||
get_inference_job_by_fal_id_from_connection(fal_id, &mut connection).await
|
||||
}
|
||||
|
||||
|
||||
/// Returns Ok(None) when the record cannot be found.
|
||||
pub async fn get_inference_job_by_fal_id_from_connection(fal_id: &str, mysql_connection: &mut PoolConnection<MySql>)
|
||||
-> AnyhowResult<Option<FalJobDetails>>
|
||||
{
|
||||
// NB(bt): jobs.uuid_idempotency_token is the current way to reconstruct the hash of the
|
||||
// TTS result since we don't store a bucket hash on the table. This is an ugly hack :(
|
||||
// TODO(bt,2023-10-12): ^^^ Is this comment still accurate? I don't see that field referenced below.
|
||||
|
||||
let maybe_status = sqlx::query_as!(
|
||||
RawJobRecord,
|
||||
r#"
|
||||
SELECT
|
||||
jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,
|
||||
|
||||
jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,
|
||||
|
||||
jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,
|
||||
jobs.maybe_external_third_party_id as `external_third_party_id`,
|
||||
|
||||
jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
|
||||
jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,
|
||||
jobs.creator_ip_address,
|
||||
|
||||
jobs.created_at,
|
||||
jobs.updated_at
|
||||
|
||||
FROM generic_inference_jobs as jobs
|
||||
|
||||
WHERE jobs.maybe_external_third_party = ?
|
||||
AND jobs.maybe_external_third_party_id = ?
|
||||
"#,
|
||||
InferenceJobExternalThirdParty::Fal.to_str(),
|
||||
fal_id
|
||||
)
|
||||
.fetch_one(&mut **mysql_connection)
|
||||
.await;
|
||||
|
||||
let record = match maybe_status {
|
||||
Ok(record) => record,
|
||||
Err(err) => match err {
|
||||
sqlx::Error::RowNotFound => return Ok(None),
|
||||
_ => {
|
||||
warn!("error querying job record: {:?}", err);
|
||||
return Err(anyhow!("error querying job record: {:?}", err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let record = raw_record_to_public_result(record)?;
|
||||
|
||||
Ok(Some(record))
|
||||
}
|
||||
|
||||
fn raw_record_to_public_result(record: RawJobRecord) -> AnyhowResult<FalJobDetails> {
|
||||
Ok(FalJobDetails {
|
||||
job_token: record.job_token,
|
||||
status: record.status,
|
||||
external_third_party: record.external_third_party.ok_or_else(|| anyhow!("missing external_third_party"))?,
|
||||
external_third_party_id: record.external_third_party_id.ok_or_else(|| anyhow!("missing external_third_party_id"))?,
|
||||
maybe_creator_user_token: record.maybe_creator_user_token,
|
||||
maybe_creator_anonymous_visitor_token: record.maybe_creator_anonymous_visitor_token,
|
||||
creator_ip_address: record.creator_ip_address,
|
||||
created_at: record.created_at,
|
||||
updated_at: record.updated_at,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct RawJobRecord {
|
||||
pub job_token: InferenceJobToken,
|
||||
|
||||
pub status: JobStatusPlus,
|
||||
|
||||
// NB: Nullable, but required to be filled for this query
|
||||
pub external_third_party: Option<InferenceJobExternalThirdParty>,
|
||||
|
||||
// NB: Nullable, but required to be filled for this query
|
||||
pub external_third_party_id: Option<String>,
|
||||
|
||||
pub maybe_creator_user_token: Option<UserToken>,
|
||||
pub maybe_creator_anonymous_visitor_token: Option<AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: String,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use log::info;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_input_source_token_type::InferenceInputSourceTokenType;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
|
||||
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
use crate::queries::tts::tts_inference_jobs::kill_tts_inference_jobs::JobStatus;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FalCategory {
|
||||
ImageGeneration,
|
||||
VideoGeneration,
|
||||
BackgroundRemoval,
|
||||
ObjectGeneration,
|
||||
}
|
||||
|
||||
pub struct InsertGenericInferenceForFalArgs<'a> {
|
||||
pub uuid_idempotency_token: &'a str,
|
||||
|
||||
/// The external primary key identifier for the job.
|
||||
pub maybe_external_third_party_id: &'a str,
|
||||
|
||||
pub fal_category: FalCategory,
|
||||
|
||||
pub maybe_inference_args: Option<GenericInferenceArgs>,
|
||||
|
||||
pub maybe_creator_user_token: Option<&'a UserToken>,
|
||||
pub maybe_avt_token: Option<&'a AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: &'a str,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub mysql_pool: &'a MySqlPool,
|
||||
}
|
||||
|
||||
pub async fn insert_generic_inference_job_for_fal_queue(args: InsertGenericInferenceForFalArgs<'_>)
|
||||
-> Result<InferenceJobToken, DatabaseQueryError>
|
||||
{
|
||||
let job_token = InferenceJobToken::generate();
|
||||
|
||||
let serialized_args_payload = serde_json::ser::to_string(&args.maybe_inference_args)
|
||||
.map_err(|_e| anyhow!("could not encode inference args"))?;
|
||||
|
||||
|
||||
const JOB_TYPE : InferenceJobType = InferenceJobType::FalQueue;
|
||||
|
||||
let (inference_category, product_category) =
|
||||
match args.fal_category {
|
||||
FalCategory::ImageGeneration => (
|
||||
InferenceCategory::ImageGeneration,
|
||||
InferenceJobProductCategory::FalImage
|
||||
),
|
||||
FalCategory::VideoGeneration => (
|
||||
InferenceCategory::VideoGeneration,
|
||||
InferenceJobProductCategory::FalVideo
|
||||
),
|
||||
FalCategory::BackgroundRemoval => (
|
||||
InferenceCategory::BackgroundRemoval,
|
||||
InferenceJobProductCategory::FalBgRemoval
|
||||
),
|
||||
FalCategory::ObjectGeneration => (
|
||||
InferenceCategory::ObjectGeneration,
|
||||
InferenceJobProductCategory::FalObject,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
let maybe_external_third_party = InferenceJobExternalThirdParty::Fal;
|
||||
|
||||
let maybe_external_third_party_id = "";
|
||||
const STATUS : JobStatusPlus = JobStatusPlus::Pending;
|
||||
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO generic_inference_jobs
|
||||
SET
|
||||
token = ?,
|
||||
uuid_idempotency_token = ?,
|
||||
|
||||
job_type = ?,
|
||||
|
||||
maybe_external_third_party = ?,
|
||||
maybe_external_third_party_id = ?,
|
||||
|
||||
product_category = ?,
|
||||
inference_category = ?,
|
||||
|
||||
maybe_model_type = NULL,
|
||||
maybe_model_token = NULL,
|
||||
|
||||
maybe_input_source_token = NULL,
|
||||
maybe_input_source_token_type = NULL,
|
||||
|
||||
maybe_download_url = NULL,
|
||||
maybe_cover_image_media_file_token = NULL,
|
||||
|
||||
maybe_raw_inference_text = NULL,
|
||||
|
||||
maybe_inference_args = ?,
|
||||
|
||||
maybe_creator_user_token = ?,
|
||||
maybe_creator_anonymous_visitor_token = ?,
|
||||
creator_ip_address = ?,
|
||||
creator_set_visibility = ?,
|
||||
|
||||
priority_level = 0,
|
||||
is_keepalive_required = FALSE,
|
||||
max_duration_seconds = 0,
|
||||
|
||||
is_debug_request = FALSE,
|
||||
maybe_routing_tag = NULL,
|
||||
|
||||
status = ?
|
||||
"#,
|
||||
job_token.as_str(),
|
||||
args.uuid_idempotency_token,
|
||||
|
||||
JOB_TYPE.to_str(),
|
||||
|
||||
maybe_external_third_party.to_str(),
|
||||
args.maybe_external_third_party_id,
|
||||
|
||||
product_category.to_str(),
|
||||
inference_category.to_str(),
|
||||
|
||||
serialized_args_payload,
|
||||
|
||||
args.maybe_creator_user_token.map(|t| t.to_string()),
|
||||
args.maybe_avt_token.map(|t| t.to_string()),
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
STATUS.to_str(),
|
||||
);
|
||||
|
||||
let query_result = query.execute(args.mysql_pool)
|
||||
.await;
|
||||
|
||||
let record_id = match query_result {
|
||||
Err(err) => return Err(DatabaseQueryError::from(err)),
|
||||
Ok(res) => res.last_insert_id(),
|
||||
};
|
||||
|
||||
info!("Insert generic inference job for FAL queue: {job_token} with record ID {record_id}");
|
||||
|
||||
Ok(job_token)
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
// TODO: Maybe in the future we set a batch_token instead of media token
|
||||
|
||||
pub async fn mark_fal_generic_inference_job_successfully_done(
|
||||
pool: &MySqlPool,
|
||||
job_token: &InferenceJobToken,
|
||||
media_file_token: MediaFileToken,
|
||||
) -> AnyhowResult<()>
|
||||
{
|
||||
const STATUS : JobStatusPlus = JobStatusPlus::CompleteSuccess;
|
||||
const RESULT_TYPE : InferenceResultType = InferenceResultType::MediaFile;
|
||||
|
||||
let query_result = sqlx::query!(
|
||||
r#"
|
||||
UPDATE generic_inference_jobs
|
||||
SET
|
||||
status = ?,
|
||||
on_success_result_entity_type = ?,
|
||||
on_success_result_entity_token = ?,
|
||||
failure_reason = NULL,
|
||||
internal_debugging_failure_reason = NULL,
|
||||
success_execution_millis = NULL,
|
||||
success_inference_execution_millis = NULL,
|
||||
retry_at = NULL,
|
||||
successfully_completed_at = NOW()
|
||||
WHERE token = ?
|
||||
"#,
|
||||
STATUS.to_str(),
|
||||
RESULT_TYPE.to_str(),
|
||||
media_file_token,
|
||||
job_token,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
match query_result {
|
||||
Err(err) => Err(anyhow!("error with query: {:?}", err)),
|
||||
Ok(_r) => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod get_inference_job_by_fal_id;
|
||||
pub mod insert_generic_inference_job_for_fal_queue;
|
||||
pub mod mark_fal_generic_inference_job_successfully_done;
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod fal;
|
||||
pub mod job;
|
||||
pub mod web;
|
||||
|
||||
-6
@@ -174,9 +174,6 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::VoiceConversion => record.maybe_voice_conversion_model_title.as_deref(),
|
||||
InferenceCategory::VideoFilter => Some("Video Filter"),
|
||||
InferenceCategory::ImageGeneration => Some("Image Generation"),
|
||||
InferenceCategory::VideoGeneration => Some("Video Generation"),
|
||||
InferenceCategory::ObjectGeneration => Some("Object Generation"),
|
||||
InferenceCategory::BackgroundRemoval => Some("Background Removal"),
|
||||
InferenceCategory::Mocap => Some("Mocap"),
|
||||
InferenceCategory::Workflow => Some("Workflow"),
|
||||
InferenceCategory::FormatConversion => Some("format conversion"),
|
||||
@@ -197,9 +194,6 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::VoiceConversion => (true, record.maybe_voice_conversion_public_bucket_hash.as_deref()),
|
||||
InferenceCategory::VideoFilter => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ImageGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::VideoGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ObjectGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::BackgroundRemoval => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::Mocap => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::Workflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::FormatConversion => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
|
||||
-6
@@ -150,9 +150,6 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
|
||||
InferenceCategory::VoiceConversion => record.maybe_voice_conversion_model_title.as_deref(),
|
||||
InferenceCategory::VideoFilter => Some("Video Filter"),
|
||||
InferenceCategory::ImageGeneration => Some("Image Generation"),
|
||||
InferenceCategory::VideoGeneration => Some("Video Generation"),
|
||||
InferenceCategory::ObjectGeneration => Some("Object Generation"),
|
||||
InferenceCategory::BackgroundRemoval => Some("Background Removal"),
|
||||
InferenceCategory::Mocap => Some("Mocap"),
|
||||
InferenceCategory::Workflow => Some("Workflow"),
|
||||
InferenceCategory::FormatConversion => Some("format conversion"),
|
||||
@@ -173,9 +170,6 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
|
||||
InferenceCategory::VoiceConversion => (true, record.maybe_voice_conversion_public_bucket_hash.as_deref()),
|
||||
InferenceCategory::VideoFilter => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ImageGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::VideoGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ObjectGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::BackgroundRemoval => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::Mocap => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::Workflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::FormatConversion => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
|
||||
-6
@@ -241,9 +241,6 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::VoiceConversion => record.maybe_voice_conversion_model_title.as_deref(),
|
||||
InferenceCategory::VideoFilter => Some("Video Filter"),
|
||||
InferenceCategory::ImageGeneration => Some("Image Generation"),
|
||||
InferenceCategory::VideoGeneration => Some("Video Generation"),
|
||||
InferenceCategory::ObjectGeneration => Some("Object Generation"),
|
||||
InferenceCategory::BackgroundRemoval => Some("Background Removal"),
|
||||
InferenceCategory::Mocap => Some("Mocap"),
|
||||
InferenceCategory::Workflow => Some("Workflow"),
|
||||
InferenceCategory::FormatConversion => Some("format conversion"),
|
||||
@@ -263,9 +260,6 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::VoiceConversion => (true, record.maybe_voice_conversion_public_bucket_hash.as_deref()),
|
||||
InferenceCategory::VideoFilter => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ImageGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::VideoGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ObjectGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::BackgroundRemoval => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::Mocap => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::Workflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::FormatConversion => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
|
||||
-8
@@ -4,7 +4,6 @@ use sqlx::MySqlPool;
|
||||
|
||||
use enums::by_table::generic_synthetic_ids::id_category::IdCategory;
|
||||
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_origin_category::MediaFileOriginCategory;
|
||||
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
|
||||
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
|
||||
@@ -52,9 +51,6 @@ pub struct InsertArgs<'a> {
|
||||
pub maybe_frame_width: Option<u32>,
|
||||
pub maybe_frame_height: Option<u32>,
|
||||
pub checksum_sha2: &'a str,
|
||||
|
||||
// Media info for certain product areas
|
||||
pub maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
|
||||
|
||||
// User text information
|
||||
pub maybe_title: Option<&'a str>,
|
||||
@@ -156,8 +152,6 @@ pub async fn insert_media_file_generic(
|
||||
maybe_frame_height = ?,
|
||||
maybe_prompt_token = ?,
|
||||
checksum_sha2 = ?,
|
||||
|
||||
maybe_engine_category = ?,
|
||||
|
||||
maybe_title = ?,
|
||||
maybe_text_transcript = ?,
|
||||
@@ -211,8 +205,6 @@ pub async fn insert_media_file_generic(
|
||||
args.maybe_prompt_token.map(|t| t.as_str()),
|
||||
args.checksum_sha2,
|
||||
|
||||
args.maybe_engine_category.map(|e| e.to_str()),
|
||||
|
||||
args.maybe_title,
|
||||
args.maybe_text_transcript,
|
||||
|
||||
|
||||
-1
@@ -107,7 +107,6 @@ pub async fn insert_media_file_generic_from_job(
|
||||
maybe_extra_media_info: args.maybe_extra_media_info,
|
||||
maybe_frame_height: args.maybe_frame_height,
|
||||
maybe_frame_width: args.maybe_frame_width,
|
||||
maybe_engine_category: None,
|
||||
maybe_mime_type: args.maybe_mime_type,
|
||||
maybe_mod_user_token: args.maybe_mod_user_token,
|
||||
maybe_origin_filename: args.maybe_origin_filename,
|
||||
|
||||
+4
-30
@@ -9,7 +9,6 @@ use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use errors::AnyhowResult;
|
||||
use sqlx::MySqlPool;
|
||||
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
@@ -45,16 +44,13 @@ pub struct MediaFileInsertBuilder {
|
||||
// Media info
|
||||
maybe_mime_type: Option<String>,
|
||||
file_size_bytes: u64, // NB: Non-nullable field
|
||||
maybe_duration_millis: Option<u64>,
|
||||
// maybe_duration_millis: Option<u64>,
|
||||
// maybe_audio_encoding: Option<&'a str>,
|
||||
// maybe_video_encoding: Option<&'a str>,
|
||||
maybe_frame_width: Option<u32>,
|
||||
maybe_frame_height: Option<u32>,
|
||||
checksum_sha2: Option<String>, // NB: Non-nullable field
|
||||
|
||||
// Media info for certain product areas
|
||||
pub maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
|
||||
|
||||
// // User text information
|
||||
// maybe_title: Option<&'a str>,
|
||||
// maybe_text_transcript: Option<&'a str>,
|
||||
@@ -94,11 +90,9 @@ impl MediaFileInsertBuilder {
|
||||
origin_product_category: MediaFileOriginProductCategory::Unknown,
|
||||
maybe_mime_type: None,
|
||||
file_size_bytes: 0,
|
||||
maybe_duration_millis: None,
|
||||
maybe_frame_width: None,
|
||||
maybe_frame_height: None,
|
||||
checksum_sha2: None,
|
||||
maybe_engine_category: None,
|
||||
public_bucket_directory_hash: None,
|
||||
}
|
||||
}
|
||||
@@ -179,44 +173,25 @@ impl MediaFileInsertBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_duration_millis(mut self, maybe_duration_millis: Option<u64>) -> Self {
|
||||
self.maybe_duration_millis = maybe_duration_millis;
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: maybe_duration_millis
|
||||
// TODO: maybe_audio_encoding
|
||||
// TODO: maybe_video_encoding
|
||||
|
||||
|
||||
pub fn frame_width(mut self, frame_width: u32) -> Self {
|
||||
self.maybe_frame_width = Some(frame_width);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_frame_width(mut self, maybe_frame_width: Option<u32>) -> Self {
|
||||
self.maybe_frame_width = maybe_frame_width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn frame_height(mut self, frame_height: u32) -> Self {
|
||||
self.maybe_frame_height = Some(frame_height);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_frame_height(mut self, maybe_frame_height: Option<u32>) -> Self {
|
||||
self.maybe_frame_height = maybe_frame_height;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn checksum_sha2(mut self, checksum_sha2: &str) -> Self {
|
||||
self.checksum_sha2 = Some(checksum_sha2.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_engine_category(mut self, maybe_engine_category: Option<MediaFileEngineCategory>) -> Self {
|
||||
self.maybe_engine_category = maybe_engine_category;
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: maybe_title
|
||||
// TODO: maybe_text_transcript
|
||||
// TODO: maybe_scene_source_media_file_token
|
||||
@@ -263,12 +238,11 @@ impl MediaFileInsertBuilder {
|
||||
maybe_origin_filename: None, // TODO
|
||||
maybe_mime_type: self.maybe_mime_type.as_deref(),
|
||||
file_size_bytes: self.file_size_bytes,
|
||||
maybe_duration_millis: self.maybe_duration_millis,
|
||||
maybe_duration_millis: None, // TODO
|
||||
maybe_audio_encoding: None, // TODO
|
||||
maybe_video_encoding: None, // TODO
|
||||
maybe_frame_width: self.maybe_frame_width,
|
||||
maybe_frame_height: self.maybe_frame_height,
|
||||
maybe_engine_category: self.maybe_engine_category,
|
||||
checksum_sha2: &checksum_sha2,
|
||||
maybe_title: None, // TODO
|
||||
maybe_text_transcript: None, // TODO
|
||||
|
||||
-1
@@ -111,7 +111,6 @@ pub async fn insert_media_file_from_studio_scene_render(
|
||||
maybe_batch_token: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_mod_user_token: None,
|
||||
maybe_engine_category: None,
|
||||
}).await?;
|
||||
|
||||
Ok(new_media_token)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "artcraft_api_defs"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "artcraft_api_defs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Hackari workspace (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
enums = { workspace = true }
|
||||
tokens = { workspace = true }
|
||||
|
||||
# Serialization
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
utoipa = { version = "4", features = ["chrono", "actix_extras"] }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -1 +0,0 @@
|
||||
pub mod remove_image_background;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct RemoveImageBackgroundRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Source of the file to remove the background from.
|
||||
/// It must be an image.
|
||||
pub media_file_token: Option<MediaFileToken>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct RemoveImageBackgroundResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod video;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user