mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-29 13:02:51 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50a1d3801f |
@@ -26,23 +26,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: install wreq dependencies
|
||||
run: |
|
||||
choco install cmake -y
|
||||
choco install strawberryperl -y
|
||||
choco install pkgconfiglite -y
|
||||
choco install llvm -y
|
||||
choco install nasm -y
|
||||
shell: cmd
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: |
|
||||
# Navigate to the frontend directory to install dependencies
|
||||
@@ -92,8 +83,6 @@ jobs:
|
||||
- name: build and publish
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
# NB(bt): This is needed by wreq. Hopefully it doesn't kill Tauri
|
||||
RUSTFLAGS: "-C target-feature=+crt-static"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Use the SQLx query cache for SQLite
|
||||
SQLX_OFFLINE: true
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO media_files\nSET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n origin_category = ?,\n origin_product_category = ?,\n\n maybe_engine_category = ?,\n maybe_animation_type = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n\n checksum_sha2 = ?,\n\n maybe_title = ?,\n maybe_duration_millis = ?,\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 creator_ip_address = ?,\n\n creator_set_visibility = ?,\n\n maybe_scene_source_media_file_token = ?,\n is_intermediate_system_file = ?,\n\n maybe_creator_file_synthetic_id = ?,\n maybe_creator_category_synthetic_id = ?,\n\n is_user_upload = TRUE,\n\n maybe_origin_model_type = NULL,\n maybe_origin_model_token = NULL\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 23
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "28cac5883464613d29bedbf7308a71c258f1bf4abb8f5ca3ae320db34c137955"
|
||||
}
|
||||
-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 on_success_result_batch_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": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7e223e284b992b6eaf31bec057bae00fc4e085d68b064e370f0e3d92b82049b2"
|
||||
}
|
||||
+17
-28
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 jobs.attempt_count,\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.product_category as `product_category: enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory`,\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n jobs.on_success_result_batch_token as maybe_result_batch_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
|
||||
"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 jobs.attempt_count,\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.product_category as `product_category: enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory`,\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -155,16 +155,6 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_result_batch_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_model_weights_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -174,7 +164,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 16,
|
||||
"name": "maybe_tts_model_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -184,7 +174,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 17,
|
||||
"name": "maybe_voice_conversion_model_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -194,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 18,
|
||||
"name": "maybe_tts_public_bucket_path",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -204,7 +194,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 19,
|
||||
"name": "maybe_voice_conversion_public_bucket_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -214,7 +204,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 20,
|
||||
"name": "maybe_media_file_public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -224,7 +214,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 21,
|
||||
"name": "maybe_media_file_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -234,7 +224,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 22,
|
||||
"name": "maybe_media_file_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -244,7 +234,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 23,
|
||||
"name": "maybe_assigned_worker",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -254,7 +244,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 24,
|
||||
"name": "maybe_assigned_cluster",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -264,7 +254,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 25,
|
||||
"name": "is_keepalive_required",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -274,7 +264,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 26,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -284,7 +274,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 27,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -294,7 +284,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 28,
|
||||
"name": "maybe_first_started_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -304,7 +294,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 29,
|
||||
"name": "maybe_successfully_completed_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -314,7 +304,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 30,
|
||||
"name": "database_clock",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -353,7 +343,6 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
@@ -362,5 +351,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "243afe0bc574f9f233c7797add9faf96440e2bafd52032e07951272fb1ec78b1"
|
||||
"hash": "90366e1705a0343bdfe4fc472d701b8e1dbd3a2dabedaadb633ac3542de72fa7"
|
||||
}
|
||||
-311
@@ -1,311 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nWHERE\n m.maybe_batch_token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: tokens::tokens::media_files::MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_mime_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_creator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_creator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_creator_gravatar_hash",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_text_transcript",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_other_prompt_args",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_file_cover_image_public_bucket_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_file_cover_image_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_file_cover_image_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "creator_set_visibility: enums::common::visibility::Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "is_user_upload",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "is_intermediate_system_file",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "971471f349bcd22364b16b5d6c68e807ed897101eec1e8a62a7b8fab3359fa0c"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO media_files\nSET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n origin_category = ?,\n origin_product_category = ?,\n\n maybe_engine_category = ?,\n maybe_animation_type = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n\n checksum_sha2 = ?,\n\n maybe_prompt_token = ?,\n\n maybe_title = ?,\n maybe_duration_millis = ?,\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 creator_ip_address = ?,\n\n creator_set_visibility = ?,\n\n maybe_scene_source_media_file_token = ?,\n is_intermediate_system_file = ?,\n\n maybe_creator_file_synthetic_id = ?,\n maybe_creator_category_synthetic_id = ?,\n\n is_user_upload = TRUE,\n\n maybe_origin_model_type = NULL,\n maybe_origin_model_token = NULL\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 24
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e86494c0a53247903aa58009e2a26c4db90a89e9f6ea805ffb3929faf69a431d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
-311
@@ -1,311 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nWHERE\n m.maybe_batch_token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: tokens::tokens::media_files::MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_mime_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_creator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_creator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_creator_gravatar_hash",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_text_transcript",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_other_prompt_args",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_file_cover_image_public_bucket_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_file_cover_image_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_file_cover_image_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "creator_set_visibility: enums::common::visibility::Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "is_user_upload",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "is_intermediate_system_file",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f75fedb86a93ac2542315ddb7ef0d46aa42ecc25a3cf9856219114064183337a"
|
||||
}
|
||||
Generated
+107
-579
File diff suppressed because it is too large
Load Diff
+1
-7
@@ -10,7 +10,6 @@ members = [
|
||||
"crates/api_clients/fal_client",
|
||||
"crates/api_clients/google_drive_common",
|
||||
"crates/api_clients/google_sign_in",
|
||||
"crates/api_clients/midjourney_client",
|
||||
"crates/api_clients/openai_sora_client",
|
||||
"crates/api_clients/openart_client",
|
||||
"crates/api_clients/runwayml_client",
|
||||
@@ -43,7 +42,6 @@ members = [
|
||||
"crates/lib/caching/redis_caching",
|
||||
"crates/lib/cloudflare_errors",
|
||||
"crates/lib/concurrency",
|
||||
"crates/lib/cookie_store",
|
||||
"crates/lib/cookies",
|
||||
"crates/lib/data/collections",
|
||||
"crates/lib/data/crockford",
|
||||
@@ -201,8 +199,7 @@ log = "0.4.27"
|
||||
once_cell = "1.21.3"
|
||||
rand = "0.9.1"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.22", features = ["json", "cookies"] }
|
||||
reqwest-websocket = { version = "0.5.0" }
|
||||
reqwest = { version = "0.12.15", features = ["json", "cookies"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_derive = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
@@ -221,7 +218,6 @@ artcraft_api_defs = { path = "crates/schema/public/api/artcraft_api_defs" }
|
||||
build_metadata = { path = "crates/lib/build_metadata" }
|
||||
cloud_storage = { path = "crates/api_clients/cloud_storage" }
|
||||
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
|
||||
cookie_store = { path = "crates/lib/cookie_store" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
enums = { path = "crates/schema/public/enums" }
|
||||
enums_public = { path = "crates/schema/public/enums_public" }
|
||||
@@ -234,7 +230,6 @@ images = { path = "crates/lib/files/images" }
|
||||
jwt_light = { path = "crates/lib/jwt_light" }
|
||||
memory_caching = { path = "crates/lib/caching/memory_caching" }
|
||||
memory_store = { path = "crates/lib/caching/memory_store" }
|
||||
midjourney_client = { path = "crates/api_clients/midjourney_client" }
|
||||
mimetypes = { path = "crates/lib/files/mimetypes" }
|
||||
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
|
||||
primitives = { path = "crates/lib/data/primitives" }
|
||||
@@ -243,6 +238,5 @@ sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
|
||||
storyteller_client = { path = "crates/api_clients/storyteller_client" }
|
||||
subprocess_common = { path = "crates/lib/subprocess_common" }
|
||||
test_data = { path = "crates/lib/test_data" }
|
||||
testing = { path = "crates/lib/testing" }
|
||||
tokens = { path = "crates/schema/public/tokens" }
|
||||
web_base64 = { path = "crates/lib/data/web_base64" }
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
--
|
||||
-- NB: This version needs to be bumped even if just adding or changing comments!
|
||||
-- Failure to do so will cause Windows to segfault at start. (This is horrible!)
|
||||
--
|
||||
-- NB: `app_state_dir.rs` contains the file version number.
|
||||
--
|
||||
-- Migration history:
|
||||
-- tasks_v1.sqlite - initial version
|
||||
-- tasks_v2.sqlite - added model_type (nullable)
|
||||
-- tasks_v3.sqlite - added frontend_caller, comments
|
||||
|
||||
CREATE TABLE tasks (
|
||||
-- Task auto-incrementing primary key.
|
||||
|
||||
-96
@@ -1,96 +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::prelude::rundiffusion_fal::juggernaut_flux_lora::inpainting::{inpainting, InpaintInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct FluxDevJuggernautInfillArgs<'a, U: IntoUrl, R: IntoUrl, L: IntoUrl> {
|
||||
// Request
|
||||
pub prompt: &'a str,
|
||||
pub image_url: U,
|
||||
pub mask_url: R,
|
||||
pub num_images: FluxDevJuggernautInfillNumImages,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: L,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxDevJuggernautInfillNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
pub async fn enqueue_flux_dev_juggernaut_infill_webhook<U: IntoUrl, R: IntoUrl, L: IntoUrl>(
|
||||
args: FluxDevJuggernautInfillArgs<'_, U, R, L>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
FluxDevJuggernautInfillNumImages::One => 1,
|
||||
FluxDevJuggernautInfillNumImages::Two => 2,
|
||||
FluxDevJuggernautInfillNumImages::Three => 3,
|
||||
FluxDevJuggernautInfillNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let request = InpaintInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_url: args.image_url.as_str().to_string(),
|
||||
mask_url: args.mask_url.as_str().to_string(),
|
||||
num_images: Some(num_images),
|
||||
|
||||
// Maybe expose
|
||||
output_format: Some("png".to_string()), // png or jpeg
|
||||
seed: None,
|
||||
strength: None,
|
||||
loras: None,
|
||||
num_inference_steps: None,
|
||||
image_size: None,
|
||||
enable_safety_checker: None,
|
||||
guidance_scale: None,
|
||||
|
||||
// Constants
|
||||
sync_mode: None, // Synchronous / slow
|
||||
};
|
||||
|
||||
let result = inpainting(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::infill::enqueue_flux_dev_juggernaut_infill_webhook::{enqueue_flux_dev_juggernaut_infill_webhook, FluxDevJuggernautInfillArgs, FluxDevJuggernautInfillNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL, TALL_MOCHI_WITH_GLASSES_IMAGE_URL};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = FluxDevJuggernautInfillArgs {
|
||||
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
|
||||
mask_url: TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL,
|
||||
prompt: "slick sunglasses, cool glasses, reflection in glasses lenses",
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
num_images: FluxDevJuggernautInfillNumImages::Two,
|
||||
};
|
||||
|
||||
let result = enqueue_flux_dev_juggernaut_infill_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
pub mod enqueue_flux_dev_juggernaut_infill_webhook;
|
||||
pub mod enqueue_flux_pro_1_infill_webhook;
|
||||
@@ -1,55 +0,0 @@
|
||||
[package]
|
||||
name = "midjourney_client"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
name = "midjourney_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
cloudflare_errors.workspace = true
|
||||
cookie_store.workspace = true
|
||||
errors.workspace = true
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
#reqwest-websocket.workspace = true
|
||||
#reqwest.workspace = true
|
||||
#reqwest-impersonate = { version = "0", features = ["websocket"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
#tokio.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
# NB: wreq gets past fingerprinting, but we have to deal with platform-specific build issues.
|
||||
#
|
||||
# Mac:
|
||||
# brew install --formula cmake pkg-config llvm
|
||||
# Windows:
|
||||
# choco install cmake -y
|
||||
# choco install strawberryperl -y
|
||||
# choco install pkgconfiglite -y
|
||||
# choco install llvm -y
|
||||
# choco install nasm -y
|
||||
wreq = { version = "5", features = ["json"] }
|
||||
wreq-util = "2"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
# NB: We need to parse HTML to get out the websocket token
|
||||
dom_query = "0.20.2"
|
||||
|
||||
[dev-dependencies]
|
||||
filesys.workspace = true
|
||||
test_data.workspace = true
|
||||
testing.workspace = true
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
/// Multiple third party Midjourney clients make this configurable.
|
||||
/// Not sure why, I'm just cargo culting it.
|
||||
#[derive(Clone)]
|
||||
pub enum MidjourneyHostname {
|
||||
Standard,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl MidjourneyHostname {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Standard => "www.midjourney.com",
|
||||
Self::Custom(domain) => domain,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod midjourney_hostname;
|
||||
pub mod midjourney_websocket_client;
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
/// One of the apparent authentication cookies used by Midjourney.
|
||||
pub (crate) const AUTH_COOKIE_NAME_R : &str = "__Host-Midjourney.AuthUserTokenV3_r";
|
||||
|
||||
/// One of the apparent authentication cookies used by Midjourney.
|
||||
pub (crate) const AUTH_COOKIE_NAME_I : &str = "__Host-Midjourney.AuthUserTokenV3_i";
|
||||
@@ -1,8 +0,0 @@
|
||||
use crate::credentials::cookie_names::{AUTH_COOKIE_NAME_I, AUTH_COOKIE_NAME_R};
|
||||
use cookie_store::cookie_store::CookieStore;
|
||||
|
||||
/// Returns true if the given cookie store has the necessary authentication cookies.
|
||||
pub fn cookie_store_has_auth_cookies(cookie_store: &CookieStore) -> bool {
|
||||
cookie_store.has_cookie(AUTH_COOKIE_NAME_I)
|
||||
&& cookie_store.has_cookie(AUTH_COOKIE_NAME_R)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
/*
|
||||
{
|
||||
"name": "u3869671173",
|
||||
"picture": "https://lh3.googleusercontent.com/a/ACg8ocKw0aaXKYSqy-Xkkn69ydIJFGnJA37S13XvjNY4d9xCzUzKUw=s96-c",
|
||||
"midjourney_id": "f8a57ac3-e416-4dd4-9be8-2c4223691b01",
|
||||
"iss": "https://securetoken.google.com/authjourney",
|
||||
"aud": "authjourney",
|
||||
"auth_time": 1754970849,
|
||||
"user_id": "bS9idl73DlSbE61KnCyKpDfazCX2",
|
||||
"sub": "bS9idl73DlSbE61KnCyKpDfazCX2",
|
||||
"iat": 1754970849,
|
||||
"exp": 1754974449,
|
||||
"email": "victorvoice89@gmail.com",
|
||||
"email_verified": true,
|
||||
"firebase": {
|
||||
"identities": {
|
||||
"google.com": [
|
||||
"116459300198619888836"
|
||||
],
|
||||
"email": [
|
||||
"victorvoice89@gmail.com"
|
||||
]
|
||||
},
|
||||
"sign_in_provider": "google.com"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
*/
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
/// The user ID used for several midjourney requests.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct MidjourneyUserId(pub String);
|
||||
|
||||
impl MidjourneyUserId {
|
||||
pub fn from_str(user_id: &str) -> Self {
|
||||
Self(user_id.to_string())
|
||||
}
|
||||
|
||||
pub fn from_string(user_id: String) -> Self {
|
||||
Self(user_id)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod cookie_names;
|
||||
pub mod cookie_store_has_auth_cookies;
|
||||
pub mod jwt_decode;
|
||||
pub mod midjourney_user_id;
|
||||
@@ -1,95 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use log::error;
|
||||
use serde::Deserialize;
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
/// This grabs the pre-rendered HTML page, which contains the websocket token and other details.
|
||||
pub struct GetIndexPageRequest {
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
pub async fn get_index_page_html(req: GetIndexPageRequest) -> Result<String, MidjourneyError> {
|
||||
let url = format!("https://{}/", req.hostname.as_str());
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox139)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let cookie_header = req.cookie_header.trim();
|
||||
|
||||
if cookie_header.len() < 20 {
|
||||
error!("Cookie header is too short (len: {}): {}", cookie_header.len(), cookie_header);
|
||||
return Err(MidjourneyClientError::CookieTooShort.into());
|
||||
}
|
||||
|
||||
// NB: missing headers that were in the browser request:
|
||||
// -H 'sec-ch-ua-platform: "macOS"' \
|
||||
// -H 'sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"' \
|
||||
|
||||
let mut http_request = client.get(url)
|
||||
.header("cookie", cookie_header)
|
||||
.header("Referrer-Policy", "origin-when-cross-origin")
|
||||
.header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||
.header("accept-language", "en-US,en;q=0.8")
|
||||
.header("content-type", "application/json")
|
||||
.header("priority", "u=0, i")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-fetch-dest", "document")
|
||||
.header("sec-fetch-mode", "navigate")
|
||||
.header("sec-fetch-site", "none")
|
||||
.header("sec-fetch-user", "?1")
|
||||
.header("upgrade-insecure-requests", "1");
|
||||
|
||||
let http_request = http_request
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = response.text().await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
if let Err(err) = filter_cloudflare_errors(status.as_u16(), &response_body) {
|
||||
return Err(MidjourneyApiError::CloudflareError(err).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response_body)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
use crate::endpoints::get_index_page_html::{get_index_page_html, GetIndexPageRequest};
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
|
||||
let result = get_index_page_html(GetIndexPageRequest {
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}\n\n", result);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::credentials::midjourney_user_id::MidjourneyUserId;
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use log::error;
|
||||
use serde::Deserialize;
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const DEFAULT_PAGE_SIZE : u64 = 1000;
|
||||
|
||||
/// This returns Midjourney's job/media list
|
||||
pub struct ImagineRequest<'a> {
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
pub user_id: &'a MidjourneyUserId,
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImagineResponse {
|
||||
pub items: Vec<ImagineItem>,
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImagineItem {
|
||||
pub id: Option<String>,
|
||||
|
||||
/// This is the prompt, e.g.
|
||||
/// `cute puppy wearing a polar bear suit --ar 4:3 --raw --v 7.0`
|
||||
pub full_command: Option<String>,
|
||||
|
||||
pub job_type: Option<MidjourneyJobType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MidjourneyJobType {
|
||||
V6Diffusion,
|
||||
V6p1Diffusion,
|
||||
V6p1RawDiffusion,
|
||||
V7Diffusion,
|
||||
V7DraftDiffusion,
|
||||
V7DraftRawDiffusion,
|
||||
V7RawDiffusion,
|
||||
Vid11I2vRenderAJointVideo,
|
||||
Vid11I2vRenderBJointVideo,
|
||||
Vid11I2vStartEndAVideo,
|
||||
Vid11I2vStartEndBVideo,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl MidjourneyJobType {
|
||||
pub fn from_str(s: &str) -> MidjourneyJobType {
|
||||
match s {
|
||||
"v6_diffusion" => MidjourneyJobType::V6Diffusion,
|
||||
"v6-1_diffusion" => MidjourneyJobType::V6p1Diffusion,
|
||||
"v6-1_raw_diffusion" => MidjourneyJobType::V6p1RawDiffusion,
|
||||
"v7_diffusion" => MidjourneyJobType::V7Diffusion,
|
||||
"v7_draft_diffusion" => MidjourneyJobType::V7DraftDiffusion,
|
||||
"v7_draft_raw_diffusion" => MidjourneyJobType::V7DraftRawDiffusion,
|
||||
"v7_raw_diffusion" => MidjourneyJobType::V7RawDiffusion,
|
||||
"vid_1.1_i2v_render_a_joint_video" => MidjourneyJobType::Vid11I2vRenderAJointVideo,
|
||||
"vid_1.1_i2v_render_b_joint_video" => MidjourneyJobType::Vid11I2vRenderBJointVideo,
|
||||
"vid_1.1_i2v_start_end_a_video" => MidjourneyJobType::Vid11I2vStartEndAVideo,
|
||||
"vid_1.1_i2v_start_end_b_video" => MidjourneyJobType::Vid11I2vStartEndBVideo,
|
||||
other => MidjourneyJobType::Other(other.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn imagine(req: ImagineRequest<'_>) -> Result<ImagineResponse, MidjourneyError> {
|
||||
let cookie_header = req.cookie_header.trim();
|
||||
|
||||
if cookie_header.len() < 20 {
|
||||
error!("Cookie header is too short (len: {}): {}", cookie_header.len(), cookie_header);
|
||||
return Err(MidjourneyClientError::CookieTooShort.into());
|
||||
}
|
||||
|
||||
let page_size = req.page_size.unwrap_or(DEFAULT_PAGE_SIZE);
|
||||
|
||||
let referer = format!("https://{}/imagine", req.hostname.as_str());
|
||||
|
||||
let url = format!("https://{}/api/imagine?user_id={}&page_size={}",
|
||||
req.hostname.as_str(),
|
||||
req.user_id.as_str(),
|
||||
page_size,
|
||||
);
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox139)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
// NB: missing headers that were in the browser request:
|
||||
// -H 'sec-ch-ua-platform: "macOS"' \
|
||||
// -H 'sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"' \
|
||||
|
||||
let mut http_request = client.get(url)
|
||||
.header("cookie", cookie_header)
|
||||
.header("Referer", &referer)
|
||||
.header("Referrer-Policy", "origin-when-cross-origin")
|
||||
.header("accept", "*/*")
|
||||
.header("accept-encoding", "gzip, deflate, br, zstd")
|
||||
.header("accept-language", "en-US,en;q=0.8")
|
||||
.header("content-type", "application/json")
|
||||
.header("priority", "u=1, i")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin")
|
||||
.header("x-csrf-protection", "1");
|
||||
|
||||
let http_request = http_request
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = response.text().await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
if let Err(err) = filter_cloudflare_errors(status.as_u16(), &response_body) {
|
||||
return Err(MidjourneyApiError::CloudflareError(err).into());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "a2339f14-ff71-43e4-a69f-8da15e7ba871",
|
||||
"enqueue_time": "2025-08-14T17:21:28.403560+00:00",
|
||||
"parent_id": null,
|
||||
"video_segments": null,
|
||||
"rating": null,
|
||||
"job_type": "v7_diffusion",
|
||||
"event_type": "diffusion",
|
||||
"parent_grid": null,
|
||||
"full_command": "a modern n64 console",
|
||||
"batch_size": 4,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"published": true,
|
||||
"shown": true,
|
||||
"user_hidden": false,
|
||||
"template": null,
|
||||
"vid_framecount": null
|
||||
},
|
||||
],
|
||||
"cursor": "encrypted cursor",
|
||||
"checkpoint": "encrypted something ???"
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawImagineResponse {
|
||||
data: Vec<RawImagineItem>,
|
||||
cursor: Option<String>,
|
||||
//pub checkpoint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawImagineItem {
|
||||
// The job id
|
||||
id: Option<String>,
|
||||
|
||||
// The prompt plus `--` args.
|
||||
full_command: Option<String>,
|
||||
|
||||
// Number of images, typically 4
|
||||
batch_size: Option<u8>,
|
||||
|
||||
// Some possible values:
|
||||
// "v6_diffusion"
|
||||
// "v7_diffusion"
|
||||
// "v7_draft_raw_diffusion"
|
||||
// "v7_raw_diffusion"
|
||||
// "vid_1.1_i2v_render_b_joint_video",
|
||||
// "vid_1.1_i2v_start_end_a_video"
|
||||
// "vid_1.1_i2v_start_end_b_video"
|
||||
job_type: Option<String>,
|
||||
|
||||
// Some possible values:
|
||||
// "diffusion"
|
||||
// "draft"
|
||||
// "variation"
|
||||
// "video_diffusion"
|
||||
// "video_start_end"
|
||||
event_type: Option<String>,
|
||||
}
|
||||
|
||||
let response : RawImagineResponse = serde_json::from_str(&response_body)
|
||||
.map_err(|err| MidjourneyApiError::DeserializationError(err))?;
|
||||
|
||||
Ok(ImagineResponse {
|
||||
cursor: response.cursor.clone(),
|
||||
items: response.data
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
ImagineItem {
|
||||
id: item.id,
|
||||
full_command: item.full_command,
|
||||
job_type: item.job_type.as_ref()
|
||||
.map(|jt| MidjourneyJobType::from_str(jt))
|
||||
}
|
||||
}).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::credentials::midjourney_user_id::MidjourneyUserId;
|
||||
use crate::endpoints::imagine::{imagine, ImagineRequest};
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
let user_id = read_to_trimmed_string("/Users/bt/secrets/midjourney/user_id.txt")?;
|
||||
let user_id = MidjourneyUserId::from_string(user_id);
|
||||
|
||||
let result = imagine(ImagineRequest {
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
user_id: &user_id,
|
||||
page_size: None,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}\n\n", result);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod get_index_page_html;
|
||||
pub mod imagine;
|
||||
pub mod storage_list;
|
||||
pub mod submit_job;
|
||||
pub mod user_connections;
|
||||
@@ -1,147 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use log::error;
|
||||
use serde::Deserialize;
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
/// This endpoint returns the user id
|
||||
pub struct GetStorageListRequest {
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StorageItem {
|
||||
// NB: The path contains the user ID.
|
||||
pub bucket_pathname: Option<String>,
|
||||
|
||||
// NB: Other fields aren't necessary for us right now.
|
||||
}
|
||||
|
||||
pub async fn storage_list(req: GetStorageListRequest) -> Result<Vec<StorageItem>, MidjourneyError> {
|
||||
|
||||
let referer = format!("https://{}", req.hostname.as_str());
|
||||
|
||||
let url = format!("https://{}/api/storage-list", req.hostname.as_str());
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox139)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let cookie_header = req.cookie_header.trim();
|
||||
|
||||
if cookie_header.len() < 20 {
|
||||
error!("Cookie header is too short (len: {}): {}", cookie_header.len(), cookie_header);
|
||||
return Err(MidjourneyClientError::CookieTooShort.into());
|
||||
}
|
||||
|
||||
// NB: missing headers that were in the browser request:
|
||||
// -H 'sec-ch-ua-platform: "macOS"' \
|
||||
// -H 'sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"' \
|
||||
|
||||
let mut http_request = client.get(url)
|
||||
.header("cookie", cookie_header)
|
||||
.header("Referer", &referer)
|
||||
.header("Referrer-Policy", "origin-when-cross-origin")
|
||||
.header("accept", "*/*")
|
||||
.header("accept-language", "en-US,en;q=0.8")
|
||||
.header("content-type", "application/json")
|
||||
.header("priority", "u=1, i")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin")
|
||||
.header("x-csrf-protection", "1");
|
||||
|
||||
let http_request = http_request
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
// if status == 301 {
|
||||
// for (name, value) in response.headers().iter() {
|
||||
// println!("- {}: {}", name.as_str(), value.to_str().unwrap());
|
||||
// }
|
||||
// }
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
if let Err(err) = filter_cloudflare_errors(status.as_u16(), &response_body) {
|
||||
return Err(MidjourneyApiError::CloudflareError(err).into());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[
|
||||
{
|
||||
"bucketPathname": "UUID/OTHER_HEX_ENTROPY.png",
|
||||
"shortUrl": null,
|
||||
"type": "image-prompt",
|
||||
"origin": "web",
|
||||
"timeCreated": TIMESTAMP,
|
||||
"hidden": false,
|
||||
"contentType": "image/png",
|
||||
"state": "loaded"
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawStorageItem {
|
||||
bucketPathname: Option<String>,
|
||||
}
|
||||
|
||||
let response : Vec<RawStorageItem> = serde_json::from_str(response_body)
|
||||
.map_err(|err| MidjourneyApiError::DeserializationError(err))?;
|
||||
|
||||
let items = response
|
||||
.into_iter()
|
||||
.map(|r| StorageItem {
|
||||
bucket_pathname: r.bucketPathname,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
use crate::endpoints::storage_list::{storage_list, GetStorageListRequest};
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
|
||||
let result = storage_list(GetStorageListRequest {
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}\n\n", result);
|
||||
|
||||
for provider in result {
|
||||
println!("Provider: {:?}", provider);
|
||||
}
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use log::{error, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
pub struct SubmitJobRequest<'a> {
|
||||
pub prompt: &'a str,
|
||||
pub channel_id: &'a str,
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmitJobResponse {
|
||||
/// On success, the job ID is returned.
|
||||
pub maybe_job_id: Option<String>,
|
||||
|
||||
/// On error, we have a list of error messages.
|
||||
pub maybe_errors: Option<Vec<SubmitJobError>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmitJobError {
|
||||
pub error_type: Option<String>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn submit_job(req: SubmitJobRequest<'_>) -> Result<SubmitJobResponse, MidjourneyError> {
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox139)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let referer = format!("https://{}", req.hostname.as_str());
|
||||
|
||||
// NB: Other recent clients use /api/app/submit-jobs, but this appears invalid.
|
||||
let url = format!("https://{}/api/submit-jobs", req.hostname.as_str());
|
||||
|
||||
let cookie_header = req.cookie_header.trim();
|
||||
|
||||
if cookie_header.len() < 20 {
|
||||
error!("Cookie header is too short (len: {}): {}", cookie_header.len(), cookie_header);
|
||||
return Err(MidjourneyClientError::CookieTooShort.into());
|
||||
}
|
||||
|
||||
let mut http_request = client.post(url)
|
||||
.header("cookie", cookie_header)
|
||||
.header("Referer", &referer)
|
||||
.header("Referrer-Policy", "origin-when-cross-origin")
|
||||
.header("accept", "*/*")
|
||||
.header("accept-language", "en-US,en;q=0.8")
|
||||
.header("content-type", "application/json")
|
||||
.header("priority", "u=1, i")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin")
|
||||
.header("x-csrf-protection", "1");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct F {
|
||||
mode: String,
|
||||
private: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct Metadata {
|
||||
imagePrompts: u8,
|
||||
imageReferences: u8,
|
||||
characterReferences: u8,
|
||||
depthReferences: u8,
|
||||
lightboxOpen: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawRequest {
|
||||
f: F,
|
||||
channelId: String,
|
||||
metadata: Metadata,
|
||||
t: String,
|
||||
prompt: String,
|
||||
}
|
||||
|
||||
let body = RawRequest {
|
||||
f: F {
|
||||
mode: "fast".to_string(),
|
||||
private: false,
|
||||
},
|
||||
channelId: req.channel_id.to_string(),
|
||||
metadata: Metadata {
|
||||
imagePrompts: 0,
|
||||
imageReferences: 0,
|
||||
characterReferences: 0,
|
||||
depthReferences: 0,
|
||||
lightboxOpen: "".to_string(),
|
||||
},
|
||||
t: "imagine".to_string(),
|
||||
prompt: req.prompt.to_string(),
|
||||
};
|
||||
|
||||
let http_request = http_request
|
||||
.json(&body)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
// if status == 301 {
|
||||
// for (name, value) in response.headers().iter() {
|
||||
// println!("- {}: {}", name.as_str(), value.to_str().unwrap());
|
||||
// }
|
||||
// }
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
if let Err(err) = filter_cloudflare_errors(status.as_u16(), &response_body) {
|
||||
return Err(MidjourneyApiError::CloudflareError(err).into());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
{"success":[
|
||||
{
|
||||
"job_id":"UUID-like-string",
|
||||
"prompt":"prompt",
|
||||
"is_queued":false,
|
||||
"event_type":"diffusion",
|
||||
"flags":{
|
||||
"mode":"fast","visibility":"public"
|
||||
},
|
||||
"meta":{
|
||||
"height":1024,"width":1024,"batch_size":4,"parent_id":null,"parent_grid":null
|
||||
},
|
||||
"optimisticJobIndex":0,
|
||||
"personalization_codes":null
|
||||
}],
|
||||
"failure":[]}
|
||||
*/
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SuccessPayload {
|
||||
job_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FailurePayload {
|
||||
r#type: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawResponse {
|
||||
success: Vec<SuccessPayload>,
|
||||
failure: Option<Vec<FailurePayload>>,
|
||||
}
|
||||
|
||||
let response = serde_json::from_str::<RawResponse>(response_body)
|
||||
.map_err(|err| MidjourneyApiError::DeserializationError(err))?;
|
||||
|
||||
let maybe_job_id = response.success
|
||||
.get(0)
|
||||
.map(|s| s.job_id.clone());
|
||||
|
||||
if maybe_job_id.is_none() {
|
||||
warn!("No job id found in body: {:?}", response_body);
|
||||
}
|
||||
|
||||
let mut maybe_errors = None;
|
||||
|
||||
if let Some(failures) = response.failure.as_ref() {
|
||||
if !failures.is_empty() {
|
||||
maybe_errors = Some(failures.iter()
|
||||
.map(|f| SubmitJobError {
|
||||
error_type: Some(f.r#type.clone()),
|
||||
message: Some(f.message.clone()),
|
||||
}).collect());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SubmitJobResponse {
|
||||
maybe_job_id,
|
||||
maybe_errors,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::endpoints::submit_job::{submit_job, SubmitJobRequest};
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
|
||||
// Get channel id via:
|
||||
// https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=[TOKEN]
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
let channel_id = read_to_trimmed_string("/Users/bt/secrets/midjourney/channel_id.txt")?;
|
||||
|
||||
let result = submit_job(SubmitJobRequest {
|
||||
prompt: "a modern n64 console",
|
||||
channel_id: &channel_id,
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}", result);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use log::error;
|
||||
use serde::Deserialize;
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const USER_CONNECTIONS_URL :&str = "https://www.midjourney.com/api/user-connections";
|
||||
|
||||
pub struct GetUserConnectionsRequest {
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserData {
|
||||
pub provider_id: String,
|
||||
pub display_name: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub user_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Provider {
|
||||
pub provider_id: String,
|
||||
pub user_data: Option<UserData>,
|
||||
pub is_linked: bool,
|
||||
}
|
||||
|
||||
pub async fn user_connections(req: GetUserConnectionsRequest) -> Result<Vec<Provider>, MidjourneyError> {
|
||||
|
||||
let referer = format!("https://{}", req.hostname.as_str());
|
||||
|
||||
let url = format!("https://{}/api/user-connections", req.hostname.as_str());
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox139)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let cookie_header = req.cookie_header.trim();
|
||||
|
||||
if cookie_header.len() < 20 {
|
||||
error!("Cookie header is too short (len: {}): {}", cookie_header.len(), cookie_header);
|
||||
return Err(MidjourneyClientError::CookieTooShort.into());
|
||||
}
|
||||
|
||||
// NB: missing headers that were in the browser request:
|
||||
// -H 'sec-ch-ua-platform: "macOS"' \
|
||||
// -H 'sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"' \
|
||||
|
||||
let mut http_request = client.get(url)
|
||||
.header("cookie", cookie_header)
|
||||
.header("Referer", &referer)
|
||||
.header("Referrer-Policy", "origin-when-cross-origin")
|
||||
.header("accept", "*/*")
|
||||
.header("accept-language", "en-US,en;q=0.8")
|
||||
.header("content-type", "application/json")
|
||||
.header("priority", "u=1, i")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin")
|
||||
.header("x-csrf-protection", "1");
|
||||
|
||||
let http_request = http_request
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
// if status == 301 {
|
||||
// for (name, value) in response.headers().iter() {
|
||||
// println!("- {}: {}", name.as_str(), value.to_str().unwrap());
|
||||
// }
|
||||
// }
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
if let Err(err) = filter_cloudflare_errors(status.as_u16(), &response_body) {
|
||||
return Err(MidjourneyApiError::CloudflareError(err).into());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[
|
||||
{
|
||||
"providerId": "google.com",
|
||||
"userData": {
|
||||
"providerId": "google.com",
|
||||
"displayName": "John Doe",
|
||||
"email": "john.doe@gmail.com",
|
||||
"userId": "123456789000000001",
|
||||
},
|
||||
"isLinked": true
|
||||
},
|
||||
{
|
||||
"providerId": "discord.com",
|
||||
"isLinked": false
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawProviderUserData {
|
||||
providerId: String,
|
||||
displayName: Option<String>,
|
||||
email: Option<String>,
|
||||
userId: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawProvider {
|
||||
providerId: String,
|
||||
userData: Option<RawProviderUserData>,
|
||||
isLinked: bool,
|
||||
}
|
||||
|
||||
let response : Vec<RawProvider> = serde_json::from_str(response_body)
|
||||
.map_err(|err| MidjourneyApiError::DeserializationError(err))?;
|
||||
|
||||
let providers = response
|
||||
.into_iter()
|
||||
.map(|r| Provider {
|
||||
provider_id: r.providerId,
|
||||
user_data: r.userData.map(|u| UserData {
|
||||
provider_id: u.providerId,
|
||||
display_name: u.displayName,
|
||||
email: u.email,
|
||||
user_id: u.userId,
|
||||
}),
|
||||
is_linked: r.isLinked,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(providers)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::endpoints::user_connections::{user_connections, GetUserConnectionsRequest};
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
|
||||
let result = user_connections(GetUserConnectionsRequest {
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}\n\n", result);
|
||||
|
||||
for provider in result {
|
||||
println!("Provider: {:?}", provider);
|
||||
}
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
use cloudflare_errors::cloudflare_error::CloudflareError;
|
||||
use errors::AnyhowError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MidjourneyApiError {
|
||||
/// There was no user ID in an otherwise valid-looking response.
|
||||
NoUserId,
|
||||
|
||||
/// No user props in index HTML payload.
|
||||
/// We need this for the user ID and the websocket token.
|
||||
NoUserProps,
|
||||
|
||||
/// No initialAuthUser in the index HTML payload.
|
||||
/// We need this for the user ID and the websocket token.
|
||||
NoInitialAuthUser,
|
||||
|
||||
/// 400. The request was invalid.
|
||||
InvalidRequest(String),
|
||||
|
||||
/// 401. The request was not authorized.
|
||||
Unauthorized(String),
|
||||
|
||||
/// 403. The request was forbidden.
|
||||
Forbidden(String),
|
||||
|
||||
/// 404. The requested resource was not found.
|
||||
NotFound(String),
|
||||
|
||||
/// 429. Too many requests.
|
||||
TooManyRequests(String),
|
||||
|
||||
/// 500. An internal server error occurred.
|
||||
InternalServerError {
|
||||
body: String,
|
||||
backend_hostname: Option<String>,
|
||||
},
|
||||
|
||||
/// Eg. when downloading images
|
||||
UnknownHttpFailure {
|
||||
status_code: u16,
|
||||
body: String,
|
||||
},
|
||||
|
||||
/// Cloudflare errors.
|
||||
CloudflareError(CloudflareError),
|
||||
|
||||
/// A deserialization error with the response.
|
||||
DeserializationError(serde_json::Error),
|
||||
|
||||
/// The request timed out.
|
||||
Timeout(String),
|
||||
|
||||
/// A network error occurred.
|
||||
NetworkError(String),
|
||||
|
||||
/// An error doing file I/O (on our side)
|
||||
IoError(io::Error),
|
||||
|
||||
/// Another type of error.
|
||||
Other(AnyhowError),
|
||||
}
|
||||
|
||||
impl Error for MidjourneyApiError {}
|
||||
|
||||
impl Display for MidjourneyApiError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
// Response body errors
|
||||
Self::NoUserId => write!(f, "No user ID found in the response body."),
|
||||
Self::NoUserProps => write!(f, "No user properties found in the index HTML payload."),
|
||||
Self::NoInitialAuthUser => write!(f, "No initialAuthUser in the index HTML payload."),
|
||||
// Server response code errors
|
||||
Self::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
|
||||
Self::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
|
||||
Self::Forbidden(msg) => write!(f, "Forbidden: {}", msg),
|
||||
Self::NotFound(msg) => write!(f, "Not found: {}", msg),
|
||||
Self::TooManyRequests(msg) => write!(f, "Too many requests: {}", msg),
|
||||
Self::InternalServerError {body, backend_hostname} =>
|
||||
write!(f, "Internal Server Error; backend hostname: {:?} ; body: {}; ", backend_hostname, body),
|
||||
Self::UnknownHttpFailure {status_code, body} =>
|
||||
write!(f, "Unknown HTTP failure; status code: {}; body: {}", status_code, body),
|
||||
// Deserialization errors
|
||||
// Server response handling errors
|
||||
Self::DeserializationError(error) => write!(f, "Deserialization error: {}", error),
|
||||
// Network errors
|
||||
Self::Timeout(msg) => write!(f, "Timeout: {}", msg),
|
||||
Self::NetworkError(msg) => write!(f, "Network error: {}", msg),
|
||||
// Cloudflare errors
|
||||
Self::CloudflareError(error) => write!(f, "Cloudflare Error: {}", error),
|
||||
// I/O errors
|
||||
Self::IoError(error) => write!(f, "IO error: {}", error),
|
||||
// Other
|
||||
Self::Other(error) => write!(f, "Other error: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for MidjourneyApiError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
Self::DeserializationError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for MidjourneyApiError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
Self::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CloudflareError> for MidjourneyApiError {
|
||||
fn from(error: CloudflareError) -> Self {
|
||||
Self::CloudflareError(error)
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// An error arising from the client, either before or after the request is sent.
|
||||
#[derive(Debug)]
|
||||
pub enum MidjourneyClientError {
|
||||
/// The cookie was too short to be valid.
|
||||
CookieTooShort,
|
||||
|
||||
/// Tried to look up an image out of range [0,4)
|
||||
InvalidImageIndex,
|
||||
|
||||
/// Could not determine the filetype for the file.
|
||||
FileTypeNotKnown(PathBuf),
|
||||
|
||||
/// Could not handle the file type.
|
||||
FileTypeNotHandled(PathBuf),
|
||||
|
||||
/// An error doing file I/O (on our side)
|
||||
IoError(io::Error),
|
||||
|
||||
// /// An error setting up the client
|
||||
// ReqwestError(reqwest::Error),
|
||||
WreqError(wreq::Error),
|
||||
|
||||
/// A serialization error with the request.
|
||||
SerializationError(serde_json::Error),
|
||||
}
|
||||
|
||||
impl Error for MidjourneyClientError {}
|
||||
|
||||
impl Display for MidjourneyClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CookieTooShort => write!(f, "The cookie header is too short to be valid."),
|
||||
Self::InvalidImageIndex => write!(f, "The image index is out of range. Valid range is [0, 4)."),
|
||||
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::WreqError(err) => write!(f, "Wreq client error: {}", err),
|
||||
Self::SerializationError(err) => write!(f, "Request serialization error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for MidjourneyClientError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
Self::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for MidjourneyClientError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
Self::SerializationError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/*impl From<reqwest::Error> for MidjourneyClientError {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
Self::ReqwestError(error)
|
||||
}
|
||||
}*/
|
||||
|
||||
impl From<wreq::Error> for MidjourneyClientError {
|
||||
fn from(error: wreq::Error) -> Self {
|
||||
Self::WreqError(error)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MidjourneyError {
|
||||
Api(MidjourneyApiError),
|
||||
Client(MidjourneyClientError),
|
||||
}
|
||||
|
||||
impl Error for MidjourneyError {}
|
||||
|
||||
impl std::fmt::Display for MidjourneyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Api(e) => write!(f, "Midjourney API error: {:?}", e),
|
||||
Self::Client(e) => write!(f, "Midjourney Client error: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MidjourneyApiError> for MidjourneyError {
|
||||
fn from(error: MidjourneyApiError) -> Self {
|
||||
Self::Api(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MidjourneyClientError> for MidjourneyError {
|
||||
fn from(error: MidjourneyClientError) -> Self {
|
||||
Self::Client(error)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod midjourney_error;
|
||||
pub mod midjourney_api_error;
|
||||
pub mod midjourney_client_error;
|
||||
@@ -1,8 +0,0 @@
|
||||
//! A midjourney API client.
|
||||
|
||||
pub mod client;
|
||||
pub mod credentials;
|
||||
pub mod endpoints;
|
||||
pub mod error;
|
||||
pub mod recipes;
|
||||
pub mod utils;
|
||||
@@ -1,20 +0,0 @@
|
||||
use crate::credentials::midjourney_user_id::MidjourneyUserId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ChannelId {
|
||||
/// Channel based on the user ID.
|
||||
/// This is what Midjourney uses by default.
|
||||
UserId(MidjourneyUserId),
|
||||
|
||||
// Bare string ID
|
||||
Raw(String),
|
||||
}
|
||||
|
||||
impl ChannelId {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::UserId(user_id) => format!("singleplayer_{}", user_id.0),
|
||||
Self::Raw(id) => id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::credentials::midjourney_user_id::MidjourneyUserId;
|
||||
use crate::endpoints::storage_list::{storage_list, GetStorageListRequest};
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
|
||||
pub struct GetMidjourneyUserIdRequest {
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
pub async fn get_midjourney_user_id_from_storage(req: GetMidjourneyUserIdRequest) -> Result<MidjourneyUserId, MidjourneyError> {
|
||||
let items = storage_list(GetStorageListRequest {
|
||||
hostname: req.hostname,
|
||||
cookie_header: req.cookie_header,
|
||||
}).await?;
|
||||
|
||||
let user_id = items
|
||||
.into_iter()
|
||||
.find_map(|item| item.bucket_pathname)
|
||||
.and_then(|path| path.split('/').next().map(|id| id.to_owned()))
|
||||
.map(|id| MidjourneyUserId(id.to_string()))
|
||||
.ok_or(MidjourneyApiError::NoUserId)?;
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::recipes::get_midjourney_user_id_from_storage::{get_midjourney_user_id_from_storage, GetMidjourneyUserIdRequest};
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
|
||||
let result = get_midjourney_user_id_from_storage(GetMidjourneyUserIdRequest {
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}\n\n", result);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::credentials::midjourney_user_id::MidjourneyUserId;
|
||||
use crate::endpoints::get_index_page_html::{get_index_page_html, GetIndexPageRequest};
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use dom_query::Document;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct GetUserInfoRequest {
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetUserInfoResponse {
|
||||
pub user_id: Option<MidjourneyUserId>,
|
||||
pub email: Option<String>,
|
||||
pub websocket_token: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_user_info(req: GetUserInfoRequest) -> Result<GetUserInfoResponse, MidjourneyError> {
|
||||
let index_html = get_index_page_html(GetIndexPageRequest {
|
||||
hostname: req.hostname,
|
||||
cookie_header: req.cookie_header,
|
||||
}).await?;
|
||||
|
||||
/*
|
||||
<script id="initialProps" type="application/json">
|
||||
{
|
||||
"initialAuthUser": {
|
||||
"idpID": "ENTROPY",
|
||||
"id": "UUID",
|
||||
"displayName": "uNUMBER",
|
||||
"email": "USER@gmail.com",
|
||||
"emailVerified": true,
|
||||
"photoURL": "https://lh3.googleusercontent.com/a/ENTROPY",
|
||||
"abilities": {
|
||||
"admin": false,
|
||||
"developer": false,
|
||||
"accepted_tos": true,
|
||||
"moderator": false,
|
||||
"guide": false,
|
||||
"vip": false,
|
||||
"employee": false,
|
||||
"content_moderator": false,
|
||||
"gdpr_preferences_set": false,
|
||||
"gdpr_allow_advertising": false,
|
||||
"gdpr_allow_analytics": false,
|
||||
"editor_whitelist": false,
|
||||
"allow_nsfw": false,
|
||||
"cooldowns_removed": false,
|
||||
"blocked": false,
|
||||
"community": false,
|
||||
"billing": false,
|
||||
"system_verified": false,
|
||||
"simplified_flow": false,
|
||||
"total_jobs": 57,
|
||||
"is_sub_at_least_one_year_old": false,
|
||||
"can_private": false,
|
||||
"can_relax": false,
|
||||
"has_discord": false,
|
||||
"delete_at": null,
|
||||
"subscription": {
|
||||
"type": "subscription",
|
||||
"plan_type": "basic",
|
||||
"recurring": "month"
|
||||
},
|
||||
"relax_video_job_concurrency": 0
|
||||
},
|
||||
"websocketToken": "WEBSOCKET_TOKEN_ENTROPY",
|
||||
"providers": [
|
||||
"google.com"
|
||||
],
|
||||
"system": "firebase",
|
||||
"streamToken": "STREAM_TOKEN_ENTROPY"
|
||||
},
|
||||
"urlFromInitialServerRequest": "/"
|
||||
}
|
||||
</script>
|
||||
*/
|
||||
|
||||
let document = Document::from(index_html);
|
||||
let maybe_nodes = document.select("script[id=initialProps]");
|
||||
|
||||
let node = match maybe_nodes.get(0) {
|
||||
Some(node) => node,
|
||||
None => {
|
||||
return Err(MidjourneyApiError::NoUserProps.into());
|
||||
}
|
||||
};
|
||||
|
||||
let inner_json = node.inner_html().to_string();
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawAuthUser {
|
||||
// NB: The Midjourney user id.
|
||||
id: Option<String>,
|
||||
email: Option<String>,
|
||||
websocketToken: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawBody {
|
||||
initialAuthUser: Option<RawAuthUser>,
|
||||
}
|
||||
|
||||
let response : RawBody = serde_json::from_str(&inner_json)
|
||||
.map_err(|err| MidjourneyApiError::DeserializationError(err))?;
|
||||
|
||||
let props = match response.initialAuthUser {
|
||||
Some(props) => props,
|
||||
None => {
|
||||
return Err(MidjourneyApiError::NoInitialAuthUser.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(GetUserInfoResponse {
|
||||
user_id: props.id
|
||||
.map(|id| MidjourneyUserId::from_string(id)),
|
||||
email: props.email
|
||||
.map(|email| email.to_string()),
|
||||
websocket_token: props.websocketToken
|
||||
.map(|email| email.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::recipes::get_user_info::{get_user_info, GetUserInfoRequest};
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?;
|
||||
|
||||
let result = get_user_info(GetUserInfoRequest {
|
||||
cookie_header,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
}).await?;
|
||||
|
||||
println!("Response: {:?}\n\n", result);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
mod get_midjourney_user_id_from_storage;
|
||||
pub mod channel_id;
|
||||
pub mod get_user_info;
|
||||
pub mod text_to_image;
|
||||
@@ -1,49 +0,0 @@
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::endpoints::submit_job::{submit_job, SubmitJobRequest};
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use crate::recipes::channel_id::ChannelId;
|
||||
|
||||
pub struct TextToImageRequest<'a> {
|
||||
pub prompt: &'a str,
|
||||
pub channel_id: &'a ChannelId,
|
||||
pub hostname: MidjourneyHostname,
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextToImageResponse {
|
||||
/// On success, the job ID is returned.
|
||||
pub maybe_job_id: Option<String>,
|
||||
|
||||
/// On error, we have a list of error messages.
|
||||
pub maybe_errors: Option<Vec<TextToImageError>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextToImageError {
|
||||
pub error_type: Option<String>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
/// Slightly more ergonomic text-to-image API.
|
||||
/// As we add more `submit_job()` cases, we'll keep this simple.
|
||||
pub async fn text_to_image(req: TextToImageRequest<'_>) -> Result<TextToImageResponse, MidjourneyError> {
|
||||
let channel_id = req.channel_id.to_string();
|
||||
|
||||
let response = submit_job(SubmitJobRequest {
|
||||
prompt: req.prompt,
|
||||
channel_id: &channel_id,
|
||||
hostname: req.hostname,
|
||||
cookie_header: req.cookie_header,
|
||||
}).await?;
|
||||
|
||||
Ok(TextToImageResponse {
|
||||
maybe_job_id: response.maybe_job_id,
|
||||
maybe_errors: response.maybe_errors.map(|errs| {
|
||||
errs.into_iter().map(|e| TextToImageError {
|
||||
error_type: e.error_type,
|
||||
message: e.message,
|
||||
}).collect()
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
|
||||
pub fn get_image_url(job_id: &str, image_index: u8) -> Result<String, MidjourneyError> {
|
||||
if image_index > 3 {
|
||||
return Err(MidjourneyClientError::InvalidImageIndex.into())
|
||||
}
|
||||
Ok(format!("https://cdn.midjourney.com/{}/0_{}.png", job_id, image_index))
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
use crate::error::midjourney_api_error::MidjourneyApiError;
|
||||
use crate::error::midjourney_client_error::MidjourneyClientError;
|
||||
use crate::error::midjourney_error::MidjourneyError;
|
||||
use crate::utils::get_image_url::get_image_url;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImageDownloaderClient {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl ImageDownloaderClient {
|
||||
pub fn create() -> Result<Self, MidjourneyClientError> {
|
||||
Ok(Self {
|
||||
client: Client::builder()
|
||||
.emulation(Emulation::Firefox139)
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn download_image(&self, job_id: &str, image_index: u8) -> anyhow::Result<Vec<u8>, MidjourneyError> {
|
||||
let url = get_image_url(job_id, image_index)?;
|
||||
|
||||
// TODO: Cookies
|
||||
// TODO: Cache control headers?
|
||||
let mut http_request = self.client.get(url)
|
||||
//.header("Referrer-Policy", "origin-when-cross-origin")
|
||||
//.header("content-type", "application/json")
|
||||
//.header("sec-fetch-user", "?1")
|
||||
//.header("upgrade-insecure-requests", "1");
|
||||
.header("Referrer", "https://www.midjourney.com/")
|
||||
.header("accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
|
||||
.header("accept-language", "en-US,en;q=0.8")
|
||||
.header("priority", "i")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-fetch-dest", "image")
|
||||
.header("sec-fetch-mode", "no-cors")
|
||||
.header("sec-fetch-site", "same-site");
|
||||
|
||||
let http_request = http_request
|
||||
.build()
|
||||
.map_err(|err| MidjourneyClientError::WreqError(err))?;
|
||||
|
||||
let response = self.client.execute(http_request)
|
||||
.await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_bytes = response.bytes().await
|
||||
.map_err(|e| MidjourneyApiError::NetworkError(e.to_string()))?;
|
||||
|
||||
if !status.is_success() {
|
||||
let response_body = String::from_utf8_lossy(&response_bytes).to_string();
|
||||
if let Err(err) = filter_cloudflare_errors(status.as_u16(), &response_body) {
|
||||
return Err(MidjourneyApiError::CloudflareError(err).into());
|
||||
}
|
||||
|
||||
return Err(MidjourneyApiError::UnknownHttpFailure {
|
||||
status_code: status.as_u16(),
|
||||
body: response_body,
|
||||
}.into());
|
||||
}
|
||||
|
||||
Ok(response_bytes.to_vec())
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod get_image_url;
|
||||
pub mod image_downloader_client;
|
||||
@@ -59,7 +59,7 @@ impl Display for ApiError {
|
||||
ApiError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
|
||||
ApiError::Forbidden(msg) => write!(f, "Forbidden: {}", msg),
|
||||
ApiError::NotFound(msg) => write!(f, "Not found: {}", msg),
|
||||
ApiError::TooManyRequests(msg) => write!(f, "Too many requests to Storyteller backend: {}", msg),
|
||||
ApiError::TooManyRequests(msg) => write!(f, "Too many requests: {}", msg),
|
||||
ApiError::InternalServerError {body, backend_hostname} =>
|
||||
write!(f, "Internal Server Error; backend hostname: {:?} ; body: {}; ", backend_hostname, body),
|
||||
// Server response handling errors
|
||||
|
||||
@@ -7,9 +7,6 @@ use crate::error::api_error::ApiError;
|
||||
/// An error arising from the client, either before or after the request is sent.
|
||||
#[derive(Debug)]
|
||||
pub enum ClientError {
|
||||
/// An invalid request was detected before it was sent.
|
||||
InvalidPreflightRequest(String),
|
||||
|
||||
/// Could not determine the filetype for the file.
|
||||
FileTypeNotKnown(PathBuf),
|
||||
|
||||
@@ -31,7 +28,6 @@ impl Error for ClientError {}
|
||||
impl Display for ClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidPreflightRequest(msg) => write!(f, "Invalid preflight request: {}", msg),
|
||||
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),
|
||||
|
||||
@@ -13,8 +13,8 @@ impl Error for StorytellerError {}
|
||||
impl std::fmt::Display for StorytellerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
StorytellerError::Api(e) => write!(f, "Storyteller API error: {:?}", e),
|
||||
StorytellerError::Client(e) => write!(f, "Storyteller Client error: {:?}", e),
|
||||
StorytellerError::Api(e) => write!(f, "API error: {:?}", e),
|
||||
StorytellerError::Client(e) => write!(f, "Client error: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::{FluxDevJuggernautInpaintImageRequest, FluxDevJuggernautInpaintImageResponse, FLUX_DEV_JUGGERNAUT_INPAINT_PATH};
|
||||
|
||||
pub async fn flux_dev_juggernaut_inpaint_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: FluxDevJuggernautInpaintImageRequest,
|
||||
) -> Result<FluxDevJuggernautInpaintImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
FLUX_DEV_JUGGERNAUT_INPAINT_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
pub mod flux_pro_1_inpaint_image;
|
||||
pub mod flux_dev_juggernaut_inpaint_image;
|
||||
pub mod flux_pro_1_inpaint_image;
|
||||
@@ -5,6 +5,6 @@ pub mod error;
|
||||
pub mod generate;
|
||||
pub mod jobs;
|
||||
pub mod media_files;
|
||||
pub mod prompts;
|
||||
pub mod recipes;
|
||||
pub mod shared_response_types;
|
||||
pub mod utils;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::shared_response_types::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::shared_response_types::media_links::MediaLinks;
|
||||
use crate::shared_response_types::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::shared_response_types::user_details_light::UserDetailsLight;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use crate::utils::http_get_anonymous::http_get_anonymous;
|
||||
use artcraft_api_defs::common::responses::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use artcraft_api_defs::common::responses::simple_entity_stats::SimpleEntityStats;
|
||||
use artcraft_api_defs::common::responses::user_details_light::UserDetailsLight;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_get_request::basic_json_get_request;
|
||||
use artcraft_api_defs::media_file::list_batch_generated_media_files::{ListBatchGeneratedReduxMediaFilesSuccessResponse, LIST_BATCH_GENERATED_REDUX_MEDIA_FILES_URL_PATH};
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
|
||||
pub async fn list_batch_generated_redux_media_files(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
batch_token: &BatchGenerationToken,
|
||||
) -> Result<ListBatchGeneratedReduxMediaFilesSuccessResponse, StorytellerError> {
|
||||
let url = format!("{}/{}", LIST_BATCH_GENERATED_REDUX_MEDIA_FILES_URL_PATH, batch_token.to_string());
|
||||
Ok(basic_json_get_request(
|
||||
api_host,
|
||||
&url,
|
||||
maybe_creds,
|
||||
).await?)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod get_media_file;
|
||||
pub mod list_batch_generated_redux_media_files;
|
||||
pub mod upload_image_media_file_from_bytes;
|
||||
pub mod upload_image_media_file_from_file;
|
||||
pub mod upload_new_engine_asset_from_file;
|
||||
|
||||
+4
@@ -2,6 +2,10 @@ 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::shared_response_types::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::shared_response_types::media_links::MediaLinks;
|
||||
use crate::shared_response_types::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::shared_response_types::user_details_light::UserDetailsLight;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
|
||||
+4
-27
@@ -2,6 +2,10 @@ 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::shared_response_types::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::shared_response_types::media_links::MediaLinks;
|
||||
use crate::shared_response_types::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::shared_response_types::user_details_light::UserDetailsLight;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
@@ -12,7 +16,6 @@ use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::visibility::Visibility;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
|
||||
@@ -39,14 +42,6 @@ pub struct UploadImageFromFileArgs<'a, P: AsRef<Path>> {
|
||||
|
||||
/// If true, we should hide the image from the user's gallery.
|
||||
pub is_intermediate_system_file: bool,
|
||||
|
||||
/// If provided, this is the prompt that this image is associated with.
|
||||
/// NOTE: Cannot set `is_intermediate_system_file = true` if this is set.
|
||||
pub maybe_prompt_token: Option<&'a PromptToken>,
|
||||
|
||||
// /// If provided, this is the service provider that created the image.
|
||||
// /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set.
|
||||
// pub maybe_generation_provider: Option<GenerationProvider>,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +49,6 @@ pub struct UploadImageFromFileArgs<'a, P: AsRef<Path>> {
|
||||
pub async fn upload_image_media_file_from_file<P: AsRef<Path>>(
|
||||
args: UploadImageFromFileArgs<'_, P>,
|
||||
) -> Result<UploadImageMediaFileSuccessResponse, StorytellerError> {
|
||||
|
||||
validate_args(&args)?;
|
||||
|
||||
let url = get_route(args.api_host);
|
||||
|
||||
@@ -77,10 +70,6 @@ pub async fn upload_image_media_file_from_file<P: AsRef<Path>>(
|
||||
if args.is_intermediate_system_file {
|
||||
form = form.text("is_intermediate_system_file", "true");
|
||||
}
|
||||
|
||||
if let Some(prompt_token) = &args.maybe_prompt_token {
|
||||
form = form.text("maybe_prompt_token", prompt_token.to_string());
|
||||
}
|
||||
|
||||
let mut request_builder = client.post(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
@@ -120,15 +109,3 @@ pub struct UploadImageMediaFileSuccessResponse {
|
||||
pub success: bool,
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
fn validate_args<P: AsRef<Path>>(
|
||||
args: &UploadImageFromFileArgs<'_, P>,
|
||||
) -> Result<(), StorytellerError> {
|
||||
if args.is_intermediate_system_file && args.maybe_prompt_token.is_some() {
|
||||
return Err(StorytellerError::Client(ClientError::InvalidPreflightRequest(
|
||||
"Cannot set `is_intermediate_system_file` to true if `maybe_prompt_token` is provided."
|
||||
.to_string())));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+4
@@ -1,5 +1,9 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::shared_response_types::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::shared_response_types::media_links::MediaLinks;
|
||||
use crate::shared_response_types::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::shared_response_types::user_details_light::UserDetailsLight;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
|
||||
+4
@@ -1,5 +1,9 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::shared_response_types::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::shared_response_types::media_links::MediaLinks;
|
||||
use crate::shared_response_types::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::shared_response_types::user_details_light::UserDetailsLight;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::prompts::create_prompt::{CreatePromptRequest, CreatePromptResponse, CREATE_PROMPT_PATH};
|
||||
|
||||
pub async fn create_prompt(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: CreatePromptRequest,
|
||||
) -> Result<CreatePromptResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
CREATE_PROMPT_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod create_prompt;
|
||||
@@ -10,7 +10,6 @@ use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use serde_derive::Deserialize;
|
||||
use std::path::Path;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UploadMediaFileSuccessResponse {
|
||||
@@ -23,7 +22,6 @@ pub async fn upload_media_file_from_file<P: AsRef<Path>>(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
path: P,
|
||||
maybe_prompt_token: Option<&PromptToken>,
|
||||
) -> Result<UploadMediaFileSuccessResponse, StorytellerError> {
|
||||
|
||||
let maybe_type = MimetypeInfo::get_for_path(path.as_ref())
|
||||
@@ -56,7 +54,6 @@ pub async fn upload_media_file_from_file<P: AsRef<Path>>(
|
||||
maybe_creds,
|
||||
path,
|
||||
is_intermediate_system_file: false,
|
||||
maybe_prompt_token,
|
||||
}).await;
|
||||
match result {
|
||||
Ok(result) => Ok(UploadMediaFileSuccessResponse {
|
||||
|
||||
+2
-5
@@ -1,11 +1,8 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_derive::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
// TODO(bt,2025-08-20): Replace the storyteller-web version of this.
|
||||
// All it needs is the impl ported into a builder.
|
||||
|
||||
/// Cover image links can only be jpg, png, etc. No videos.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CoverImageLinks {
|
||||
/// Primary link to the cover image via the CDN.
|
||||
pub cdn_url: Url,
|
||||
+11
-14
@@ -1,22 +1,19 @@
|
||||
use crate::common::responses::cover_image_links::CoverImageLinks;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
// TODO(bt,2025-08-20): Replace the storyteller-web version of this.
|
||||
// We can't drop the old version just yet due to the `impl` (easy)
|
||||
// and a few differing fields.
|
||||
use serde_derive::{Deserialize};
|
||||
use url::Url;
|
||||
use crate::shared_response_types::cover_image_links::CoverImageLinks;
|
||||
|
||||
/// Everything we need to create a cover image.
|
||||
/// Cover images are small descriptive images that can be set for any media file.
|
||||
/// If a cover image is set, this is the path to the asset.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct MediaFileCoverImageDetails {
|
||||
// /// (DEPRECATED) URL path to the media file
|
||||
// #[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
|
||||
// pub maybe_cover_image_public_bucket_path: Option<String>,
|
||||
// /// (DEPRECATED) URL path to the media file
|
||||
// #[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
|
||||
// pub maybe_cover_image_public_bucket_path: Option<String>,
|
||||
|
||||
// /// (DEPRECATED) Full URL to the media file
|
||||
// #[deprecated(note="This points to the bucket. Use media_links instead to leverage the CDN.")]
|
||||
// pub maybe_cover_image_public_bucket_url: Option<Url>,
|
||||
// /// (DEPRECATED) Full URL to the media file
|
||||
// #[deprecated(note="This points to the bucket. Use media_links instead to leverage the CDN.")]
|
||||
// pub maybe_cover_image_public_bucket_url: Option<Url>,
|
||||
|
||||
// NB(bt,2024-09-19): I accidentally rolled this field out to production.
|
||||
// I don't think this field is in use, but maybe ...
|
||||
@@ -36,7 +33,7 @@ pub struct MediaFileCoverImageDetails {
|
||||
|
||||
/// The default cover is composed of an image and color pair that are
|
||||
/// predefined by the frontend.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct MediaFileDefaultCover {
|
||||
pub image_index: u8,
|
||||
pub color_index: u8,
|
||||
@@ -0,0 +1,34 @@
|
||||
use serde_derive::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
/// Links to media file locations (bucket, CDN, etc.)
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct MediaLinks {
|
||||
/// Primary link to the asset via the CDN.
|
||||
pub cdn_url: Url,
|
||||
|
||||
/// Template to construct thumbnail URLs.
|
||||
/// Replace the string `{WIDTH}` with the desired width.
|
||||
/// Only relevant for image media files. (Video media files instead have
|
||||
/// video previews, which, in turn, have their own thumbnail templates.)
|
||||
pub maybe_thumbnail_template: Option<String>,
|
||||
|
||||
/// Video preview images (still and animated gif) for mp4 video files.
|
||||
/// These are only set for video media files.
|
||||
/// These are not set for "cover images" that are gif or webms, but rather video files.
|
||||
pub maybe_video_previews: Option<VideoPreviews>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct VideoPreviews {
|
||||
/// A static single frame preview image of the video.
|
||||
pub still: Url,
|
||||
/// An animated gif preview of the video.
|
||||
pub animated: Url,
|
||||
/// A template used to construct the still thumbnail URL.
|
||||
/// Replace the string `{WIDTH}` with the desired width.
|
||||
pub still_thumbnail_template: String,
|
||||
/// A template used to construct the animated thumbnail URL.
|
||||
/// Replace the string `{WIDTH}` with the desired width.
|
||||
pub animated_thumbnail_template: String,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod cover_image_links;
|
||||
pub mod media_file_cover_image_details;
|
||||
pub mod media_links;
|
||||
pub mod simple_entity_stats;
|
||||
pub mod user_details_light;
|
||||
+2
-4
@@ -1,9 +1,7 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
// TODO(bt,2025-08-20): Replace the storyteller-web version of this.
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// Simple stats that can be attached to any entity
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SimpleEntityStats {
|
||||
/// Number of positive ratings (or "likes") for this item
|
||||
pub positive_rating_count: u32,
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_derive::Deserialize;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// Everything we need to refer to a user on the public web interface.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UserDetailsLight {
|
||||
/// The token for the user
|
||||
pub user_token: UserToken,
|
||||
@@ -26,7 +26,7 @@ pub struct UserDetailsLight {
|
||||
// In the future, we'll also support user-uploaded avatars that we store on our servers.
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UserDefaultAvatarInfo {
|
||||
pub image_index: u8,
|
||||
pub color_index: u8,
|
||||
@@ -23,27 +23,25 @@ tauri-build = { version = "2.1.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs.workspace = true
|
||||
build_metadata.workspace = true
|
||||
cookie_store.workspace = true
|
||||
enums.workspace = true
|
||||
errors.workspace = true
|
||||
fal_client.workspace = true
|
||||
filesys.workspace = true
|
||||
idempotency.workspace = true
|
||||
images.workspace = true
|
||||
memory_store.workspace = true
|
||||
midjourney_client.workspace = true
|
||||
mimetypes.workspace = true
|
||||
openai_sora_client.workspace = true
|
||||
primitives.workspace = true
|
||||
artcraft_api_defs = { workspace = true }
|
||||
build_metadata = { workspace = true }
|
||||
enums = { workspace = true }
|
||||
errors = { workspace = true }
|
||||
fal_client = { workspace = true }
|
||||
filesys = { workspace = true }
|
||||
idempotency = { workspace = true }
|
||||
images = { workspace = true }
|
||||
memory_store = { workspace = true }
|
||||
mimetypes = { workspace = true }
|
||||
openai_sora_client = { workspace = true }
|
||||
primitives = { workspace = true }
|
||||
sqlite_tasks.workspace = true
|
||||
storyteller_client.workspace = true
|
||||
tokens.workspace = true
|
||||
web_base64.workspace = true
|
||||
storyteller_client = { workspace = true }
|
||||
tokens = { workspace = true }
|
||||
web_base64 = { workspace = true }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": ["main"],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"log:default",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-close",
|
||||
"upload:allow-download",
|
||||
"upload:default",
|
||||
"http:allow-fetch",
|
||||
@@ -25,4 +24,4 @@
|
||||
},
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
+5
-20
@@ -1,10 +1,9 @@
|
||||
use crate::core::commands::enqueue::image::generic::handle_image_artcraft::handle_image_artcraft;
|
||||
use crate::core::commands::enqueue::image::generic::handle_image_fal::handle_image_fal;
|
||||
use crate::core::commands::enqueue::image::gpt_image_1::handle_gpt_image_1::handle_gpt_image_1;
|
||||
use crate::core::commands::enqueue::image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::generic::handle_image_artcraft::handle_image_artcraft;
|
||||
use crate::core::commands::enqueue::text_to_image::generic::handle_image_fal::handle_image_fal;
|
||||
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1::handle_gpt_image_1;
|
||||
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::text_to_image::midjourney::handle_midjourney::handle_midjourney;
|
||||
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;
|
||||
@@ -19,7 +18,6 @@ use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
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;
|
||||
@@ -95,7 +93,6 @@ pub async fn enqueue_text_to_image_command(
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
mj_creds_manager: State<'_, MidjourneyCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
@@ -111,7 +108,6 @@ pub async fn enqueue_text_to_image_command(
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&mj_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
@@ -177,7 +173,6 @@ pub async fn handle_request(
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
@@ -194,7 +189,6 @@ pub async fn handle_request(
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
&mj_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
@@ -225,7 +219,6 @@ pub async fn dispatch_request(
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
@@ -246,14 +239,6 @@ pub async fn dispatch_request(
|
||||
sora_task_queue,
|
||||
).await;
|
||||
}
|
||||
Some(ImageModel::Midjourney) => {
|
||||
return handle_midjourney(
|
||||
app,
|
||||
request,
|
||||
app_env_configs,
|
||||
mj_creds_manager,
|
||||
).await;
|
||||
}
|
||||
_ => {
|
||||
// Fall-through
|
||||
}
|
||||
+4
-7
@@ -1,6 +1,6 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
@@ -57,11 +57,8 @@ pub async fn handle_image_artcraft(
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
ImageModel::GptImage1 |
|
||||
ImageModel::Midjourney
|
||||
) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
|
||||
Some(ImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: an earlier branch should have handled gpt-image-1 images for Artcraft")));
|
||||
}
|
||||
Some(ImageModel::Recraft3) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented in Artcraft")));
|
||||
+4
-7
@@ -1,6 +1,6 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
@@ -51,11 +51,8 @@ pub async fn handle_image_fal(
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
ImageModel::GptImage1 |
|
||||
ImageModel::Midjourney
|
||||
) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
|
||||
Some(ImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: fal is handling sora images")));
|
||||
}
|
||||
Some(
|
||||
ImageModel::Flux1Dev |
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::image::gpt_image_1::handle_gpt_image_1_artcraft::handle_gpt_image_1_artcraft;
|
||||
use crate::core::commands::enqueue::image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_artcraft::handle_gpt_image_1_artcraft;
|
||||
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
+8
-17
@@ -1,8 +1,7 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
@@ -10,7 +9,6 @@ use crate::services::sora::state::sora_credential_manager::SoraCredentialManager
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
@@ -28,19 +26,12 @@ pub async fn handle_gpt_image_1_sora(
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Sora credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(InternalImageError::NeedsSoraCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(InternalImageError::NeedsSoraCredentials);
|
||||
}
|
||||
};
|
||||
let mut creds = sora_creds_manager
|
||||
.get_credentials_required()
|
||||
.map_err(|err| {
|
||||
error!("EnqueueTextToImage Sora creds required: {:?}", err);
|
||||
InternalImageError::NeedsFalApiKey
|
||||
})?;
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
-10
@@ -2,7 +2,6 @@ use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use midjourney_client::error::midjourney_error::MidjourneyError;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
@@ -11,18 +10,9 @@ pub enum InternalImageError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
|
||||
NeedsMidjourneyCredentials,
|
||||
NeedsMidjourneyUserInfo,
|
||||
NeedsMidjourneyUserId,
|
||||
|
||||
/// Generic - possibly this came with an error message that we sent as an event.
|
||||
MidjourneyJobEnqueueFailed,
|
||||
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
MidjourneyError(MidjourneyError),
|
||||
SoraError(SoraError),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
pub mod enqueue_text_to_image_command;
|
||||
pub mod generic;
|
||||
pub mod internal_image_error;
|
||||
mod gpt_image_1;
|
||||
mod midjourney;
|
||||
mod gpt_image_1;
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, EnqueueTextToImageSuccessResponse};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal::handle_generic_bg_removal;
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
@@ -27,7 +29,6 @@ use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use errors::AnyhowError;
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
@@ -42,6 +43,7 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
+2
-1
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
|
||||
@@ -127,7 +129,6 @@ async fn upload_image_from_base64_bytes(
|
||||
maybe_creds: Some(&creds),
|
||||
path: temp_file,
|
||||
is_intermediate_system_file: true, // NB: Probably not essential to keep this.
|
||||
maybe_prompt_token: None, // NB: Not used for bg removal.
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
|
||||
+4
-23
@@ -1,5 +1,6 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, EnqueueTextToImageSuccessResponse};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::flux_kontext::handle_flux_kontext_edit::handle_flux_kontext_edit;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::handle_gpt_image_1_edit;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
@@ -41,8 +42,8 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::commands::enqueue::image_edit::flux_kontext::handle_flux_kontext_edit::handle_flux_kontext_edit;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueContextualEditImageCommand {
|
||||
@@ -73,21 +74,6 @@ pub struct EnqueueContextualEditImageCommand {
|
||||
|
||||
/// Image quality.
|
||||
pub image_quality: Option<EditImageQuality>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
@@ -253,12 +239,7 @@ pub async fn handle_request(
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller.clone(),
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref(),
|
||||
)
|
||||
.insert_into_task_database(task_database)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
|
||||
+4
-2
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::MAX_IMAGES;
|
||||
@@ -21,8 +23,6 @@ use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::Gener
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
@@ -33,6 +33,8 @@ use storyteller_client::generate::image::generate_flux_1_schnell_text_to_image::
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
use tauri::AppHandle;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
|
||||
pub async fn handle_gpt_image_1_edit_artcraft(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
|
||||
+6
-4
@@ -1,10 +1,11 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageSize, EnqueueContextualEditImageCommand, EnqueueContextualEditImageErrorType};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::MAX_IMAGES;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
@@ -56,16 +57,14 @@ pub async fn handle_gpt_image_1_edit_sora(
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
let sora_creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
warn!("No Sora credentials found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(InternalContextualEditImageError::NeedsSoraCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(InternalContextualEditImageError::AnyhowError(err));
|
||||
}
|
||||
};
|
||||
@@ -114,6 +113,9 @@ pub async fn handle_gpt_image_1_edit_sora(
|
||||
files_to_upload_to_sora.push(filename);
|
||||
}
|
||||
|
||||
|
||||
let mut creds = sora_creds_manager.get_credentials_required()?;
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
|
||||
+4
-19
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, EnqueueTextToImageSuccessResponse};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::handle_gpt_image_1_edit;
|
||||
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
|
||||
@@ -44,13 +46,10 @@ use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::commands::enqueue::image_inpaint::flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint;
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageInpaintModel {
|
||||
#[serde(rename = "flux_dev_juggernaut")]
|
||||
FluxDevJuggernaut,
|
||||
#[serde(rename = "flux_pro_1")]
|
||||
FluxPro1,
|
||||
}
|
||||
@@ -148,8 +147,8 @@ pub async fn enqueue_image_inpaint_command(
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueImageInpaintSuccessResponse, EnqueueInpaintImageErrorType> {
|
||||
|
||||
info!("enqueue_image_inpaint_command called; model: {:?}, image_media_token: {:?}, prompt: {:?}, image_count: {:?}",
|
||||
&request.model, &request.image_media_token, &request.prompt, &request.image_count);
|
||||
info!("enqueue_image_inpaint_command called; model: {:?}, image_media_token: {:?}, prompt: {:?}",
|
||||
&request.model, &request.image_media_token, &request.prompt);
|
||||
|
||||
let result = handle_request(
|
||||
&request,
|
||||
@@ -216,20 +215,6 @@ pub async fn handle_request(
|
||||
None => {
|
||||
return Err(InternalImageInpaintError::NoModelSpecified)
|
||||
}
|
||||
Some(ImageInpaintModel::FluxDevJuggernaut) => {
|
||||
handle_flux_dev_juggernaut_inpaint(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
}
|
||||
Some(ImageInpaintModel::FluxPro1) => {
|
||||
handle_flux_pro_1_inpaint(
|
||||
request,
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
|
||||
use crate::core::commands::enqueue::image_inpaint::flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint_artcraft::handle_flux_dev_juggernaut_inpaint_artcraft;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
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 log::info;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_flux_dev_juggernaut_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
// TODO: Check if providers are available before proceeding.
|
||||
|
||||
info!("Providers by priority: {:?}", priority);
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {
|
||||
// Fallthrough (for now)
|
||||
}
|
||||
Provider::Sora => {
|
||||
// Fallthrough
|
||||
}
|
||||
Provider::Artcraft => {
|
||||
info!("Dispatching flux dev juggernaut inpaint via Artcraft...");
|
||||
return handle_flux_dev_juggernaut_inpaint_artcraft(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalImageInpaintError::NoProviderAvailable)
|
||||
}
|
||||
-180
@@ -1,180 +0,0 @@
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::model::contextual_image_edit_models::ContextualImageEditModel;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
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::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::{FluxDevJuggernautInpaintImageNumImages, FluxDevJuggernautInpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask;
|
||||
use log::{error, info};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use storyteller_client::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
use storyteller_client::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::flux_dev_juggernaut_inpaint_image;
|
||||
use storyteller_client::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(InternalImageInpaintError::NoSourceImageSpecified);
|
||||
},
|
||||
};
|
||||
|
||||
//let image_media_file = get_media_file(
|
||||
// &app_env_configs.storyteller_host,
|
||||
// &image_media_token,
|
||||
//).await.map_err(|err| {
|
||||
// error!("Failed to get media file for image: {:?}", err);
|
||||
// InternalImageInpaintError::StorytellerError(err)
|
||||
//})?;
|
||||
|
||||
let mask_media_token = get_mask(
|
||||
request,
|
||||
app_env_configs,
|
||||
&creds,
|
||||
).await?;
|
||||
|
||||
info!("Calling Artcraft flux dev juggernaut inpaint ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(FluxDevJuggernautInpaintImageNumImages::One),
|
||||
Some(2) => Some(FluxDevJuggernautInpaintImageNumImages::Two),
|
||||
Some(3) => Some(FluxDevJuggernautInpaintImageNumImages::Three),
|
||||
Some(4) => Some(FluxDevJuggernautInpaintImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
let request = FluxDevJuggernautInpaintImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_token,
|
||||
mask_media_token,
|
||||
num_images,
|
||||
};
|
||||
|
||||
let result = flux_dev_juggernaut_inpaint_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft flux dev juggernaut inpaint. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux dev juggernaut inpaint: {:?}", err);
|
||||
return Err(InternalImageInpaintError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::FluxDevJuggernaut),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_mask(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds: &StorytellerCredentialSet,
|
||||
) -> Result<MediaFileToken, InternalImageInpaintError> {
|
||||
|
||||
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
|
||||
return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
|
||||
}
|
||||
|
||||
if let Some(token) = request.mask_image_media_token.as_ref() {
|
||||
return Ok(token.clone());
|
||||
};
|
||||
|
||||
let image_bytes = request.mask_image_raw_bytes.as_ref()
|
||||
.ok_or(InternalImageInpaintError::NoMaskImageSpecified)?;
|
||||
|
||||
let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes)
|
||||
.map_err(|err| {
|
||||
error!("Failed to convert image bytes to png: {:?}", err);
|
||||
InternalImageInpaintError::CouldNotEncodeMask
|
||||
})?;
|
||||
|
||||
info!("Uploading image media file from bytes...");
|
||||
|
||||
let result = upload_image_media_file_from_bytes(UploadImageBytesArgs {
|
||||
api_host: &app_env_configs.storyteller_host,
|
||||
maybe_creds: Some(&storyteller_creds),
|
||||
image_bytes: image_bytes.0,
|
||||
image_type: ImageType::Png,
|
||||
is_intermediate_system_file: true,
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
InternalImageInpaintError::StorytellerError(err)
|
||||
})?;
|
||||
|
||||
Ok(result.media_file_token)
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
pub (super) mod handle_flux_dev_juggernaut_inpaint;
|
||||
mod handle_flux_dev_juggernaut_inpaint_artcraft;
|
||||
+2
@@ -17,6 +17,8 @@ use crate::services::storyteller::state::storyteller_credential_manager::Storyte
|
||||
use log::info;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub(super) const MAX_IMAGES: usize = 10;
|
||||
|
||||
pub async fn handle_flux_pro_1_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
|
||||
@@ -134,7 +136,7 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::FluxProKontextMax),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod flux_dev_juggernaut_inpaint;
|
||||
mod flux_pro_1_inpaint;
|
||||
pub mod enqueue_image_inpaint_command;
|
||||
pub mod errors;
|
||||
pub mod errors;
|
||||
mod flux_pro_1_inpaint;
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod image;
|
||||
pub mod image_bg_removal;
|
||||
pub mod image_edit;
|
||||
pub mod image_inpaint;
|
||||
pub mod image_to_object;
|
||||
pub mod image_to_video;
|
||||
pub mod object;
|
||||
pub mod task_enqueue_success;
|
||||
pub mod text_to_image;
|
||||
pub mod video;
|
||||
+5
-5
@@ -1,15 +1,12 @@
|
||||
use crate::core::commands::enqueue::image_to_object::generic::handle_object::handle_object;
|
||||
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::enqueue::object::generic::handle_object::handle_object;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
@@ -19,6 +16,9 @@ use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
|
||||
use crate::core::commands::enqueue::image_to_object::generic::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::image_to_object::generic::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
|
||||
use crate::core::commands::enqueue::object::generic::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::object::generic::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
@@ -24,7 +24,6 @@ impl TaskEnqueueSuccess{
|
||||
TaskType::VideoGeneration => GenerationAction::GenerateVideo,
|
||||
TaskType::BackgroundRemoval => GenerationAction::RemoveBackground,
|
||||
TaskType::ObjectGeneration => GenerationAction::ImageTo3d,
|
||||
TaskType::ImageInpaintEdit => GenerationAction::ImageInpaintEdit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +31,6 @@ impl TaskEnqueueSuccess{
|
||||
match self.provider {
|
||||
GenerationProvider::Artcraft => GenerationServiceProvider::Artcraft,
|
||||
GenerationProvider::Fal => GenerationServiceProvider::Fal,
|
||||
GenerationProvider::Midjourney => GenerationServiceProvider::Midjourney,
|
||||
GenerationProvider::Sora => GenerationServiceProvider::Sora,
|
||||
}
|
||||
}
|
||||
@@ -58,7 +56,6 @@ impl TaskEnqueueSuccess{
|
||||
let model_type = match self.model {
|
||||
None => None,
|
||||
Some(GenerationModel::Flux1Dev) => Some(TaskModelType::Flux1Dev),
|
||||
Some(GenerationModel::FluxDevJuggernaut) => Some(TaskModelType::FluxDevJuggernaut),
|
||||
Some(GenerationModel::Flux1Schnell) => Some(TaskModelType::Flux1Schnell),
|
||||
Some(GenerationModel::FluxPro1) => Some(TaskModelType::FluxPro1), // NB: This is for inpainting.
|
||||
Some(GenerationModel::FluxPro11) => Some(TaskModelType::FluxPro11),
|
||||
@@ -72,7 +69,6 @@ impl TaskEnqueueSuccess{
|
||||
Some(GenerationModel::Veo2) => Some(TaskModelType::Veo2),
|
||||
Some(GenerationModel::Hunyuan3d2_0) => Some(TaskModelType::Hunyuan3d2_0),
|
||||
Some(GenerationModel::Hunyuan3d2_1) => Some(TaskModelType::Hunyuan3d2_1),
|
||||
Some(GenerationModel::Midjourney) => Some(TaskModelType::Midjourney), // NB: This is a generic Midjourney model, version unknown.
|
||||
|
||||
// TODO: These seem wrong -
|
||||
Some(GenerationModel::Kling1_6) => Some(TaskModelType::Kling16Pro), // NB: `VideoModel::Kling16Pro`.
|
||||
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent;
|
||||
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
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 artcraft_api_defs::generate::image::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageImageQuality, GenerateGptImage1TextToImageImageSize, GenerateGptImage1TextToImageNumImages, GenerateGptImage1TextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use midjourney_client::client::midjourney_hostname::MidjourneyHostname;
|
||||
use midjourney_client::endpoints::submit_job::{submit_job, SubmitJobRequest};
|
||||
use midjourney_client::error::midjourney_api_error::MidjourneyApiError;
|
||||
use midjourney_client::recipes::channel_id::ChannelId;
|
||||
use midjourney_client::recipes::text_to_image::{text_to_image, TextToImageError, TextToImageRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use storyteller_client::generate::image::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_midjourney(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let creds = match mj_creds_manager.maybe_copy_cookie_store() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Midjourney credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(InternalImageError::NeedsMidjourneyCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(InternalImageError::NeedsMidjourneyCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: We can request population of the user info if absent or expired.
|
||||
|
||||
let user_info = match mj_creds_manager.maybe_copy_user_info() {
|
||||
Ok(Some(user_info)) => user_info,
|
||||
Ok(None) => {
|
||||
return Err(InternalImageError::NeedsMidjourneyUserInfo);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney user info: {:?}", err);
|
||||
return Err(InternalImageError::NeedsMidjourneyUserInfo);
|
||||
},
|
||||
};
|
||||
|
||||
let channel_id = match user_info.user_id {
|
||||
Some(user_id) => ChannelId::UserId(user_id),
|
||||
None => {
|
||||
error!("Midjourney user info does not contain a user ID.");
|
||||
return Err(InternalImageError::NeedsMidjourneyUserId);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Calling midjourney ...");
|
||||
|
||||
let cookie_header = creds.to_cookie_string();
|
||||
|
||||
let prompt = request.prompt
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
let result = text_to_image(TextToImageRequest {
|
||||
prompt,
|
||||
channel_id: &channel_id,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
cookie_header,
|
||||
}).await;
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
error!("Failed to use MidJourney: {:?}", err);
|
||||
return Err(InternalImageError::MidjourneyError(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = match result.maybe_job_id {
|
||||
Some(job_id) => job_id,
|
||||
None => {
|
||||
error!("Failed to enqueue MidJourney: No job ID returned.");
|
||||
return handle_midjourney_errors(app, result.maybe_errors);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued MidJourney. Job token: {}", job_id);
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Midjourney,
|
||||
model: Some(GenerationModel::Midjourney),
|
||||
provider_job_id: Some(job_id),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_midjourney_errors(
|
||||
app: &AppHandle,
|
||||
maybe_errors: Option<Vec<TextToImageError>>
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
if let Some(errors) = maybe_errors {
|
||||
if !errors.is_empty() {
|
||||
let messages: Vec<String> = errors.iter()
|
||||
.map(|e| format!("{:?}", e))
|
||||
.collect();
|
||||
|
||||
let combined_message = messages.join("; ");
|
||||
|
||||
let event = FlashUserInputErrorEvent {
|
||||
message: format!("Midjourney Error: {}", combined_message),
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalImageError::MidjourneyJobEnqueueFailed)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub (super) mod handle_midjourney;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crate::core::commands::enqueue::image_to_video::generic::handle_video::handle_video;
|
||||
use crate::core::commands::enqueue::image_to_video::internal_video_error::InternalVideoError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::video::generic::handle_video::handle_video;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user