mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 13:02:37 -05:00
Compare commits
69
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02b5435884 | ||
|
|
5e8bb32692 | ||
|
|
3873dae52d | ||
|
|
4956313723 | ||
|
|
3c8fa9010b | ||
|
|
8780935b62 | ||
|
|
3681f4faef | ||
|
|
62094e5a33 | ||
|
|
28b7444697 | ||
|
|
f4005603f6 | ||
|
|
a4c3be4c99 | ||
|
|
d9680ae0a8 | ||
|
|
4a837d49a7 | ||
|
|
3f3da24ab6 | ||
|
|
39f4c84241 | ||
|
|
bab0f62973 | ||
|
|
6632dd34cf | ||
|
|
94430a8817 | ||
|
|
51e9f046e2 | ||
|
|
6721d2ded8 | ||
|
|
aff4a5616e | ||
|
|
b95f6fdf91 | ||
|
|
3af5291150 | ||
|
|
2710e875ec | ||
|
|
5e05877619 | ||
|
|
fec717057e | ||
|
|
9ad3b86524 | ||
|
|
2b5e509a29 | ||
|
|
28db87c1b2 | ||
|
|
292cf33f57 | ||
|
|
58b7cd0cd4 | ||
|
|
101b735c35 | ||
|
|
32291ce080 | ||
|
|
cd9c06d2ce | ||
|
|
a780779f7a | ||
|
|
076b7fe4a4 | ||
|
|
7eef728d22 | ||
|
|
d09c7320fd | ||
|
|
0ca91e29b7 | ||
|
|
8af4657393 | ||
|
|
f8ee94e538 | ||
|
|
01e0e27037 | ||
|
|
794f65b517 | ||
|
|
c53b407952 | ||
|
|
e0611e0147 | ||
|
|
8a03449128 | ||
|
|
e4af73c9ac | ||
|
|
efa9a54bee | ||
|
|
718b8261fd | ||
|
|
61c4936c29 | ||
|
|
c23e652499 | ||
|
|
a26ebce483 | ||
|
|
1d3da065a2 | ||
|
|
b4a3eb88bf | ||
|
|
48ab5f73ee | ||
|
|
76add858c9 | ||
|
|
e39e67195e | ||
|
|
ea49ddf60a | ||
|
|
e0c04efa8f | ||
|
|
dc140c0bc1 | ||
|
|
21d3617970 | ||
|
|
11a0ac82b6 | ||
|
|
4b0618e50b | ||
|
|
35aae54d49 | ||
|
|
088edd270f | ||
|
|
61311028e2 | ||
|
|
6d22d0d3c3 | ||
|
|
50e68b0721 | ||
|
|
1351a40b3d |
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_prompt_token = ?,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n maybe_debug_log_event_token = ?,\n\n frontend_failure_category = ?,\n failure_reason = ?,\n\n status = ?\n ",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_prompt_token = ?,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n frontend_failure_category = ?,\n failure_reason = ?,\n\n status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 17
|
||||
"Right": 16
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c73be10c5677f78f81bc1c29872fd5af889790c3b2a8de27eb8ab64383c638d6"
|
||||
"hash": "29916e73a1784ea0b70ef4f637520b05efccc31e01840f681e9b042f82eb8f7e"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_prompt_token = ?,\n\n maybe_wallet_ledger_entry_token = ?,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n maybe_debug_log_event_token = ?,\n\n status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 16
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8f8b64c0af5383ff3baaa8aa2f2aa22cc760addd850d618ce543fe05816d11e0"
|
||||
}
|
||||
+22
-33
@@ -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 jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n jobs.failure_reason,\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 jobs.failure_reason,\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 ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -125,16 +125,6 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -144,7 +134,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 13,
|
||||
"name": "failure_reason",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -154,7 +144,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 14,
|
||||
"name": "maybe_result_entity_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -164,7 +154,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 15,
|
||||
"name": "maybe_result_entity_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -174,7 +164,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 16,
|
||||
"name": "maybe_result_batch_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -184,7 +174,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 17,
|
||||
"name": "maybe_model_weights_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -194,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 18,
|
||||
"name": "maybe_tts_model_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -204,7 +194,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 19,
|
||||
"name": "maybe_voice_conversion_model_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -214,7 +204,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 20,
|
||||
"name": "maybe_tts_public_bucket_path",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -224,7 +214,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 21,
|
||||
"name": "maybe_voice_conversion_public_bucket_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -234,7 +224,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 22,
|
||||
"name": "maybe_media_file_public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -244,7 +234,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 23,
|
||||
"name": "maybe_media_file_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -254,7 +244,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 24,
|
||||
"name": "maybe_media_file_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -264,7 +254,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 25,
|
||||
"name": "maybe_assigned_worker",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -274,7 +264,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 26,
|
||||
"name": "maybe_assigned_cluster",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -284,7 +274,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 27,
|
||||
"name": "is_keepalive_required",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -294,7 +284,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 28,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -304,7 +294,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 29,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -314,7 +304,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 30,
|
||||
"name": "maybe_first_started_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -324,7 +314,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"ordinal": 31,
|
||||
"name": "maybe_successfully_completed_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -334,7 +324,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"ordinal": 32,
|
||||
"name": "database_clock",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -375,7 +365,6 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
@@ -384,5 +373,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6948edca3f06643605695665df51cc94f897e2cb9a2d6906910d58d4d56ee73e"
|
||||
"hash": "a2c9e2f61eba666254f8432b3c50a0a7738ee3b3faa77c5281bc8e4af96159b9"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n id as `id: u64`,\n event_token as `event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n debug_log_type as `debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType`,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n message,\n created_at\nFROM debug_logs\nWHERE event_token = ?\nORDER BY id ASC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "message",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "NOT_NULL | BLOB | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 67108860
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a62b88fec40a11952cc199383090ea338f97091e8bfc6b68aad67416685889af"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO debug_logs\nSET\n event_token = ?,\n debug_log_type = ?,\n maybe_creator_user_token = ?,\n message = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a9745ecf60b564ab096ee1dbe124221b5f288a4ae38e788e19bbd359db0b580f"
|
||||
}
|
||||
-355
@@ -1,355 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n uuid_idempotency_token,\n job_type as `maybe_job_type`,\n product_category as `maybe_product_category`,\n inference_category,\n maybe_external_third_party,\n maybe_external_third_party_id,\n maybe_model_type,\n maybe_model_token,\n maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n maybe_inference_args,\n maybe_download_url,\n on_success_result_entity_type,\n on_success_result_entity_token,\n on_success_result_batch_token,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n maybe_creator_anonymous_visitor_token,\n creator_ip_address,\n creator_set_visibility,\n maybe_wallet_ledger_entry_token,\n maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n status,\n attempt_count as `attempt_count: u16`,\n failure_reason,\n internal_debugging_failure_reason,\n frontend_failure_category,\n maybe_routing_tag,\n assigned_worker,\n assigned_cluster,\n created_at,\n updated_at\nFROM generic_inference_jobs\nWHERE token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: tokens::tokens::generic_inference_jobs::InferenceJobToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "uuid_idempotency_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 144
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "maybe_job_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_product_category",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "inference_category",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_external_third_party",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_external_third_party_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_model_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_model_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_inference_args",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_download_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 4096
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "on_success_result_entity_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "on_success_result_entity_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "on_success_result_batch_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_creator_anonymous_visitor_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "creator_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "creator_set_visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "maybe_wallet_ledger_entry_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "status",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "attempt_count: u16",
|
||||
"type_info": {
|
||||
"type": "Short",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "failure_reason",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "internal_debugging_failure_reason",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "frontend_failure_category",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "maybe_routing_tag",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "assigned_worker",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 512
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "assigned_cluster",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 512
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e869f065e6f184abbabae04ecd0da6ea02c0576cc22ee55e47eb91dd34048ef1"
|
||||
}
|
||||
+3
-14
@@ -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 \n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n \n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
|
||||
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n \n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n \n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -85,16 +85,6 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -104,7 +94,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -126,10 +116,9 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2c98621c08055374fd09502c94ac40bb01d23fcea3f844cceea7446a5c0cb4dc"
|
||||
"hash": "fbe482c8987231c3b5d75f2e792d0fdd033de8a7eb821428fa034752c73edfb7"
|
||||
}
|
||||
Generated
-5
@@ -11469,18 +11469,13 @@ name = "support-tool"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"artcraft_client",
|
||||
"clap 4.5.60",
|
||||
"easyenv",
|
||||
"enums",
|
||||
"errors",
|
||||
"log",
|
||||
"seedance2pro_client",
|
||||
"serde_json",
|
||||
"tokens",
|
||||
"tokio",
|
||||
"uuid 1.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE prompts
|
||||
DROP COLUMN maybe_quality;
|
||||
@@ -1,8 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Add quality parameter to prompts table.
|
||||
-- Represented by the enum `CommonQuality` (high, medium, low).
|
||||
ALTER TABLE prompts
|
||||
ADD COLUMN maybe_quality VARCHAR(16) DEFAULT NULL;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS debug_logs;
|
||||
@@ -1,31 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE debug_logs (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Event token. Not unique — multiple log rows can share the same event.
|
||||
event_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of debug log entry.
|
||||
debug_log_type VARCHAR(24) NOT NULL,
|
||||
|
||||
-- The user who created the log entry (if any).
|
||||
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- The log message body.
|
||||
message MEDIUMTEXT NOT NULL,
|
||||
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
KEY index_event_token (event_token),
|
||||
KEY index_debug_log_type (debug_log_type),
|
||||
KEY index_maybe_creator_user_token (maybe_creator_user_token),
|
||||
KEY index_created_at (created_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
DROP COLUMN maybe_debug_log_event_token;
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_debug_log_event_token VARCHAR(32) DEFAULT NULL;
|
||||
@@ -1,31 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE debug_logs (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Event token. Not unique — multiple log rows can share the same event.
|
||||
event_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of debug log entry.
|
||||
debug_log_type VARCHAR(24) NOT NULL,
|
||||
|
||||
-- The user who created the log entry (if any).
|
||||
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- The log message body.
|
||||
message MEDIUMTEXT NOT NULL,
|
||||
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
KEY index_event_token (event_token),
|
||||
KEY index_debug_log_type (debug_log_type),
|
||||
KEY index_maybe_creator_user_token (maybe_creator_user_token),
|
||||
KEY index_created_at (created_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -109,7 +109,7 @@ CREATE TABLE generic_inference_jobs (
|
||||
-- If we pre-constructed a prompt, this will be copied to the resultant media generated.
|
||||
-- This is especially important for Fal-related jobs and endpoints. We need this for keeping
|
||||
-- track of all the prompt details, context images, etc.
|
||||
maybe_prompt_token VARCHAR(32) DEFAULT NULL,
|
||||
maybe_prompt_token VARCHAR(30) DEFAULT NULL,
|
||||
|
||||
-- A migration flag we can control from the backend API service which will tell
|
||||
-- the worker where to store the results. If false, GCP. If true, AWS.
|
||||
@@ -204,10 +204,6 @@ CREATE TABLE generic_inference_jobs (
|
||||
-- off special workers to handle certain jobs.
|
||||
maybe_routing_tag VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- Foreign key to the debug_logs table (event_token column).
|
||||
-- Multiple debug_logs rows can share the same event token.
|
||||
maybe_debug_log_event_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- ========== JOB SYSTEM DETAILS ==========
|
||||
|
||||
-- Jobs begin as "pending", then transition to other states.
|
||||
|
||||
@@ -68,10 +68,6 @@ CREATE TABLE prompts (
|
||||
-- The resolution for the prompt (eg. four_k, 720p, etc.)
|
||||
maybe_resolution VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- Represented by the enum `CommonQuality`.
|
||||
-- The quality setting (eg. high, medium, low)
|
||||
maybe_quality VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- The duration in seconds
|
||||
maybe_duration_seconds INT UNSIGNED DEFAULT NULL,
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
$kind: http-request
|
||||
name: Batch Get Prompt
|
||||
url: "{{api_host}}/v1/prompts/prompt_8q8fwcmh93nn8pjb2szeqbxgz"
|
||||
method: GET
|
||||
headers:
|
||||
- key: Accept
|
||||
value: application/json
|
||||
- key: Session
|
||||
value: ""
|
||||
disabled: true
|
||||
order: 2171327660164107
|
||||
@@ -1,17 +1,16 @@
|
||||
use crate::common::responses::job_details::{JobDetailsLipsyncRequest, JobDetailsLivePortraitRequest};
|
||||
use crate::common::responses::media_links::MediaLinks;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::api_safe::by_table::generic_inference_jobs::frontend_failure_category_for_api_clients::FrontendFailureCategoryForApiClients;
|
||||
use enums::api_safe::by_table::generic_inference_jobs::frontend_failure_category_for_old_clients::FrontendFailureCategoryForOldClients;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use enums::api_safe::by_table::generic_inference_jobs::frontend_failure_category_for_api_clients::FrontendFailureCategoryForApiClients;
|
||||
use enums::api_safe::by_table::generic_inference_jobs::frontend_failure_category_for_old_clients::FrontendFailureCategoryForOldClients;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
|
||||
pub const LIST_SESSION_JOBS_URL_PATH: &str = "/v1/jobs/session";
|
||||
|
||||
@@ -54,9 +53,6 @@ pub struct ListSessionJobsItem {
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct ListSessionRequestDetailsResponse {
|
||||
pub inference_category: InferenceCategory,
|
||||
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
|
||||
pub maybe_model_type: Option<String>,
|
||||
pub maybe_model_token: Option<String>,
|
||||
|
||||
|
||||
-7
@@ -6,12 +6,5 @@ use utoipa::ToSchema;
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct OmniGenVideoGenerateResponse {
|
||||
pub success: bool,
|
||||
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
|
||||
/// All job tokens created by this request (including the primary).
|
||||
/// For single-job requests this will contain just one element matching
|
||||
/// `inference_job_token`. For batch requests (eg. Seedance2Pro with
|
||||
/// multiple order IDs) this will contain all of them.
|
||||
pub all_job_tokens: Vec<InferenceJobToken>,
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
use crate::prompts::get_prompt::GetPromptImageContextItem;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_model_class::CommonModelClass;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub const BATCH_GET_PROMPTS_PATH: &str = "/v1/prompt/batch";
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct BatchGetPromptsQuery {
|
||||
/// Prompt tokens to look up.
|
||||
pub tokens: HashSet<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BatchGetPromptsResponse {
|
||||
pub success: bool,
|
||||
pub prompts: Vec<BatchPromptInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BatchPromptInfo {
|
||||
pub token: PromptToken,
|
||||
|
||||
/// The type of model used
|
||||
pub maybe_model_type: Option<CommonModelType>,
|
||||
|
||||
/// The class of model used
|
||||
pub maybe_model_class: Option<CommonModelClass>,
|
||||
|
||||
/// The service provider used
|
||||
pub maybe_generation_provider: Option<GenerationProvider>,
|
||||
|
||||
/// Positive prompt (technically optional, but usually present)
|
||||
pub maybe_positive_prompt: Option<String>,
|
||||
|
||||
/// Negative prompt (optional)
|
||||
pub maybe_negative_prompt: Option<String>,
|
||||
|
||||
/// OPTIONAL. The generation mode (eg. keyframe, reference, inpaint, etc.)
|
||||
pub maybe_generation_mode: Option<CommonGenerationMode>,
|
||||
|
||||
/// OPTIONAL. The aspect ratio (eg. square, auto, wide_three_by_two, etc.)
|
||||
pub maybe_aspect_ratio: Option<CommonAspectRatio>,
|
||||
|
||||
/// OPTIONAL. The resolution (eg. one_k, two_k, four_k, etc.)
|
||||
pub maybe_resolution: Option<CommonResolution>,
|
||||
|
||||
/// OPTIONAL. The number of outputs to generate (0-255).
|
||||
pub maybe_batch_count: Option<u8>,
|
||||
|
||||
/// OPTIONAL. Whether to generate audio.
|
||||
pub maybe_generate_audio: Option<bool>,
|
||||
|
||||
/// OPTIONAL. The duration in seconds.
|
||||
pub maybe_duration_seconds: Option<u32>,
|
||||
|
||||
/// Context images (optional)
|
||||
pub maybe_context_images: Option<Vec<GetPromptImageContextItem>>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -27,6 +27,13 @@ pub struct GetPromptSuccessResponse {
|
||||
pub prompt: PromptInfo,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct GetPromptImageContextItem {
|
||||
pub media_token: MediaFileToken,
|
||||
pub semantic: PromptContextSemanticType,
|
||||
pub media_links: MediaLinks,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct PromptInfo {
|
||||
pub token: PromptToken,
|
||||
@@ -123,13 +130,6 @@ pub struct PromptInfo {
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct GetPromptImageContextItem {
|
||||
pub media_token: MediaFileToken,
|
||||
pub semantic: PromptContextSemanticType,
|
||||
pub media_links: MediaLinks,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct PromptInfoModeratorFields {
|
||||
/// How many milliseconds it took to run generation.
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod batch_get_prompts;
|
||||
pub mod create_prompt;
|
||||
pub mod get_prompt;
|
||||
@@ -1,6 +1,5 @@
|
||||
use cloudflare_errors::cloudflare_error::CloudflareError;
|
||||
use errors::AnyhowError;
|
||||
use reqwest::StatusCode;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
@@ -31,12 +30,6 @@ pub enum ApiError {
|
||||
backend_hostname: Option<String>,
|
||||
},
|
||||
|
||||
/// An uncategorized bad HTTP response with status code and body.
|
||||
UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code: StatusCode,
|
||||
body: String,
|
||||
},
|
||||
|
||||
/// Cloudflare errors.
|
||||
CloudflareError(CloudflareError),
|
||||
|
||||
@@ -73,8 +66,6 @@ impl Display for ApiError {
|
||||
ApiError::TooManyRequests(msg) => write!(f, "Too many requests to Storyteller backend: {}", msg),
|
||||
ApiError::InternalServerError {body, backend_hostname} =>
|
||||
write!(f, "Internal Server Error; backend hostname: {:?} ; body: {}; ", backend_hostname, body),
|
||||
ApiError::UncategorizedBadResponseWithStatusAndBody { status_code, body } =>
|
||||
write!(f, "Uncategorized bad response with status code {} and body: {}", status_code, body),
|
||||
// Server response handling errors
|
||||
ApiError::DeserializationError(error) => write!(f, "Deserialization error: {}", error),
|
||||
// Network errors
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::{info, warn};
|
||||
use reqwest::Client;
|
||||
use url::Url;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::endpoints::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
|
||||
pub struct DownloadMediaFileArgs<'a, P: AsRef<Path>> {
|
||||
pub media_token: &'a MediaFileToken,
|
||||
pub api_host: &'a ApiHost,
|
||||
pub download_path: DownloadPath<P>,
|
||||
}
|
||||
|
||||
pub enum DownloadPath<P: AsRef<Path>> {
|
||||
/// Write the file to this exact path.
|
||||
ExactFilename(P),
|
||||
/// Write the file into this directory, generating a filename from the CDN URL extension.
|
||||
Directory(P),
|
||||
}
|
||||
|
||||
pub struct DownloadMediaFileResult {
|
||||
/// The path the file was written to.
|
||||
pub downloaded_file_path: PathBuf,
|
||||
|
||||
/// The full media file response from the API.
|
||||
pub media_file_response: GetMediaFileSuccessResponse,
|
||||
|
||||
/// Size of downloaded file in bytes.
|
||||
pub filesize_bytes: usize,
|
||||
}
|
||||
|
||||
pub async fn download_media_file<P: AsRef<Path>>(
|
||||
args: DownloadMediaFileArgs<'_, P>,
|
||||
) -> Result<DownloadMediaFileResult, StorytellerError> {
|
||||
let DownloadMediaFileArgs {
|
||||
media_token,
|
||||
api_host,
|
||||
download_path
|
||||
} = args;
|
||||
|
||||
// 1. Fetch media file info from the API.
|
||||
let response = get_media_file(api_host, media_token).await?;
|
||||
let media_class = &response.media_file.media_class;
|
||||
let cdn_url = &response.media_file.media_links.cdn_url;
|
||||
|
||||
info!("Downloading media file {} of class {} from CDN: {}",
|
||||
media_token.as_str(), media_class.to_str(), cdn_url);
|
||||
|
||||
// 2. Determine the output file path.
|
||||
let output_path = match &download_path {
|
||||
DownloadPath::ExactFilename(path) => path.as_ref().to_path_buf(),
|
||||
DownloadPath::Directory(dir) => {
|
||||
let filename = derive_filename_from_url(cdn_url, &media_token);
|
||||
dir.as_ref().join(filename)
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Download the bytes from the CDN.
|
||||
let bytes = download_bytes(cdn_url).await?;
|
||||
|
||||
// 4. Write to disk.
|
||||
let mut file = fs::File::create(&output_path)
|
||||
.map_err(|err| StorytellerError::Client(ClientError::IoError(err)))?;
|
||||
|
||||
file.write_all(&bytes)
|
||||
.map_err(|err| StorytellerError::Client(ClientError::IoError(err)))?;
|
||||
|
||||
file.flush()
|
||||
.map_err(|err| StorytellerError::Client(ClientError::IoError(err)))?;
|
||||
|
||||
info!("Downloaded {} bytes to {:?}", bytes.len(), output_path);
|
||||
|
||||
Ok(DownloadMediaFileResult {
|
||||
downloaded_file_path: output_path,
|
||||
media_file_response: response,
|
||||
filesize_bytes: bytes.len(),
|
||||
})
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
async fn download_bytes(url: &Url) -> Result<Vec<u8>, StorytellerError> {
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|err| StorytellerError::Client(ClientError::ReqwestError(err)))?;
|
||||
|
||||
let response = client.get(url.as_str())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::OtherReqwestError(err)))?;
|
||||
|
||||
let status_code = response.status();
|
||||
|
||||
if !status_code.is_success() {
|
||||
let body = response.text().await.unwrap_or_else(|err| {
|
||||
warn!("Failed to retrieve response body: {}", err);
|
||||
"".to_string()
|
||||
});
|
||||
return Err(StorytellerError::Api(ApiError::UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code,
|
||||
body,
|
||||
}));
|
||||
}
|
||||
|
||||
let bytes = response.bytes()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Client(ClientError::ReqwestError(err)))?;
|
||||
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
|
||||
/// Derive a filename from the CDN URL's path extension, falling back to the media token.
|
||||
fn derive_filename_from_url(url: &Url, media_token: &MediaFileToken) -> String {
|
||||
let path = url.path();
|
||||
let extension = Path::new(path)
|
||||
.extension() // NB: without dot '.'
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("bin");
|
||||
|
||||
format!("{}.{}", media_token.as_str(), extension)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn derive_filename_from_url_extracts_extension() {
|
||||
let url = Url::parse("https://cdn.example.com/files/abc123.png").unwrap();
|
||||
let token = MediaFileToken::new_from_str("mf_test123");
|
||||
assert_eq!(derive_filename_from_url(&url, &token), "mf_test123.png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_filename_from_url_handles_no_extension() {
|
||||
let url = Url::parse("https://cdn.example.com/files/abc123").unwrap();
|
||||
let token = MediaFileToken::new_from_str("mf_test123");
|
||||
assert_eq!(derive_filename_from_url(&url, &token), "mf_test123.bin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_filename_from_url_handles_query_params() {
|
||||
let url = Url::parse("https://cdn.example.com/files/abc123.mp4?token=xyz").unwrap();
|
||||
let token = MediaFileToken::new_from_str("mf_test123");
|
||||
assert_eq!(derive_filename_from_url(&url, &token), "mf_test123.mp4");
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
pub mod download_media_file;
|
||||
pub mod upload_media_file_from_file;
|
||||
+1
-1
@@ -80,7 +80,6 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(CommonAspectRatioEnum::WideFourByThree)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(CommonAspectRatioEnum::TallThreeByFour)),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(CommonAspectRatioEnum::WideTwentyOneByNine)),
|
||||
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
@@ -101,6 +100,7 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> CommonAspectRatioEnu
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideThreeByTwo => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideTwentyOneByNine => CommonAspectRatioEnum::WideSixteenByNine,
|
||||
CommonAspectRatio::TallFourByFive => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallTwoByThree => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineByTwentyOne => CommonAspectRatioEnum::TallNineBySixteen,
|
||||
|
||||
+2
-3
@@ -222,10 +222,9 @@ mod tests {
|
||||
fn artcraft_matches_kinovi_all_combos() {
|
||||
let resolutions = [
|
||||
Some(CommonResolution::FourEightyP),
|
||||
Some(CommonResolution::SevenTwentyP),
|
||||
None,
|
||||
Some(CommonResolution::TenEightyP),
|
||||
// NB: These are underpriced (for now)
|
||||
//Some(CommonResolution::SevenTwentyP),
|
||||
//None,
|
||||
];
|
||||
let durations: [u16; 4] = [4, 5, 10, 15];
|
||||
let batches: [u16; 3] = [1, 2, 4];
|
||||
|
||||
+1
-1
@@ -81,7 +81,6 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(CommonAspectRatioEnum::WideFourByThree)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(CommonAspectRatioEnum::TallThreeByFour)),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(CommonAspectRatioEnum::WideTwentyOneByNine)),
|
||||
|
||||
// Mismatches
|
||||
Some(unsupported) => match strategy {
|
||||
@@ -103,6 +102,7 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> CommonAspectRatioEnu
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideThreeByTwo => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideTwentyOneByNine => CommonAspectRatioEnum::WideSixteenByNine,
|
||||
CommonAspectRatio::TallFourByFive => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallTwoByThree => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineByTwentyOne => CommonAspectRatioEnum::TallNineBySixteen,
|
||||
|
||||
+41
-41
@@ -185,47 +185,47 @@ mod tests {
|
||||
|
||||
// -- Cross-check with Kinovi Fast via builder --
|
||||
|
||||
//mod cross_check_with_kinovi {
|
||||
// use super::*;
|
||||
//
|
||||
// #[test]
|
||||
// fn artcraft_fast_matches_kinovi_fast_all_combos() {
|
||||
// let resolutions = [
|
||||
// Some(CommonResolution::FourEightyP),
|
||||
// Some(CommonResolution::SevenTwentyP),
|
||||
// None,
|
||||
// ];
|
||||
// let durations: [u16; 4] = [4, 5, 10, 15];
|
||||
// let batches: [u16; 3] = [1, 2, 4];
|
||||
//
|
||||
// for res in &resolutions {
|
||||
// for dur in &durations {
|
||||
// for batch in &batches {
|
||||
// let artcraft_cost = build_cost(*res, *dur, *batch);
|
||||
//
|
||||
// let kinovi = GenerateVideoRequestBuilder {
|
||||
// model: CommonVideoModel::Seedance2p0Fast,
|
||||
// provider: Provider::Seedance2Pro,
|
||||
// resolution: *res,
|
||||
// duration_seconds: Some(*dur),
|
||||
// video_batch_count: Some(*batch),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// let kinovi_cost = kinovi.build2()
|
||||
// .expect("kinovi build2")
|
||||
// .estimate_cost()
|
||||
// .expect("kinovi estimate_cost");
|
||||
//
|
||||
// assert_eq!(
|
||||
// artcraft_cost.cost_in_usd_cents, kinovi_cost.cost_in_usd_cents,
|
||||
// "USD cents mismatch: res={:?} dur={}s batch={}",
|
||||
// res, dur, batch,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
mod cross_check_with_kinovi {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn artcraft_fast_matches_kinovi_fast_all_combos() {
|
||||
let resolutions = [
|
||||
Some(CommonResolution::FourEightyP),
|
||||
Some(CommonResolution::SevenTwentyP),
|
||||
None,
|
||||
];
|
||||
let durations: [u16; 4] = [4, 5, 10, 15];
|
||||
let batches: [u16; 3] = [1, 2, 4];
|
||||
|
||||
for res in &resolutions {
|
||||
for dur in &durations {
|
||||
for batch in &batches {
|
||||
let artcraft_cost = build_cost(*res, *dur, *batch);
|
||||
|
||||
let kinovi = GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::Seedance2p0Fast,
|
||||
provider: Provider::Seedance2Pro,
|
||||
resolution: *res,
|
||||
duration_seconds: Some(*dur),
|
||||
video_batch_count: Some(*batch),
|
||||
..Default::default()
|
||||
};
|
||||
let kinovi_cost = kinovi.build2()
|
||||
.expect("kinovi build2")
|
||||
.estimate_cost()
|
||||
.expect("kinovi estimate_cost");
|
||||
|
||||
assert_eq!(
|
||||
artcraft_cost.cost_in_usd_cents, kinovi_cost.cost_in_usd_cents,
|
||||
"USD cents mismatch: res={:?} dur={}s batch={}",
|
||||
res, dur, batch,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Helpers --
|
||||
|
||||
|
||||
+3
-11
@@ -1,8 +1,4 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
KinoviSeedance2p0AspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0BatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0OutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
@@ -77,7 +73,6 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(KinoviAspectRatio::Standard4x3),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(KinoviAspectRatio::Portrait3x4),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(KinoviAspectRatio::UltraWide21x9),
|
||||
|
||||
// Mismatches — apply strategy
|
||||
Some(unsupported) => match strategy {
|
||||
@@ -100,6 +95,7 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> KinoviAspectRatio {
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => KinoviAspectRatio::Standard4x3, // 1.25, nearest 1.33
|
||||
CommonAspectRatio::WideThreeByTwo => KinoviAspectRatio::Standard4x3, // 1.50, nearest 1.33
|
||||
CommonAspectRatio::WideTwentyOneByNine => KinoviAspectRatio::Landscape16x9, // 2.33, nearest 1.78
|
||||
CommonAspectRatio::TallFourByFive => KinoviAspectRatio::Portrait3x4, // 0.80, nearest 0.75
|
||||
CommonAspectRatio::TallTwoByThree => KinoviAspectRatio::Portrait3x4, // 0.67, nearest 0.75
|
||||
CommonAspectRatio::TallNineByTwentyOne => KinoviAspectRatio::Portrait9x16, // 0.43, nearest 0.56
|
||||
@@ -197,11 +193,7 @@ fn plan_duration(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
KinoviSeedance2p0AspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0BatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0OutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
|
||||
+54
-60
@@ -1,23 +1,22 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
GenerateSeedance2p0Request, KinoviSeedance2p0BatchCount,
|
||||
KinoviSeedance2p0OutputResolution,
|
||||
};
|
||||
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::draft::KinoviSeedance2p0DraftState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::request::KinoviSeedance2p0RequestState;
|
||||
|
||||
pub struct KinoviSeedance2p0CostState {
|
||||
pub resolution: Option<KinoviSeedance2p0OutputResolution>,
|
||||
pub resolution: KinoviOutputResolution,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: Option<KinoviSeedance2p0BatchCount>,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
|
||||
/// Forward-looking: Kinovi charges for this. We need to take it into
|
||||
/// account in the future.
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0CostState {
|
||||
pub fn from_request(request: &KinoviSeedance2p0RequestState) -> Self {
|
||||
Self {
|
||||
resolution: request.request.output_resolution,
|
||||
resolution: request.request.output_resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: request.request.duration_seconds,
|
||||
batch_count: request.request.batch_count,
|
||||
has_video_reference: request.request.reference_video_urls
|
||||
@@ -33,22 +32,27 @@ impl KinoviSeedance2p0CostState {
|
||||
.is_some();
|
||||
|
||||
Self {
|
||||
resolution: draft.resolution,
|
||||
resolution: draft.resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: draft.duration_seconds,
|
||||
batch_count: Some(draft.batch_count),
|
||||
batch_count: draft.batch_count,
|
||||
has_video_reference,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
let pricing_request = GenerateSeedance2p0Request {
|
||||
output_resolution: self.resolution,
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
// Pricing factors
|
||||
model_type: KinoviModelType::Seedance2Pro,
|
||||
output_resolution: Some(self.resolution),
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// NB: has_video_reference is tracked but does not affect price yet.
|
||||
// When Kinovi starts charging for video references, add cost here.
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: None,
|
||||
aspect_ratio: KinoviAspectRatio::Portrait9x16,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
@@ -58,8 +62,8 @@ impl KinoviSeedance2p0CostState {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let cost_in_credits = request.estimate_credits();
|
||||
let cost_in_usd_cents = request.estimate_cost_in_usd_cents();
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
@@ -75,11 +79,6 @@ impl KinoviSeedance2p0CostState {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
KinoviSeedance2p0OutputResolution as KinoviOutputResolution,
|
||||
KinoviSeedance2p0BatchCount as KinoviBatchCount,
|
||||
};
|
||||
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
@@ -94,26 +93,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 83);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 104);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 124);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 207);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 311);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 64);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 80);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 96);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 112);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 160);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 240);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 166);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 207);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 622);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 128);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 160);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 480);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 332);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 415);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Four), 1244);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 256);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 320);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Four), 960);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +202,9 @@ mod tests {
|
||||
#[test]
|
||||
fn video_reference_does_not_affect_cost() {
|
||||
let base = KinoviSeedance2p0CostState {
|
||||
resolution: Some(KinoviOutputResolution::SevenTwentyP),
|
||||
resolution: KinoviOutputResolution::SevenTwentyP,
|
||||
duration_seconds: 5,
|
||||
batch_count: Some(KinoviBatchCount::One),
|
||||
batch_count: KinoviBatchCount::One,
|
||||
has_video_reference: false,
|
||||
};
|
||||
let without = base.estimate_cost();
|
||||
@@ -223,19 +222,19 @@ mod tests {
|
||||
fn from_request_720p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_none_defaults_to_720p() {
|
||||
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(cost.resolution.is_none()); // None defaults to 720p pricing
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -258,7 +257,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -278,18 +277,18 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(cost.resolution.is_none()); // None defaults to 720p pricing
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_draft_480p() {
|
||||
let draft = make_draft(5, 1, Some(CommonResolution::FourEightyP), false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::FourEightyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(39));
|
||||
}
|
||||
|
||||
@@ -297,8 +296,8 @@ mod tests {
|
||||
fn from_draft_1080p_batch_4() {
|
||||
let draft = make_draft(5, 4, Some(CommonResolution::TenEightyP), false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::TenEightyP)));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Four)));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::TenEightyP));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::Four));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(933));
|
||||
}
|
||||
|
||||
@@ -308,7 +307,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -323,8 +322,8 @@ mod tests {
|
||||
let draft = make_draft(15, 2, Some(CommonResolution::SevenTwentyP), false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert_eq!(cost.duration_seconds, 15);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(622));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::Two));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(480));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,10 +382,7 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0CostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
KinoviSeedance2p0CostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
.estimate_cost()
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
@@ -397,10 +393,7 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0CostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
KinoviSeedance2p0CostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
.estimate_cost()
|
||||
.cost_in_credits
|
||||
.unwrap()
|
||||
@@ -450,12 +443,13 @@ mod tests {
|
||||
};
|
||||
|
||||
KinoviSeedance2p0RequestState {
|
||||
request: GenerateSeedance2p0Request {
|
||||
request: KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Pro,
|
||||
prompt: "test".to_string(),
|
||||
aspect_ratio: None,
|
||||
aspect_ratio: KinoviAspectRatio::Landscape16x9,
|
||||
output_resolution: resolution,
|
||||
duration_seconds,
|
||||
batch_count: Some(batch_count),
|
||||
batch_count,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
|
||||
+8
-10
@@ -1,7 +1,4 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
GenerateSeedance2p0Request, KinoviSeedance2p0AspectRatio,
|
||||
KinoviSeedance2p0BatchCount, KinoviSeedance2p0OutputResolution,
|
||||
};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
@@ -22,10 +19,10 @@ pub struct KinoviSeedance2p0DraftState {
|
||||
// Materialized / finalized types
|
||||
|
||||
pub prompt: String,
|
||||
pub aspect_ratio: KinoviSeedance2p0AspectRatio,
|
||||
pub resolution: Option<KinoviSeedance2p0OutputResolution>,
|
||||
pub aspect_ratio: KinoviAspectRatio,
|
||||
pub resolution: Option<KinoviOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviSeedance2p0BatchCount,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
|
||||
// Pending types that need to be queried.
|
||||
pub unhandled_request_state: Option<KinoviSeedance2p0RemainingItems>,
|
||||
@@ -80,12 +77,13 @@ impl KinoviSeedance2p0DraftState {
|
||||
)?;
|
||||
}
|
||||
|
||||
let request = GenerateSeedance2p0Request {
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Pro,
|
||||
prompt: self.prompt.clone(),
|
||||
aspect_ratio: Some(self.aspect_ratio),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: Some(self.batch_count),
|
||||
batch_count: self.batch_count,
|
||||
start_frame_url,
|
||||
end_frame_url,
|
||||
reference_image_urls,
|
||||
|
||||
+324
-10
@@ -1,31 +1,28 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
generate_seedance_2p0, GenerateSeedance2p0Args, GenerateSeedance2p0Request,
|
||||
};
|
||||
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::provider_error::ProviderError;
|
||||
use crate::generate::generate_video::generate_video_response::{GenerateVideoResponse, Seedance2proVideoResponsePayload};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{generate_video, GenerateVideoArgs, KinoviGenerateVideoRequest};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0RequestState {
|
||||
/// Final materialized request; ready to fire.
|
||||
pub request: GenerateSeedance2p0Request,
|
||||
pub request: KinoviGenerateVideoRequest,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0RequestState {
|
||||
pub async fn send(&self, client: &RouterSeedance2ProClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let session = &client.session;
|
||||
|
||||
let args = GenerateSeedance2p0Args {
|
||||
let args = GenerateVideoArgs {
|
||||
session,
|
||||
host_override: None,
|
||||
request: self.request.clone(),
|
||||
request: self.request.clone(), // TODO: Yuck.
|
||||
};
|
||||
|
||||
let response = generate_seedance_2p0(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
let response = generate_video(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
|
||||
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
|
||||
order_id: response.order_id,
|
||||
@@ -35,3 +32,320 @@ impl KinoviSeedance2p0RequestState {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_context::VideoGenerationDraftContext;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use test_data::web::image_urls::{FOREST_BACKDROP_IMAGE_URL, JUNO_AT_LAKE_IMAGE_URL, WHITE_HOUSE_SUNSET_IMAGE_URL};
|
||||
|
||||
const STEAMPUNK_CLOWN_KINOVI_ID: &str = "char_1775176566518_sik0te";
|
||||
const MOCHI_KINOVI_ID: &str = "char_1775177718294_g2pitx";
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn landscape() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn portrait() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A cat sitting on a windowsill watching rain.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn square() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A hummingbird hovering near a flower.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_480p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A shiba inu playing in autumn leaves.".to_string()),
|
||||
resolution: Some(CommonResolution::FourEightyP),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_720p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A golden retriever catching a frisbee on the beach.".to_string()),
|
||||
resolution: Some(CommonResolution::SevenTwentyP),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_1080p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A fox walking through a snowy forest.".to_string()),
|
||||
resolution: Some(CommonResolution::TenEightyP),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod modality_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn text_to_video() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A whale breaching in the open ocean at dawn, cinematic.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn keyframe_start_and_end_frame() {
|
||||
let start_token = MediaFileToken::new("mf_start".to_string());
|
||||
let end_token = MediaFileToken::new("mf_end".to_string());
|
||||
|
||||
let mut media_map = HashMap::new();
|
||||
media_map.insert(start_token.clone(), JUNO_AT_LAKE_IMAGE_URL.to_string());
|
||||
media_map.insert(end_token.clone(), FOREST_BACKDROP_IMAGE_URL.to_string());
|
||||
|
||||
let response = run_pipeline_with_media_map(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog walks from the lake to the forest.".to_string()),
|
||||
start_frame: Some(ImageRef::MediaFileToken(start_token)),
|
||||
end_frame: Some(ImageRef::MediaFileToken(end_token)),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}, media_map).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn image_references() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog in @2 runs through the scenery in @1 towards the building in @3.".to_string()),
|
||||
reference_images: Some(ImageListRef::Urls(vec![
|
||||
FOREST_BACKDROP_IMAGE_URL.to_string(),
|
||||
JUNO_AT_LAKE_IMAGE_URL.to_string(),
|
||||
WHITE_HOUSE_SUNSET_IMAGE_URL.to_string(),
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn video_reference() {
|
||||
// NB: This URL is a pre-uploaded video on the Seedance2Pro CDN from prior tests.
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("Change @video1 to a nighttime scene with moonlight.".to_string()),
|
||||
reference_videos: Some(VideoListRef::Urls(vec![
|
||||
"https://static.seedance2-pro.com/materials/20260315/1773594284659-3a46d231.mp4".to_string(),
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn character_references() {
|
||||
let char_clown = CharacterToken::new("char_clown".to_string());
|
||||
let char_mochi = CharacterToken::new("char_mochi".to_string());
|
||||
|
||||
let mut character_map = HashMap::new();
|
||||
character_map.insert(char_clown.clone(), STEAMPUNK_CLOWN_KINOVI_ID.to_string());
|
||||
character_map.insert(char_mochi.clone(), MOCHI_KINOVI_ID.to_string());
|
||||
|
||||
let response = run_pipeline_with_maps(
|
||||
GenerateVideoRequestBuilder {
|
||||
prompt: Some("@Steampunk Clown and @Mochi are playing fetch in a sunny park.".to_string()),
|
||||
reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![
|
||||
char_clown,
|
||||
char_mochi,
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
},
|
||||
HashMap::new(),
|
||||
character_map,
|
||||
).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
// TODO: Add audio reference test once a web-accessible audio URL is available in test_data.
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn seedance2pro_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
provider: Provider::Seedance2Pro,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_seedance2pro_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/seedance2pro_cookies.txt")
|
||||
.expect("Failed to read seedance2pro cookies");
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies.trim().to_string());
|
||||
RouterClient::Seedance2Pro(RouterSeedance2ProClient::new(session))
|
||||
}
|
||||
|
||||
/// Run the full pipeline: builder → build2 → finalize → send.
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Run the full pipeline with a media file token map for resolving references.
|
||||
async fn run_pipeline_with_media_map(
|
||||
builder: GenerateVideoRequestBuilder,
|
||||
media_map: HashMap<MediaFileToken, String>,
|
||||
) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
media_file_to_artcraft_url_map: Some(&media_map),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Run the full pipeline with both a media map and character map.
|
||||
async fn run_pipeline_with_maps(
|
||||
builder: GenerateVideoRequestBuilder,
|
||||
media_map: HashMap<MediaFileToken, String>,
|
||||
character_map: HashMap<CharacterToken, String>,
|
||||
) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
media_file_to_artcraft_url_map: Some(&media_map),
|
||||
character_token_to_kinovi_id_map: Some(&character_map),
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
+3
-11
@@ -1,8 +1,4 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
KinoviSeedance2p0FastAspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0FastBatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0FastOutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
@@ -77,7 +73,6 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(KinoviAspectRatio::Standard4x3),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(KinoviAspectRatio::Portrait3x4),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(KinoviAspectRatio::UltraWide21x9),
|
||||
|
||||
// Mismatches — apply strategy
|
||||
Some(unsupported) => match strategy {
|
||||
@@ -100,6 +95,7 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> KinoviAspectRatio {
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => KinoviAspectRatio::Standard4x3, // 1.25, nearest 1.33
|
||||
CommonAspectRatio::WideThreeByTwo => KinoviAspectRatio::Standard4x3, // 1.50, nearest 1.33
|
||||
CommonAspectRatio::WideTwentyOneByNine => KinoviAspectRatio::Landscape16x9, // 2.33, nearest 1.78
|
||||
CommonAspectRatio::TallFourByFive => KinoviAspectRatio::Portrait3x4, // 0.80, nearest 0.75
|
||||
CommonAspectRatio::TallTwoByThree => KinoviAspectRatio::Portrait3x4, // 0.67, nearest 0.75
|
||||
CommonAspectRatio::TallNineByTwentyOne => KinoviAspectRatio::Portrait9x16, // 0.43, nearest 0.56
|
||||
@@ -212,11 +208,7 @@ fn plan_duration(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
KinoviSeedance2p0FastAspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0FastBatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0FastOutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
|
||||
+57
-63
@@ -1,23 +1,22 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
GenerateSeedance2p0FastRequest, KinoviSeedance2p0FastBatchCount,
|
||||
KinoviSeedance2p0FastOutputResolution,
|
||||
};
|
||||
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::draft::KinoviSeedance2p0FastDraftState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::request::KinoviSeedance2p0FastRequestState;
|
||||
|
||||
pub struct KinoviSeedance2p0FastCostState {
|
||||
pub resolution: Option<KinoviSeedance2p0FastOutputResolution>,
|
||||
pub resolution: KinoviOutputResolution,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: Option<KinoviSeedance2p0FastBatchCount>,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
|
||||
/// Forward-looking: Kinovi charges for this. We need to take it into
|
||||
/// account in the future.
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0FastCostState {
|
||||
pub fn from_request(request: &KinoviSeedance2p0FastRequestState) -> Self {
|
||||
Self {
|
||||
resolution: request.request.output_resolution,
|
||||
resolution: request.request.output_resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: request.request.duration_seconds,
|
||||
batch_count: request.request.batch_count,
|
||||
has_video_reference: request.request.reference_video_urls
|
||||
@@ -33,22 +32,27 @@ impl KinoviSeedance2p0FastCostState {
|
||||
.is_some();
|
||||
|
||||
Self {
|
||||
resolution: draft.resolution,
|
||||
resolution: draft.resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: draft.duration_seconds,
|
||||
batch_count: Some(draft.batch_count),
|
||||
batch_count: draft.batch_count,
|
||||
has_video_reference,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
let pricing_request = GenerateSeedance2p0FastRequest {
|
||||
output_resolution: self.resolution,
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
// Pricing factors
|
||||
model_type: KinoviModelType::Seedance2Fast,
|
||||
output_resolution: Some(self.resolution),
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// NB: has_video_reference is tracked but does not affect price yet.
|
||||
// When Kinovi starts charging for video references, add cost here.
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: None,
|
||||
aspect_ratio: KinoviAspectRatio::Portrait9x16,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
@@ -58,8 +62,8 @@ impl KinoviSeedance2p0FastCostState {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let cost_in_credits = request.estimate_credits();
|
||||
let cost_in_usd_cents = request.estimate_cost_in_usd_cents();
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
@@ -76,11 +80,6 @@ impl KinoviSeedance2p0FastCostState {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
KinoviSeedance2p0FastOutputResolution as KinoviOutputResolution,
|
||||
KinoviSeedance2p0FastBatchCount as KinoviBatchCount,
|
||||
};
|
||||
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
@@ -91,51 +90,51 @@ mod tests {
|
||||
// ── Direct estimate_cost() tests ──
|
||||
// These must match the reference implementation values exactly.
|
||||
|
||||
// -- 720p (28 credits/sec, 193 credits/$1) --
|
||||
// -- 720p (legacy pricing: 220 credits/$1, 28 credits/sec) --
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 58);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 218);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 51);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 64);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 127);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 191);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 127);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 290);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 255);
|
||||
}
|
||||
}
|
||||
|
||||
// -- 480p (14 credits/sec, 193 credits/$1) --
|
||||
// -- 480p (new pricing: 193 credits/$1, 10 credits/sec) --
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 29);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 36);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 109);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 21);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 26);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 52);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 78);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 104);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +174,9 @@ mod tests {
|
||||
#[test]
|
||||
fn video_reference_does_not_affect_cost() {
|
||||
let base = KinoviSeedance2p0FastCostState {
|
||||
resolution: Some(KinoviOutputResolution::SevenTwentyP),
|
||||
resolution: KinoviOutputResolution::SevenTwentyP,
|
||||
duration_seconds: 5,
|
||||
batch_count: Some(KinoviBatchCount::One),
|
||||
batch_count: KinoviBatchCount::One,
|
||||
has_video_reference: false,
|
||||
};
|
||||
let without = base.estimate_cost();
|
||||
@@ -195,26 +194,26 @@ mod tests {
|
||||
fn from_request_720p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_none_defaults_to_720p() {
|
||||
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(cost.resolution.is_none());
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_480p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(36));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(26));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -223,7 +222,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -243,19 +242,19 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(cost.resolution.is_none());
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_draft_480p() {
|
||||
let draft = make_draft(5, 1, Some(CommonResolution::FourEightyP), false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(36));
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::FourEightyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(26));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -264,7 +263,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -279,8 +278,8 @@ mod tests {
|
||||
let draft = make_draft(15, 2, Some(CommonResolution::SevenTwentyP), false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert_eq!(cost.duration_seconds, 15);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(435));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::Two));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(382));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +322,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn credits_480p() {
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 70);
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,10 +333,7 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0FastCostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
KinoviSeedance2p0FastCostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
.estimate_cost()
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
@@ -348,10 +344,7 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0FastCostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
KinoviSeedance2p0FastCostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
.estimate_cost()
|
||||
.cost_in_credits
|
||||
.unwrap()
|
||||
@@ -402,12 +395,13 @@ mod tests {
|
||||
};
|
||||
|
||||
KinoviSeedance2p0FastRequestState {
|
||||
request: GenerateSeedance2p0FastRequest {
|
||||
request: KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Fast,
|
||||
prompt: "test".to_string(),
|
||||
aspect_ratio: None,
|
||||
aspect_ratio: KinoviAspectRatio::Landscape16x9,
|
||||
output_resolution: resolution,
|
||||
duration_seconds,
|
||||
batch_count: Some(batch_count),
|
||||
batch_count,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
|
||||
+13
-12
@@ -1,7 +1,4 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
GenerateSeedance2p0FastRequest, KinoviSeedance2p0FastAspectRatio,
|
||||
KinoviSeedance2p0FastBatchCount, KinoviSeedance2p0FastOutputResolution,
|
||||
};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
@@ -19,12 +16,15 @@ use crate::generate::generate_video_v2::video_generation_draft_context::VideoGen
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0FastDraftState {
|
||||
pub prompt: String,
|
||||
pub aspect_ratio: KinoviSeedance2p0FastAspectRatio,
|
||||
pub resolution: Option<KinoviSeedance2p0FastOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviSeedance2p0FastBatchCount,
|
||||
// Materialized / finalized types
|
||||
|
||||
pub prompt: String,
|
||||
pub aspect_ratio: KinoviAspectRatio,
|
||||
pub resolution: Option<KinoviOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
|
||||
// Pending types that need to be queried.
|
||||
pub unhandled_request_state: Option<KinoviSeedance2p0FastRemainingItems>,
|
||||
}
|
||||
|
||||
@@ -77,12 +77,13 @@ impl KinoviSeedance2p0FastDraftState {
|
||||
)?;
|
||||
}
|
||||
|
||||
let request = GenerateSeedance2p0FastRequest {
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Fast,
|
||||
prompt: self.prompt.clone(),
|
||||
aspect_ratio: Some(self.aspect_ratio),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: Some(self.batch_count),
|
||||
batch_count: self.batch_count,
|
||||
start_frame_url,
|
||||
end_frame_url,
|
||||
reference_image_urls,
|
||||
|
||||
+233
-10
@@ -1,31 +1,28 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
generate_seedance_2p0_fast, GenerateSeedance2p0FastArgs, GenerateSeedance2p0FastRequest,
|
||||
};
|
||||
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::provider_error::ProviderError;
|
||||
use crate::generate::generate_video::generate_video_response::{GenerateVideoResponse, Seedance2proVideoResponsePayload};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{generate_video, GenerateVideoArgs, KinoviGenerateVideoRequest};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0FastRequestState {
|
||||
/// Final materialized request; ready to fire.
|
||||
pub request: GenerateSeedance2p0FastRequest,
|
||||
pub request: KinoviGenerateVideoRequest,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0FastRequestState {
|
||||
pub async fn send(&self, client: &RouterSeedance2ProClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let session = &client.session;
|
||||
|
||||
let args = GenerateSeedance2p0FastArgs {
|
||||
let args = GenerateVideoArgs {
|
||||
session,
|
||||
host_override: None,
|
||||
request: self.request.clone(),
|
||||
request: self.request.clone(), // TODO: Yuck.
|
||||
};
|
||||
|
||||
let response = generate_seedance_2p0_fast(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
let response = generate_video(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
|
||||
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
|
||||
order_id: response.order_id,
|
||||
@@ -35,3 +32,229 @@ impl KinoviSeedance2p0FastRequestState {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_context::VideoGenerationDraftContext;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use test_data::web::image_urls::{FOREST_BACKDROP_IMAGE_URL, JUNO_AT_LAKE_IMAGE_URL, WHITE_HOUSE_SUNSET_IMAGE_URL};
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn landscape() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn portrait() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A cat sitting on a windowsill watching rain.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn square() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A hummingbird hovering near a flower.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_480p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A shiba inu playing in autumn leaves.".to_string()),
|
||||
resolution: Some(CommonResolution::FourEightyP),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_720p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A golden retriever catching a frisbee on the beach.".to_string()),
|
||||
resolution: Some(CommonResolution::SevenTwentyP),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod modality_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn text_to_video() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A whale breaching in the open ocean at dawn, cinematic.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn keyframe_start_and_end_frame() {
|
||||
let start_token = MediaFileToken::new("mf_start".to_string());
|
||||
let end_token = MediaFileToken::new("mf_end".to_string());
|
||||
|
||||
let mut media_map = HashMap::new();
|
||||
media_map.insert(start_token.clone(), JUNO_AT_LAKE_IMAGE_URL.to_string());
|
||||
media_map.insert(end_token.clone(), FOREST_BACKDROP_IMAGE_URL.to_string());
|
||||
|
||||
let response = run_pipeline_with_media_map(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog walks from the lake to the forest.".to_string()),
|
||||
start_frame: Some(ImageRef::MediaFileToken(start_token)),
|
||||
end_frame: Some(ImageRef::MediaFileToken(end_token)),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}, media_map).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn image_references() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog in @2 runs through the scenery in @1 towards the building in @3.".to_string()),
|
||||
reference_images: Some(ImageListRef::Urls(vec![
|
||||
FOREST_BACKDROP_IMAGE_URL.to_string(),
|
||||
JUNO_AT_LAKE_IMAGE_URL.to_string(),
|
||||
WHITE_HOUSE_SUNSET_IMAGE_URL.to_string(),
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn seedance2pro_fast_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::Seedance2p0Fast,
|
||||
provider: Provider::Seedance2Pro,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_seedance2pro_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/seedance2pro_cookies.txt")
|
||||
.expect("Failed to read seedance2pro cookies");
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies.trim().to_string());
|
||||
RouterClient::Seedance2Pro(RouterSeedance2ProClient::new(session))
|
||||
}
|
||||
|
||||
/// Run the full pipeline: builder -> build2 -> finalize -> send.
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Run the full pipeline with a media file token map for resolving references.
|
||||
async fn run_pipeline_with_media_map(
|
||||
builder: GenerateVideoRequestBuilder,
|
||||
media_map: HashMap<MediaFileToken, String>,
|
||||
) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
media_file_to_artcraft_url_map: Some(&media_map),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ pub struct GenerateSeedance2p0Request {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KinoviSeedance2p0AspectRatio {
|
||||
Landscape16x9,
|
||||
UltraWide21x9,
|
||||
Portrait9x16,
|
||||
Square1x1,
|
||||
Standard4x3,
|
||||
@@ -156,7 +155,6 @@ pub async fn generate_seedance_2p0(
|
||||
fn map_aspect_ratio(ar: Option<KinoviSeedance2p0AspectRatio>) -> KinoviAspectRatioRaw {
|
||||
match ar {
|
||||
Some(KinoviSeedance2p0AspectRatio::Landscape16x9) => KinoviAspectRatioRaw::Landscape16x9,
|
||||
Some(KinoviSeedance2p0AspectRatio::UltraWide21x9) => KinoviAspectRatioRaw::UltraWide21x9,
|
||||
Some(KinoviSeedance2p0AspectRatio::Portrait9x16) => KinoviAspectRatioRaw::Portrait9x16,
|
||||
Some(KinoviSeedance2p0AspectRatio::Square1x1) => KinoviAspectRatioRaw::Square1x1,
|
||||
Some(KinoviSeedance2p0AspectRatio::Standard4x3) => KinoviAspectRatioRaw::Landscape4x3,
|
||||
@@ -432,7 +430,6 @@ mod tests {
|
||||
|
||||
let ratios = [
|
||||
KinoviSeedance2p0AspectRatio::Landscape16x9,
|
||||
KinoviSeedance2p0AspectRatio::UltraWide21x9,
|
||||
KinoviSeedance2p0AspectRatio::Portrait9x16,
|
||||
KinoviSeedance2p0AspectRatio::Square1x1,
|
||||
KinoviSeedance2p0AspectRatio::Standard4x3,
|
||||
@@ -531,70 +528,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod ultra_wide {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_text_to_video_21x9() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let session = test_session()?;
|
||||
let result = generate_seedance_2p0(GenerateSeedance2p0Args {
|
||||
session: &session,
|
||||
host_override: None,
|
||||
request: GenerateSeedance2p0Request {
|
||||
prompt: "A corgi is riding on the back of a sauropod dinosaur".to_string(),
|
||||
aspect_ratio: Some(KinoviSeedance2p0AspectRatio::UltraWide21x9),
|
||||
output_resolution: None,
|
||||
batch_count: None,
|
||||
duration_seconds: 5,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls: None,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
},
|
||||
}).await?;
|
||||
println!("t2v 21:9 — task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
assert!(!result.task_id.is_empty());
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_keyframe_21x9() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let session = test_session()?;
|
||||
let start_frame_url = upload_test_image(&session, test_data::web::image_urls::WIDE_CORGI_SHIBA_TREASURE_OCEAN_URL).await?;
|
||||
|
||||
let result = generate_seedance_2p0(GenerateSeedance2p0Args {
|
||||
session: &session,
|
||||
host_override: None,
|
||||
request: GenerateSeedance2p0Request {
|
||||
prompt: "The dogs sail across the ocean on a treasure ship.".to_string(),
|
||||
aspect_ratio: Some(KinoviSeedance2p0AspectRatio::UltraWide21x9),
|
||||
output_resolution: None,
|
||||
batch_count: None,
|
||||
duration_seconds: 5,
|
||||
start_frame_url: Some(start_frame_url),
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls: None,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
},
|
||||
}).await?;
|
||||
println!("keyframe 21:9 — task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
assert!(!result.task_id.is_empty());
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod keyframe {
|
||||
use super::*;
|
||||
|
||||
|
||||
-67
@@ -38,7 +38,6 @@ pub struct GenerateSeedance2p0FastRequest {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KinoviSeedance2p0FastAspectRatio {
|
||||
Landscape16x9,
|
||||
UltraWide21x9,
|
||||
Portrait9x16,
|
||||
Square1x1,
|
||||
Standard4x3,
|
||||
@@ -153,7 +152,6 @@ pub async fn generate_seedance_2p0_fast(
|
||||
fn map_aspect_ratio(ar: Option<KinoviSeedance2p0FastAspectRatio>) -> KinoviAspectRatioRaw {
|
||||
match ar {
|
||||
Some(KinoviSeedance2p0FastAspectRatio::Landscape16x9) => KinoviAspectRatioRaw::Landscape16x9,
|
||||
Some(KinoviSeedance2p0FastAspectRatio::UltraWide21x9) => KinoviAspectRatioRaw::UltraWide21x9,
|
||||
Some(KinoviSeedance2p0FastAspectRatio::Portrait9x16) => KinoviAspectRatioRaw::Portrait9x16,
|
||||
Some(KinoviSeedance2p0FastAspectRatio::Square1x1) => KinoviAspectRatioRaw::Square1x1,
|
||||
Some(KinoviSeedance2p0FastAspectRatio::Standard4x3) => KinoviAspectRatioRaw::Landscape4x3,
|
||||
@@ -384,7 +382,6 @@ mod tests {
|
||||
|
||||
let ratios = [
|
||||
KinoviSeedance2p0FastAspectRatio::Landscape16x9,
|
||||
KinoviSeedance2p0FastAspectRatio::UltraWide21x9,
|
||||
KinoviSeedance2p0FastAspectRatio::Portrait9x16,
|
||||
KinoviSeedance2p0FastAspectRatio::Square1x1,
|
||||
KinoviSeedance2p0FastAspectRatio::Standard4x3,
|
||||
@@ -483,70 +480,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod ultra_wide {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_text_to_video_21x9() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let session = test_session()?;
|
||||
let result = generate_seedance_2p0_fast(GenerateSeedance2p0FastArgs {
|
||||
session: &session,
|
||||
host_override: None,
|
||||
request: GenerateSeedance2p0FastRequest {
|
||||
prompt: "A shiba is riding on the back of a sauropod dinosaur".to_string(),
|
||||
aspect_ratio: Some(KinoviSeedance2p0FastAspectRatio::UltraWide21x9),
|
||||
output_resolution: Some(KinoviSeedance2p0FastOutputResolution::FourEightyP),
|
||||
batch_count: None,
|
||||
duration_seconds: 5,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls: None,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
},
|
||||
}).await?;
|
||||
println!("fast t2v 21:9 480p — task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
assert!(!result.task_id.is_empty());
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_keyframe_21x9() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let session = test_session()?;
|
||||
let start_frame_url = upload_test_image(&session, test_data::web::image_urls::WIDE_CORGI_SHIBA_TREASURE_OCEAN_URL).await?;
|
||||
|
||||
let result = generate_seedance_2p0_fast(GenerateSeedance2p0FastArgs {
|
||||
session: &session,
|
||||
host_override: None,
|
||||
request: GenerateSeedance2p0FastRequest {
|
||||
prompt: "The dogs in @1 set sail across the ocean on a treasure ship.".to_string(),
|
||||
aspect_ratio: Some(KinoviSeedance2p0FastAspectRatio::UltraWide21x9),
|
||||
output_resolution: None,
|
||||
batch_count: None,
|
||||
duration_seconds: 5,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls: Some(vec![start_frame_url]),
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
},
|
||||
}).await?;
|
||||
println!("fast keyframe 21:9 — task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
assert!(!result.task_id.is_empty());
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod keyframe {
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -172,8 +172,6 @@ impl KinoviGenerateVideoRequest {
|
||||
pub enum KinoviAspectRatio {
|
||||
/// 16:9 landscape (1280x720)
|
||||
Landscape16x9,
|
||||
/// 21:9 ultra-wide (1280x540)
|
||||
UltraWide21x9,
|
||||
/// 9:16 portrait (720x1280)
|
||||
Portrait9x16,
|
||||
/// 1:1 square (720x720)
|
||||
@@ -188,7 +186,6 @@ impl KinoviAspectRatio {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Landscape16x9 => "1280x720",
|
||||
Self::UltraWide21x9 => "1280x540",
|
||||
Self::Portrait9x16 => "720x1280",
|
||||
Self::Square1x1 => "720x720",
|
||||
Self::Standard4x3 => "960x720",
|
||||
|
||||
-3
@@ -181,8 +181,6 @@ impl WorkflowRunTaskRequest {
|
||||
pub enum KinoviAspectRatioRaw {
|
||||
/// 16:9 landscape (1280x720)
|
||||
Landscape16x9,
|
||||
/// 21:9 ultra-wide (1280x540)
|
||||
UltraWide21x9,
|
||||
/// 9:16 portrait (720x1280)
|
||||
Portrait9x16,
|
||||
/// 1:1 square (720x720)
|
||||
@@ -197,7 +195,6 @@ impl KinoviAspectRatioRaw {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Landscape16x9 => "1280x720",
|
||||
Self::UltraWide21x9 => "1280x540",
|
||||
Self::Portrait9x16 => "720x1280",
|
||||
Self::Square1x1 => "720x720",
|
||||
Self::Landscape4x3 => "960x720",
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# support-tool
|
||||
|
||||
Production support CLI for debugging and manual operations against external services.
|
||||
|
||||
## Building & Running
|
||||
|
||||
```bash
|
||||
cargo build -p support-tool
|
||||
cargo run -p support-tool -- seedance2pro generate_video --prompt "A corgi at the lake"
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
Requires a `.env-support-tool-secrets` file (or env vars) with:
|
||||
|
||||
- `SEEDANCE2PRO_COOKIES` — session cookies for Kinovi/Seedance2Pro API auth
|
||||
- `ARTCRAFT_COOKIES` — session cookies for ArtCraft API auth (format: `session=...; visitor=...`)
|
||||
|
||||
## Architecture
|
||||
|
||||
- Entry point: `src/main.rs` → normalizes args (underscores optional) → dispatches
|
||||
- Top-level commands in `src/commands/run.rs` (`TopLevelCommand` enum)
|
||||
- Each top-level command has its own module with `dispatch.rs`, `state.rs`, `subcommands/`
|
||||
- Subcommands use `#[derive(Args)]` for their argument structs
|
||||
- All subcommand `run()` functions are `async fn` returning `anyhow::Result<()>`
|
||||
|
||||
## Adding a New Subcommand (to seedance2pro)
|
||||
|
||||
1. Create `src/commands/seedance2pro/subcommands/my_command.rs`
|
||||
2. Add `pub mod my_command;` to `subcommands/mod.rs`
|
||||
3. Add the name to `SUBCOMMAND_NAMES` in `dispatch.rs`
|
||||
4. Add a variant to `Seedance2proCommand` enum in `dispatch.rs`
|
||||
5. Add a match arm in `dispatch.rs::run()`
|
||||
|
||||
## Utilities
|
||||
|
||||
- `src/utils/parse_video_model.rs` — shared parser for loose video model names → `CommonVideoModel`
|
||||
- `src/utils/normalize_subcommands.rs` — underscore-insensitive arg normalizer
|
||||
|
||||
## Conventions
|
||||
|
||||
- Use `log::info!()` for status output, never `println!` (except for final results)
|
||||
- Use `anyhow::anyhow!()` for ad-hoc errors
|
||||
- Get `Seedance2ProSession` from `state.cookies` via `from_cookies_string()`
|
||||
- Get `StorytellerCredentialSet` from `state.creds` (parsed from ARTCRAFT_COOKIES)
|
||||
- External crates used: `artcraft_client` (media file download, omni API), `seedance2pro_client` (Kinovi direct), `artcraft_api_defs` (request types)
|
||||
|
||||
## Subcommands
|
||||
|
||||
### seedance2pro (direct Kinovi API)
|
||||
|
||||
- `seedance2pro find_job --token <order_id>` — search for a job across all pages
|
||||
- `seedance2pro failed_job_histogram` — histogram of failure reasons
|
||||
- `seedance2pro generate_video --prompt <text_or_file> [--model seedance2p0] [--start_frame_media_token mf_xxx] [--localhost] [--download_path /tmp/media_files]`
|
||||
|
||||
### artcraft (omni API)
|
||||
|
||||
- `artcraft generate_video --prompt <text_or_file> [--model seedance2p0] [--start_frame_media_token mf_xxx] [--duration 5] [--localhost]`
|
||||
@@ -11,12 +11,8 @@ path = "src/main.rs"
|
||||
easyenv.workspace = true
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs.workspace = true
|
||||
artcraft_client.workspace = true
|
||||
enums.workspace = true
|
||||
errors.workspace = true
|
||||
seedance2pro_client.workspace = true
|
||||
tokens.workspace = true
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
@@ -24,4 +20,3 @@ clap.workspace = true
|
||||
log.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use clap::Subcommand;
|
||||
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
use super::state::ArtcraftState;
|
||||
use super::subcommands;
|
||||
|
||||
/// All canonical subcommand names for this module.
|
||||
/// Used by the underscore-insensitive arg normalizer.
|
||||
pub const SUBCOMMAND_NAMES: &[&str] = &["generate_video"];
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[command(rename_all = "snake_case")]
|
||||
pub enum ArtcraftCommand {
|
||||
/// Generate a video via the ArtCraft omni endpoint
|
||||
GenerateVideo(subcommands::generate_video::GenerateVideoArgs),
|
||||
}
|
||||
|
||||
pub async fn run(command: ArtcraftCommand) -> anyhow::Result<()> {
|
||||
let cookies_str = easyenv::get_env_string_required("ARTCRAFT_COOKIES")
|
||||
.map_err(|err| anyhow!("Missing ARTCRAFT_COOKIES env var: {:?}", err))?;
|
||||
|
||||
let creds = StorytellerCredentialSet::parse_multi_cookie_header(&cookies_str)
|
||||
.map_err(|err| anyhow!("Failed to parse ARTCRAFT_COOKIES: {:?}", err))?
|
||||
.ok_or_else(|| anyhow!("ARTCRAFT_COOKIES contained no recognized cookies (expected session=... and/or visitor=...)"))?;
|
||||
|
||||
let state = ArtcraftState { creds };
|
||||
|
||||
match command {
|
||||
ArtcraftCommand::GenerateVideo(args) => subcommands::generate_video::run(&state, args).await,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
pub mod dispatch;
|
||||
mod state;
|
||||
mod subcommands;
|
||||
|
||||
pub use dispatch::ArtcraftCommand;
|
||||
pub use dispatch::run;
|
||||
@@ -1,5 +0,0 @@
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
pub struct ArtcraftState {
|
||||
pub creds: StorytellerCredentialSet,
|
||||
}
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use clap::Args;
|
||||
use log::info;
|
||||
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
use artcraft_client::endpoints::omni_gen::generate::video::omni_gen_video::omni_gen_video_generate;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::utils::parse_video_model::parse_video_model;
|
||||
use super::super::state::ArtcraftState;
|
||||
|
||||
// ── Args ──
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(
|
||||
override_usage = "support-tool artcraft generate_video [OPTIONS] --prompt <PROMPT>",
|
||||
after_help = "\
|
||||
EXAMPLES:
|
||||
support-tool artcraft generate_video --prompt \"A corgi at the lake\" --model seedance2
|
||||
support-tool artcraft generate_video --prompt prompt.txt --model veo3
|
||||
support-tool artcraft generate_video --prompt \"Dancing\" --start_frame_media_token mf_abc123
|
||||
support-tool artcraft generate_video --prompt \"Cat\" --image_reference_tokens \"mf_abc,mf_def\" --model happyhorse
|
||||
",
|
||||
)]
|
||||
pub struct GenerateVideoArgs {
|
||||
/// A prompt string or path to a file containing the prompt (.txt or .md).
|
||||
#[arg(long)]
|
||||
pub prompt: String,
|
||||
|
||||
/// A single media token for the start frame image.
|
||||
#[arg(long)]
|
||||
pub start_frame_media_token: Option<String>,
|
||||
|
||||
/// A single media token for the end frame image.
|
||||
#[arg(long)]
|
||||
pub end_frame_media_token: Option<String>,
|
||||
|
||||
/// Comma or space separated media tokens for image references.
|
||||
#[arg(long)]
|
||||
pub image_reference_tokens: Option<String>,
|
||||
|
||||
/// Comma or space separated media tokens for video references.
|
||||
#[arg(long)]
|
||||
pub video_reference_tokens: Option<String>,
|
||||
|
||||
/// Comma or space separated media tokens for audio references.
|
||||
#[arg(long)]
|
||||
pub audio_reference_tokens: Option<String>,
|
||||
|
||||
/// Use localhost:12345 instead of production API.
|
||||
#[arg(long)]
|
||||
pub localhost: bool,
|
||||
|
||||
/// Video model to use. Accepts canonical names (eg. "seedance_2p0") or aliases
|
||||
/// (eg. "seedance2", "happyhorse", "veo3fast"). [default: seedance2p0]
|
||||
#[arg(long)]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Duration in seconds. [default: 5]
|
||||
#[arg(long)]
|
||||
pub duration: Option<u16>,
|
||||
|
||||
/// Idempotency token (auto-generated if not specified).
|
||||
#[arg(long)]
|
||||
pub idempotency_token: Option<String>,
|
||||
}
|
||||
|
||||
// ── Entry point ──
|
||||
|
||||
pub async fn run(state: &ArtcraftState, args: GenerateVideoArgs) -> anyhow::Result<()> {
|
||||
let model_str = args.model.as_deref().unwrap_or("seedance2p0");
|
||||
let model = parse_video_model(model_str)
|
||||
.ok_or_else(|| anyhow!("Unknown model '{}'. Try --help for supported names.", model_str))?;
|
||||
|
||||
let prompt = resolve_prompt(&args.prompt)?;
|
||||
let api_host = if args.localhost { ApiHost::Localhost { port: 12345 } } else { ApiHost::Storyteller };
|
||||
let duration = args.duration.unwrap_or(5);
|
||||
|
||||
info!("Model: {:?}", model);
|
||||
info!("Prompt: {:?}", prompt);
|
||||
info!("Duration: {}s", duration);
|
||||
|
||||
// Parse media tokens.
|
||||
let start_frame_token = args.start_frame_media_token.as_deref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| MediaFileToken::new_from_str(s));
|
||||
|
||||
let end_frame_token = args.end_frame_media_token.as_deref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| MediaFileToken::new_from_str(s));
|
||||
|
||||
let image_ref_tokens = parse_media_tokens(args.image_reference_tokens.as_deref());
|
||||
let video_ref_tokens = parse_media_tokens(args.video_reference_tokens.as_deref());
|
||||
let audio_ref_tokens = parse_media_tokens(args.audio_reference_tokens.as_deref());
|
||||
|
||||
if let Some(t) = &start_frame_token { info!("Start frame: {}", t.as_str()); }
|
||||
if let Some(t) = &end_frame_token { info!("End frame: {}", t.as_str()); }
|
||||
if !image_ref_tokens.is_empty() { info!("Image refs: {:?}", image_ref_tokens.iter().map(|t| t.as_str()).collect::<Vec<_>>()); }
|
||||
if !video_ref_tokens.is_empty() { info!("Video refs: {:?}", video_ref_tokens.iter().map(|t| t.as_str()).collect::<Vec<_>>()); }
|
||||
if !audio_ref_tokens.is_empty() { info!("Audio refs: {:?}", audio_ref_tokens.iter().map(|t| t.as_str()).collect::<Vec<_>>()); }
|
||||
|
||||
// Build the omni request. Media tokens are already uploaded — no download/re-upload needed.
|
||||
let idempotency_token = args.idempotency_token
|
||||
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
||||
|
||||
let request = OmniGenVideoCostAndGenerateRequest {
|
||||
idempotency_token: Some(idempotency_token.clone()),
|
||||
model: Some(model),
|
||||
prompt: Some(prompt),
|
||||
negative_prompt: None,
|
||||
start_frame_image_media_token: start_frame_token,
|
||||
end_frame_image_media_token: end_frame_token,
|
||||
reference_image_media_tokens: if image_ref_tokens.is_empty() { None } else { Some(image_ref_tokens) },
|
||||
reference_video_media_tokens: if video_ref_tokens.is_empty() { None } else { Some(video_ref_tokens) },
|
||||
reference_audio_media_tokens: if audio_ref_tokens.is_empty() { None } else { Some(audio_ref_tokens) },
|
||||
reference_character_tokens: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
duration_seconds: Some(duration),
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
};
|
||||
|
||||
info!("Sending request to omni endpoint (idempotency_token={})...", idempotency_token);
|
||||
|
||||
let response = omni_gen_video_generate(
|
||||
&api_host,
|
||||
Some(&state.creds),
|
||||
request,
|
||||
).await.map_err(|err| anyhow!("Omni video generation failed: {}", err))?;
|
||||
|
||||
info!("Success!");
|
||||
info!("Primary job token: {}", response.inference_job_token.as_str());
|
||||
info!("All job tokens: {:?}", response.all_job_tokens.iter().map(|t| t.as_str()).collect::<Vec<_>>());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn resolve_prompt(input: &str) -> anyhow::Result<String> {
|
||||
let looks_like_file = input.ends_with(".txt") || input.ends_with(".md");
|
||||
|
||||
if looks_like_file {
|
||||
let path = Path::new(input);
|
||||
if !path.exists() {
|
||||
return Err(anyhow!("Prompt file does not exist: {:?}", path));
|
||||
}
|
||||
let content = fs::read_to_string(path)
|
||||
.map_err(|err| anyhow!("Failed to read prompt file {:?}: {}", path, err))?;
|
||||
let trimmed = content.trim().to_string();
|
||||
info!("Read prompt from file {:?} ({} chars)", path, trimmed.len());
|
||||
Ok(trimmed)
|
||||
} else {
|
||||
info!("Using prompt string directly ({} chars)", input.len());
|
||||
Ok(input.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_media_tokens(input: Option<&str>) -> Vec<MediaFileToken> {
|
||||
let input = match input {
|
||||
Some(s) if !s.trim().is_empty() => s,
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
|
||||
input
|
||||
.split(|c: char| c == ',' || c.is_whitespace())
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| MediaFileToken::new_from_str(s))
|
||||
.collect()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod generate_video;
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod artcraft;
|
||||
pub mod run;
|
||||
pub mod seedance2pro;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use super::artcraft;
|
||||
use super::seedance2pro;
|
||||
|
||||
/// All canonical subcommand names across all modules.
|
||||
/// Used by the underscore-insensitive arg normalizer.
|
||||
pub fn all_canonical_names() -> Vec<&'static str> {
|
||||
let mut names: Vec<&str> = vec!["seedance2pro", "artcraft"];
|
||||
let mut names: Vec<&str> = vec!["seedance2pro"];
|
||||
names.extend_from_slice(seedance2pro::dispatch::SUBCOMMAND_NAMES);
|
||||
names.extend_from_slice(artcraft::dispatch::SUBCOMMAND_NAMES);
|
||||
names
|
||||
}
|
||||
|
||||
@@ -22,17 +20,11 @@ pub struct Cli {
|
||||
#[derive(Subcommand)]
|
||||
#[command(rename_all = "snake_case")]
|
||||
pub enum TopLevelCommand {
|
||||
/// Seedance2 Pro support commands (direct Kinovi API)
|
||||
/// Seedance2 Pro support commands
|
||||
Seedance2pro {
|
||||
#[command(subcommand)]
|
||||
command: seedance2pro::Seedance2proCommand,
|
||||
},
|
||||
|
||||
/// ArtCraft support commands (omni API)
|
||||
Artcraft {
|
||||
#[command(subcommand)]
|
||||
command: artcraft::ArtcraftCommand,
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn run(cli: Cli) -> anyhow::Result<()> {
|
||||
@@ -40,8 +32,5 @@ pub async fn run(cli: Cli) -> anyhow::Result<()> {
|
||||
TopLevelCommand::Seedance2pro { command } => {
|
||||
seedance2pro::run(command).await
|
||||
}
|
||||
TopLevelCommand::Artcraft { command } => {
|
||||
artcraft::run(command).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,7 @@ use super::subcommands;
|
||||
|
||||
/// All canonical subcommand names for this module.
|
||||
/// Used by the underscore-insensitive arg normalizer.
|
||||
pub const SUBCOMMAND_NAMES: &[&str] = &[
|
||||
"failed_job_histogram",
|
||||
"find_job",
|
||||
"generate_video",
|
||||
];
|
||||
pub const SUBCOMMAND_NAMES: &[&str] = &["find_job", "failed_job_histogram"];
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[command(rename_all = "snake_case")]
|
||||
@@ -20,9 +16,6 @@ pub enum Seedance2proCommand {
|
||||
|
||||
/// Scan all jobs and print a histogram of failure reasons
|
||||
FailedJobHistogram,
|
||||
|
||||
/// Generate a video via Seedance2Pro/Kinovi directly
|
||||
GenerateVideo(subcommands::generate_video::GenerateVideoArgs),
|
||||
}
|
||||
|
||||
pub async fn run(command: Seedance2proCommand) -> anyhow::Result<()> {
|
||||
@@ -34,6 +27,5 @@ pub async fn run(command: Seedance2proCommand) -> anyhow::Result<()> {
|
||||
match command {
|
||||
Seedance2proCommand::FindJob(args) => subcommands::find_job::run(&state, args).await,
|
||||
Seedance2proCommand::FailedJobHistogram => subcommands::failed_job_histogram::run(&state).await,
|
||||
Seedance2proCommand::GenerateVideo(args) => subcommands::generate_video::run(&state, args).await,
|
||||
}
|
||||
}
|
||||
|
||||
-475
@@ -1,475 +0,0 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use clap::Args;
|
||||
use log::info;
|
||||
|
||||
use artcraft_client::recipes::download_media_file::{
|
||||
download_media_file, DownloadMediaFileArgs, DownloadPath,
|
||||
};
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use seedance2pro_client::generate::video::generate_happy_horse_1p0::{
|
||||
generate_happy_horse_1p0, GenerateHappyHorse1p0Args, GenerateHappyHorse1p0Request,
|
||||
};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
generate_seedance_2p0, GenerateSeedance2p0Args, GenerateSeedance2p0Request,
|
||||
};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
generate_seedance_2p0_fast, GenerateSeedance2p0FastArgs, GenerateSeedance2p0FastRequest,
|
||||
};
|
||||
use seedance2pro_client::requests::prepare_file_upload::prepare_file_upload::{
|
||||
prepare_file_upload, PrepareFileUploadArgs,
|
||||
};
|
||||
use seedance2pro_client::requests::upload_file::upload_file::{upload_file, UploadFileArgs};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use super::super::state::Seedance2ProState;
|
||||
|
||||
const DEFAULT_DOWNLOAD_PATH: &str = "/tmp/media_files";
|
||||
|
||||
// ── Args ──
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(
|
||||
override_usage = "support-tool seedance2pro generate_video [OPTIONS] --prompt <PROMPT>",
|
||||
after_help = "\
|
||||
EXAMPLES:
|
||||
support-tool seedance2pro generate_video --prompt \"A corgi at the lake\"
|
||||
support-tool seedance2pro generate_video --prompt prompt.txt --model happyhorse
|
||||
support-tool seedance2pro generate_video --prompt \"Dancing\" --start_frame_media_token mf_abc123
|
||||
support-tool seedance2pro generate_video --prompt \"Cat\" --image_reference_tokens \"mf_abc,mf_def\"
|
||||
",
|
||||
)]
|
||||
pub struct GenerateVideoArgs {
|
||||
/// A prompt string or path to a file containing the prompt (.txt or .md).
|
||||
#[arg(long)]
|
||||
pub prompt: String,
|
||||
|
||||
/// A single media token for the start frame image.
|
||||
#[arg(long)]
|
||||
pub start_frame_media_token: Option<String>,
|
||||
|
||||
/// A single media token for the end frame image.
|
||||
#[arg(long)]
|
||||
pub end_frame_media_token: Option<String>,
|
||||
|
||||
/// Comma or space separated media tokens for image references.
|
||||
#[arg(long)]
|
||||
pub image_reference_tokens: Option<String>,
|
||||
|
||||
/// Comma or space separated media tokens for video references.
|
||||
#[arg(long)]
|
||||
pub video_reference_tokens: Option<String>,
|
||||
|
||||
/// Comma or space separated media tokens for audio references.
|
||||
#[arg(long)]
|
||||
pub audio_reference_tokens: Option<String>,
|
||||
|
||||
/// Use localhost:12345 instead of production API for artcraft_client lookups.
|
||||
#[arg(long)]
|
||||
pub localhost: bool,
|
||||
|
||||
/// Directory to cache downloaded media files. [default: /tmp/media_files]
|
||||
#[arg(long)]
|
||||
pub download_path: Option<String>,
|
||||
|
||||
/// Model: seedance2p0, seedance2p0fast, happyhorse (and underscore variants).
|
||||
/// [default: seedance2p0]
|
||||
#[arg(long)]
|
||||
pub model: Option<String>,
|
||||
}
|
||||
|
||||
// ── Model enum ──
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum VideoModel {
|
||||
Seedance2p0,
|
||||
Seedance2p0Fast,
|
||||
HappyHorse,
|
||||
}
|
||||
|
||||
fn parse_model(value: &str) -> anyhow::Result<VideoModel> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"seedance2" | "seedance2p0" | "seedance_2p0" => Ok(VideoModel::Seedance2p0),
|
||||
"seedance2fast" | "seedance2p0fast" | "seedance_2p0_fast" => Ok(VideoModel::Seedance2p0Fast),
|
||||
"happyhorse" | "happy_horse" => Ok(VideoModel::HappyHorse),
|
||||
other => Err(anyhow!(
|
||||
"Unknown model '{}'. Valid: seedance2, seedance2p0, seedance_2p0, \
|
||||
seedance2fast, seedance2p0fast, seedance_2p0_fast, happyhorse, happy_horse",
|
||||
other
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resolved media inputs ──
|
||||
|
||||
struct ResolvedMediaInputs {
|
||||
start_frame_url: Option<String>,
|
||||
end_frame_url: Option<String>,
|
||||
reference_image_urls: Option<Vec<String>>,
|
||||
reference_video_urls: Option<Vec<String>>,
|
||||
reference_audio_urls: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// ── Entry point ──
|
||||
|
||||
pub async fn run(state: &Seedance2ProState, args: GenerateVideoArgs) -> anyhow::Result<()> {
|
||||
let session = Seedance2ProSession::from_cookies_string(state.cookies.clone());
|
||||
let model = parse_model(args.model.as_deref().unwrap_or("seedance2p0"))?;
|
||||
let prompt = resolve_prompt(&args.prompt)?;
|
||||
let api_host = if args.localhost { ApiHost::Localhost { port: 12345 } } else { ApiHost::Storyteller };
|
||||
|
||||
info!("Model: {:?}", model);
|
||||
info!("Prompt: {:?}", prompt);
|
||||
|
||||
// Resolve download path.
|
||||
let download_dir = resolve_download_dir(args.download_path.as_deref())?;
|
||||
info!("Download cache directory: {:?}", download_dir);
|
||||
|
||||
// Parse media tokens.
|
||||
let start_frame_token = args.start_frame_media_token.as_deref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| MediaFileToken::new_from_str(s));
|
||||
|
||||
let end_frame_token = args.end_frame_media_token.as_deref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| MediaFileToken::new_from_str(s));
|
||||
|
||||
let image_ref_tokens = parse_media_tokens(args.image_reference_tokens.as_deref());
|
||||
let video_ref_tokens = parse_media_tokens(args.video_reference_tokens.as_deref());
|
||||
let audio_ref_tokens = parse_media_tokens(args.audio_reference_tokens.as_deref());
|
||||
|
||||
// Collect all tokens that need downloading.
|
||||
let mut all_tokens: Vec<MediaFileToken> = Vec::new();
|
||||
if let Some(t) = &start_frame_token { all_tokens.push(t.clone()); }
|
||||
if let Some(t) = &end_frame_token { all_tokens.push(t.clone()); }
|
||||
all_tokens.extend(image_ref_tokens.iter().cloned());
|
||||
all_tokens.extend(video_ref_tokens.iter().cloned());
|
||||
all_tokens.extend(audio_ref_tokens.iter().cloned());
|
||||
|
||||
info!("Total media tokens to process: {}", all_tokens.len());
|
||||
|
||||
// Download media files (with caching).
|
||||
let downloaded_files = download_media_files(&all_tokens, &api_host, &download_dir).await?;
|
||||
|
||||
// Upload media files to Kinovi/Seedance2Pro.
|
||||
let uploaded_urls = upload_media_files_to_kinovi(&session, &downloaded_files).await?;
|
||||
|
||||
// Map uploaded URLs back to their roles.
|
||||
let mut url_iter = uploaded_urls.into_iter();
|
||||
|
||||
let start_frame_url = if start_frame_token.is_some() { url_iter.next() } else { None };
|
||||
let end_frame_url = if end_frame_token.is_some() { url_iter.next() } else { None };
|
||||
|
||||
let reference_image_urls: Vec<String> = url_iter.by_ref().take(image_ref_tokens.len()).collect();
|
||||
let reference_video_urls: Vec<String> = url_iter.by_ref().take(video_ref_tokens.len()).collect();
|
||||
let reference_audio_urls: Vec<String> = url_iter.by_ref().take(audio_ref_tokens.len()).collect();
|
||||
|
||||
let media_inputs = ResolvedMediaInputs {
|
||||
start_frame_url,
|
||||
end_frame_url,
|
||||
reference_image_urls: if reference_image_urls.is_empty() { None } else { Some(reference_image_urls) },
|
||||
reference_video_urls: if reference_video_urls.is_empty() { None } else { Some(reference_video_urls) },
|
||||
reference_audio_urls: if reference_audio_urls.is_empty() { None } else { Some(reference_audio_urls) },
|
||||
};
|
||||
|
||||
// Generate video.
|
||||
generate(model, &session, &prompt, media_inputs).await
|
||||
}
|
||||
|
||||
// ── Prompt resolution ──
|
||||
|
||||
fn resolve_prompt(input: &str) -> anyhow::Result<String> {
|
||||
let looks_like_file = input.ends_with(".txt") || input.ends_with(".md");
|
||||
|
||||
if looks_like_file {
|
||||
let path = Path::new(input);
|
||||
if !path.exists() {
|
||||
return Err(anyhow!("Prompt file does not exist: {:?}", path));
|
||||
}
|
||||
let content = fs::read_to_string(path)
|
||||
.map_err(|err| anyhow!("Failed to read prompt file {:?}: {}", path, err))?;
|
||||
let trimmed = content.trim().to_string();
|
||||
info!("Read prompt from file {:?} ({} chars)", path, trimmed.len());
|
||||
Ok(trimmed)
|
||||
} else {
|
||||
info!("Using prompt string directly ({} chars)", input.len());
|
||||
Ok(input.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// ── Download path resolution ──
|
||||
|
||||
fn resolve_download_dir(maybe_path: Option<&str>) -> anyhow::Result<PathBuf> {
|
||||
let dir = match maybe_path {
|
||||
Some(path) => {
|
||||
let p = PathBuf::from(path);
|
||||
if p.exists() && !p.is_dir() {
|
||||
return Err(anyhow!("--download_path {:?} exists but is not a directory", p));
|
||||
}
|
||||
p
|
||||
}
|
||||
None => PathBuf::from(DEFAULT_DOWNLOAD_PATH),
|
||||
};
|
||||
|
||||
if !dir.exists() {
|
||||
info!("Creating download directory: {:?}", dir);
|
||||
fs::create_dir_all(&dir)
|
||||
.map_err(|err| anyhow!("Failed to create download directory {:?}: {}", dir, err))?;
|
||||
}
|
||||
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
// ── Media token parsing ──
|
||||
|
||||
fn parse_media_tokens(input: Option<&str>) -> Vec<MediaFileToken> {
|
||||
let input = match input {
|
||||
Some(s) if !s.trim().is_empty() => s,
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
|
||||
input
|
||||
.split(|c: char| c == ',' || c.is_whitespace())
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| MediaFileToken::new_from_str(s))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ── Download media files with caching ──
|
||||
|
||||
struct DownloadedFile {
|
||||
path: PathBuf,
|
||||
extension: String,
|
||||
}
|
||||
|
||||
async fn download_media_files(
|
||||
tokens: &[MediaFileToken],
|
||||
api_host: &ApiHost,
|
||||
download_dir: &Path,
|
||||
) -> anyhow::Result<Vec<DownloadedFile>> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for token in tokens {
|
||||
// Check if file already exists in cache.
|
||||
let maybe_cached = find_cached_file(download_dir, token);
|
||||
|
||||
if let Some(cached) = maybe_cached {
|
||||
info!(
|
||||
"Cache hit: {} already exists at {:?} ({} bytes)",
|
||||
token.as_str(),
|
||||
cached.path,
|
||||
fs::metadata(&cached.path).map(|m| m.len()).unwrap_or(0),
|
||||
);
|
||||
results.push(cached);
|
||||
continue;
|
||||
}
|
||||
|
||||
info!("Downloading {} from API...", token.as_str());
|
||||
|
||||
let result = download_media_file(DownloadMediaFileArgs {
|
||||
media_token: token,
|
||||
api_host,
|
||||
download_path: DownloadPath::Directory(download_dir),
|
||||
}).await.map_err(|err| anyhow!("Failed to download {}: {}", token.as_str(), err))?;
|
||||
|
||||
let extension = result.downloaded_file_path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("bin")
|
||||
.to_string();
|
||||
|
||||
info!(
|
||||
"Downloaded: {} -> {:?} ({} bytes, class: {})",
|
||||
token.as_str(),
|
||||
result.downloaded_file_path,
|
||||
result.filesize_bytes,
|
||||
result.media_file_response.media_file.media_class.to_str(),
|
||||
);
|
||||
|
||||
results.push(DownloadedFile {
|
||||
path: result.downloaded_file_path,
|
||||
extension,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Look for an existing cached file matching the token (any extension).
|
||||
fn find_cached_file(download_dir: &Path, token: &MediaFileToken) -> Option<DownloadedFile> {
|
||||
let prefix = format!("{}.", token.as_str());
|
||||
|
||||
let entries = fs::read_dir(download_dir).ok()?;
|
||||
for entry in entries.flatten() {
|
||||
let filename = entry.file_name();
|
||||
let filename_str = filename.to_string_lossy();
|
||||
if filename_str.starts_with(&prefix) {
|
||||
let path = entry.path();
|
||||
// Skip zero-byte files — treat as incomplete downloads.
|
||||
let metadata = fs::metadata(&path).ok()?;
|
||||
if metadata.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
let extension = path.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("bin")
|
||||
.to_string();
|
||||
return Some(DownloadedFile { path, extension });
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// ── Upload to Kinovi/Seedance2Pro ──
|
||||
|
||||
async fn upload_media_files_to_kinovi(
|
||||
session: &Seedance2ProSession,
|
||||
files: &[DownloadedFile],
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let mut urls = Vec::new();
|
||||
|
||||
for file in files {
|
||||
info!("Uploading {:?} to Kinovi (extension: {})...", file.path, file.extension);
|
||||
|
||||
// 1. Prepare upload (get signed URL).
|
||||
let prepare_result = prepare_file_upload(PrepareFileUploadArgs {
|
||||
session,
|
||||
extension: file.extension.clone(),
|
||||
host_override: None,
|
||||
}).await.map_err(|err| anyhow!("Failed to prepare upload for {:?}: {:?}", file.path, err))?;
|
||||
|
||||
// 2. Read file bytes.
|
||||
let file_bytes = fs::read(&file.path)
|
||||
.map_err(|err| anyhow!("Failed to read {:?}: {}", file.path, err))?;
|
||||
|
||||
info!("Uploading {} bytes to signed URL...", file_bytes.len());
|
||||
|
||||
// 3. Upload to Cloudflare R2.
|
||||
let upload_result = upload_file(UploadFileArgs {
|
||||
upload_url: prepare_result.upload_url,
|
||||
file_bytes,
|
||||
host_override: None,
|
||||
}).await.map_err(|err| anyhow!("Failed to upload {:?}: {:?}", file.path, err))?;
|
||||
|
||||
info!("Uploaded: {:?} -> {}", file.path, upload_result.public_url);
|
||||
urls.push(upload_result.public_url);
|
||||
}
|
||||
|
||||
Ok(urls)
|
||||
}
|
||||
|
||||
// ── Generate video ──
|
||||
|
||||
async fn generate(
|
||||
model: VideoModel,
|
||||
session: &Seedance2ProSession,
|
||||
prompt: &str,
|
||||
media: ResolvedMediaInputs,
|
||||
) -> anyhow::Result<()> {
|
||||
match model {
|
||||
VideoModel::HappyHorse => {
|
||||
// Happy Horse only supports start_frame_url.
|
||||
if media.end_frame_url.is_some() {
|
||||
return Err(anyhow!("Happy Horse does not support --end_frame_media_token"));
|
||||
}
|
||||
if media.reference_image_urls.is_some() {
|
||||
return Err(anyhow!("Happy Horse does not support --image_reference_tokens"));
|
||||
}
|
||||
if media.reference_video_urls.is_some() {
|
||||
return Err(anyhow!("Happy Horse does not support --video_reference_tokens"));
|
||||
}
|
||||
if media.reference_audio_urls.is_some() {
|
||||
return Err(anyhow!("Happy Horse does not support --audio_reference_tokens"));
|
||||
}
|
||||
|
||||
info!("Generating video with Happy Horse 1.0...");
|
||||
let result = generate_happy_horse_1p0(GenerateHappyHorse1p0Args {
|
||||
request: GenerateHappyHorse1p0Request {
|
||||
prompt: prompt.to_string(),
|
||||
aspect_ratio: None,
|
||||
output_resolution: None,
|
||||
batch_count: None,
|
||||
duration_seconds: 5,
|
||||
start_frame_url: media.start_frame_url,
|
||||
},
|
||||
session,
|
||||
host_override: None,
|
||||
}).await.map_err(|err| anyhow!("Happy Horse generation failed: {:?}", err))?;
|
||||
|
||||
info!("Success! task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
if let Some(task_ids) = &result.task_ids {
|
||||
info!("All task_ids: {:?}", task_ids);
|
||||
}
|
||||
if let Some(order_ids) = &result.order_ids {
|
||||
info!("All order_ids: {:?}", order_ids);
|
||||
}
|
||||
}
|
||||
VideoModel::Seedance2p0 => {
|
||||
info!("Generating video with Seedance 2.0 Pro...");
|
||||
let result = generate_seedance_2p0(GenerateSeedance2p0Args {
|
||||
request: GenerateSeedance2p0Request {
|
||||
prompt: prompt.to_string(),
|
||||
aspect_ratio: None,
|
||||
output_resolution: None,
|
||||
duration_seconds: 5,
|
||||
batch_count: None,
|
||||
start_frame_url: media.start_frame_url,
|
||||
end_frame_url: media.end_frame_url,
|
||||
reference_image_urls: media.reference_image_urls,
|
||||
reference_video_urls: media.reference_video_urls,
|
||||
reference_audio_urls: media.reference_audio_urls,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
},
|
||||
session,
|
||||
host_override: None,
|
||||
}).await.map_err(|err| anyhow!("Seedance 2.0 generation failed: {:?}", err))?;
|
||||
|
||||
info!("Success! task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
if let Some(task_ids) = &result.task_ids {
|
||||
info!("All task_ids: {:?}", task_ids);
|
||||
}
|
||||
if let Some(order_ids) = &result.order_ids {
|
||||
info!("All order_ids: {:?}", order_ids);
|
||||
}
|
||||
}
|
||||
VideoModel::Seedance2p0Fast => {
|
||||
info!("Generating video with Seedance 2.0 Fast...");
|
||||
let result = generate_seedance_2p0_fast(GenerateSeedance2p0FastArgs {
|
||||
request: GenerateSeedance2p0FastRequest {
|
||||
prompt: prompt.to_string(),
|
||||
aspect_ratio: None,
|
||||
output_resolution: None,
|
||||
duration_seconds: 5,
|
||||
batch_count: None,
|
||||
start_frame_url: media.start_frame_url,
|
||||
end_frame_url: media.end_frame_url,
|
||||
reference_image_urls: media.reference_image_urls,
|
||||
reference_video_urls: media.reference_video_urls,
|
||||
reference_audio_urls: media.reference_audio_urls,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
},
|
||||
session,
|
||||
host_override: None,
|
||||
}).await.map_err(|err| anyhow!("Seedance 2.0 Fast generation failed: {:?}", err))?;
|
||||
|
||||
info!("Success! task_id={}, order_id={}", result.task_id, result.order_id);
|
||||
if let Some(task_ids) = &result.task_ids {
|
||||
info!("All task_ids: {:?}", task_ids);
|
||||
}
|
||||
if let Some(order_ids) = &result.order_ids {
|
||||
info!("All order_ids: {:?}", order_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod failed_job_histogram;
|
||||
pub mod find_job;
|
||||
pub mod generate_video;
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub mod normalize_subcommands;
|
||||
pub mod parse_video_model;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
use enums::common::generation::common_video_model::CommonVideoModel;
|
||||
|
||||
/// Parse a loose model name into a CommonVideoModel.
|
||||
///
|
||||
/// Accepts the canonical serde names (eg. "seedance_2p0") as well as
|
||||
/// human-friendly aliases (eg. "seedance2", "happyhorse", "veo3fast").
|
||||
pub fn parse_video_model(value: &str) -> Option<CommonVideoModel> {
|
||||
let value = value.trim();
|
||||
|
||||
// Try canonical serde deserialization first (eg. "seedance_2p0", "happy_horse_1p0").
|
||||
if let Ok(model) = serde_json::from_str::<CommonVideoModel>(&format!("\"{}\"", value)) {
|
||||
return Some(model);
|
||||
}
|
||||
|
||||
// Fallback: human-friendly aliases.
|
||||
match value.to_lowercase().replace('-', "_").as_str() {
|
||||
// Seedance family
|
||||
"seedance2" | "seedance2p0" | "seedance_2p0" => Some(CommonVideoModel::Seedance2p0),
|
||||
"seedance2fast" | "seedance2p0fast" | "seedance_2p0_fast" => Some(CommonVideoModel::Seedance2p0Fast),
|
||||
"seedance1" | "seedance10lite" | "seedance_1p0_lite" => Some(CommonVideoModel::Seedance10Lite),
|
||||
"seedance15" | "seedance1p5" | "seedance_1p5_pro" => Some(CommonVideoModel::Seedance1p5Pro),
|
||||
|
||||
// Happy Horse
|
||||
"happyhorse" | "happy_horse" | "happyhorse1" | "happy_horse_1p0" => Some(CommonVideoModel::HappyHorse1p0),
|
||||
|
||||
// Kling family
|
||||
"kling16" | "kling_1p6_pro" => Some(CommonVideoModel::Kling16Pro),
|
||||
"kling21" | "kling_2p1_pro" => Some(CommonVideoModel::Kling21Pro),
|
||||
"kling21master" | "kling_2p1_master" => Some(CommonVideoModel::Kling21Master),
|
||||
"kling25turbo" | "kling_2p5_turbo_pro" => Some(CommonVideoModel::Kling2p5TurboPro),
|
||||
"kling26" | "kling_2p6_pro" => Some(CommonVideoModel::Kling2p6Pro),
|
||||
"kling3" | "kling30" | "kling_3p0_standard" => Some(CommonVideoModel::Kling3p0Standard),
|
||||
"kling3pro" | "kling30pro" | "kling_3p0_pro" => Some(CommonVideoModel::Kling3p0Pro),
|
||||
|
||||
// Veo family
|
||||
"veo2" | "veo_2" => Some(CommonVideoModel::Veo2),
|
||||
"veo3" | "veo_3" => Some(CommonVideoModel::Veo3),
|
||||
"veo3fast" | "veo_3_fast" => Some(CommonVideoModel::Veo3Fast),
|
||||
"veo31" | "veo3p1" | "veo_3p1" => Some(CommonVideoModel::Veo3p1),
|
||||
"veo31fast" | "veo3p1fast" | "veo_3p1_fast" => Some(CommonVideoModel::Veo3p1Fast),
|
||||
|
||||
// Sora family
|
||||
"sora2" | "sora_2" => Some(CommonVideoModel::Sora2),
|
||||
"sora2pro" | "sora_2_pro" => Some(CommonVideoModel::Sora2Pro),
|
||||
|
||||
// Grok
|
||||
"grok" | "grok_video" | "grokvideo" => Some(CommonVideoModel::GrokVideo),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn canonical_names() {
|
||||
assert_eq!(parse_video_model("seedance_2p0"), Some(CommonVideoModel::Seedance2p0));
|
||||
assert_eq!(parse_video_model("happy_horse_1p0"), Some(CommonVideoModel::HappyHorse1p0));
|
||||
assert_eq!(parse_video_model("veo_3"), Some(CommonVideoModel::Veo3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliases() {
|
||||
assert_eq!(parse_video_model("seedance2"), Some(CommonVideoModel::Seedance2p0));
|
||||
assert_eq!(parse_video_model("seedance2fast"), Some(CommonVideoModel::Seedance2p0Fast));
|
||||
assert_eq!(parse_video_model("happyhorse"), Some(CommonVideoModel::HappyHorse1p0));
|
||||
assert_eq!(parse_video_model("veo3fast"), Some(CommonVideoModel::Veo3Fast));
|
||||
assert_eq!(parse_video_model("kling3pro"), Some(CommonVideoModel::Kling3p0Pro));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown() {
|
||||
assert_eq!(parse_video_model("nonexistent"), None);
|
||||
assert_eq!(parse_video_model(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trims_whitespace() {
|
||||
assert_eq!(parse_video_model(" seedance2 "), Some(CommonVideoModel::Seedance2p0));
|
||||
}
|
||||
}
|
||||
+4
-8
@@ -37,20 +37,16 @@ pub async fn handle_video_artcraft(
|
||||
None => {
|
||||
Err(GenerateError::no_model_specified())
|
||||
},
|
||||
// Handled via router
|
||||
Some(VideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await,
|
||||
Some(VideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await,
|
||||
Some(VideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await,
|
||||
Some(VideoModel::Seedance2p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance2p0, GenerationModel::Seedance2p0).await,
|
||||
Some(VideoModel::Seedance2p0Fast) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance2p0Fast, GenerationModel::Seedance2p0Fast).await,
|
||||
Some(VideoModel::HappyHorse1p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::HappyHorse1p0, GenerationModel::HappyHorse1p0).await,
|
||||
// Legacy endpoints
|
||||
Some(VideoModel::Kling16Pro) => handle_artcraft_kling_1p6_pro(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Kling21Pro) => handle_artcraft_kling_2p1_pro(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Kling21Master) => handle_artcraft_kling_2p1_master(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Kling2p5TurboPro) => handle_artcraft_kling_2p5_turbo_pro(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Kling2p6Pro) => handle_artcraft_kling_2p6_pro(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await,
|
||||
Some(VideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await,
|
||||
Some(VideoModel::Seedance10Lite) => handle_artcraft_seedance_1_lite(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await,
|
||||
Some(VideoModel::Seedance2p0) => handle_artcraft_seedance_2p0(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Sora2) => handle_artcraft_sora2(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Sora2Pro) => handle_artcraft_sora2_pro(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Veo2) => handle_artcraft_veo2(request, app_env_configs, &creds).await,
|
||||
|
||||
+22
-66
@@ -3,23 +3,18 @@ use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_comma
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_router::api::audio_list_ref::AudioListRef;
|
||||
use artcraft_router::api::character_list_ref::CharacterListRef;
|
||||
use artcraft_router::api::common_video_model::CommonVideoModel;
|
||||
use artcraft_router::api::image_list_ref::ImageListRef;
|
||||
use artcraft_router::api::image_ref::ImageRef;
|
||||
use artcraft_router::api::provider::Provider;
|
||||
use artcraft_router::api::video_list_ref::VideoListRef;
|
||||
use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use artcraft_router::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use artcraft_router::client::router_client::RouterClient;
|
||||
use artcraft_router::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use artcraft_router::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use artcraft_router::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
pub(super) async fn handle_artcraft_video_via_router(
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
@@ -36,43 +31,44 @@ pub(super) async fn handle_artcraft_video_via_router(
|
||||
let start_frame = request.image_media_token.clone().map(ImageRef::MediaFileToken);
|
||||
let end_frame = request.end_frame_image_media_token.clone().map(ImageRef::MediaFileToken);
|
||||
|
||||
let reference_images = request.reference_image_media_tokens.clone().map(ImageListRef::MediaFileTokens);
|
||||
let reference_videos = request.reference_video_media_tokens.clone().map(VideoListRef::MediaFileTokens);
|
||||
let reference_audio = request.reference_audio_media_tokens.clone().map(AudioListRef::MediaFileTokens);
|
||||
|
||||
let reference_character_tokens = request.reference_character_tokens.clone().map(CharacterListRef::CharacterTokens);
|
||||
|
||||
let router_request = GenerateVideoRequestBuilder {
|
||||
model,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: request.prompt.clone(),
|
||||
negative_prompt: None,
|
||||
start_frame,
|
||||
end_frame,
|
||||
reference_images,
|
||||
reference_videos,
|
||||
reference_audio,
|
||||
reference_character_tokens,
|
||||
resolution: request.resolution,
|
||||
reference_images: None,
|
||||
reference_videos: None,
|
||||
reference_audio: None,
|
||||
reference_character_tokens: request.reference_character_tokens.clone().map(CharacterListRef::CharacterTokens),
|
||||
resolution: None,
|
||||
aspect_ratio: request.aspect_ratio,
|
||||
duration_seconds: request.duration_seconds,
|
||||
video_batch_count: request.video_batch_count,
|
||||
generate_audio: request.generate_audio,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
idempotency_token: None,
|
||||
negative_prompt: None,
|
||||
};
|
||||
|
||||
let response = if router_request.use_new_builder() {
|
||||
info!("Building request for artcraft_router (v2 pipeline)...");
|
||||
generate_via_v2(router_request, &client).await?
|
||||
} else {
|
||||
info!("Building request for artcraft_router (v1 pipeline)...");
|
||||
generate_via_v1(router_request, &client).await?
|
||||
let plan = router_request.build()?;
|
||||
|
||||
info!("Video Generation Plan: {:?}", plan);
|
||||
|
||||
let response = match plan.generate_video(&client).await {
|
||||
Ok(resp) => {
|
||||
info!("Successfully enqueued.");
|
||||
resp
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = response.get_artcraft_payload()
|
||||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::VideoGeneration,
|
||||
@@ -81,43 +77,3 @@ pub(super) async fn handle_artcraft_video_via_router(
|
||||
provider_job_id: Some(job_id),
|
||||
})
|
||||
}
|
||||
|
||||
/// V1 pipeline: build → plan → generate_video.
|
||||
async fn generate_via_v1(
|
||||
router_request: GenerateVideoRequestBuilder,
|
||||
client: &RouterClient,
|
||||
) -> Result<GenerateVideoResponse, GenerateError> {
|
||||
let plan = router_request.build()?;
|
||||
|
||||
let response = plan.generate_video(client).await.map_err(|err| {
|
||||
error!("V1 failed to enqueue: {:?}", err);
|
||||
GenerateError::from(err)
|
||||
})?;
|
||||
|
||||
info!("V1 successfully enqueued.");
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// V2 pipeline: build2 → send_request (Artcraft skips draft phase).
|
||||
async fn generate_via_v2(
|
||||
router_request: GenerateVideoRequestBuilder,
|
||||
client: &RouterClient,
|
||||
) -> Result<GenerateVideoResponse, GenerateError> {
|
||||
let draft_or_request = router_request.build2()?;
|
||||
|
||||
let request = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Request(r) => r,
|
||||
VideoGenerationDraftOrRequest::Draft(_) => {
|
||||
error!("Unexpected Draft variant for Artcraft provider");
|
||||
return Err(GenerateError::NotYetImplemented("Artcraft provider should not produce a draft request".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let response = request.send_request(client).await.map_err(|err| {
|
||||
error!("V2 failed to enqueue: {:?}", err);
|
||||
GenerateError::from(err)
|
||||
})?;
|
||||
|
||||
info!("V2 successfully enqueued.");
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
+4
-12
@@ -1,14 +1,17 @@
|
||||
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_video::handle_video_artcraft;
|
||||
use crate::core::commands::enqueue::image_to_video::grok::handle_grok_video::handle_grok_video;
|
||||
use crate::core::commands::enqueue::image_to_video::sora2::handle_sora_sora2::handle_sora_sora2;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::TextToImageModel;
|
||||
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::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::core::events::generation_events::common::GenerationAction;
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
@@ -21,7 +24,6 @@ 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_router::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use artcraft_router::api::common_resolution::CommonResolution;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use log::{error, info, warn};
|
||||
@@ -59,9 +61,6 @@ pub enum VideoModel {
|
||||
#[serde(rename = "kling_3p0_pro")]
|
||||
Kling3p0Pro,
|
||||
|
||||
#[serde(rename = "happy_horse_1p0")]
|
||||
HappyHorse1p0,
|
||||
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
|
||||
@@ -70,9 +69,6 @@ pub enum VideoModel {
|
||||
|
||||
#[serde(rename = "seedance_2p0")]
|
||||
Seedance2p0,
|
||||
|
||||
#[serde(rename = "seedance_2p0_fast")]
|
||||
Seedance2p0Fast,
|
||||
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
@@ -145,10 +141,6 @@ pub struct EnqueueImageToVideoRequest {
|
||||
/// Aspect ratio for the generated video.
|
||||
pub aspect_ratio: Option<CommonAspectRatio>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Resolution for the generated video.
|
||||
pub resolution: Option<CommonResolution>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Duration of the generated video in seconds.
|
||||
pub duration_seconds: Option<u16>,
|
||||
|
||||
@@ -88,11 +88,9 @@ impl TaskEnqueueSuccess{
|
||||
Some(GenerationModel::Kling2p6Pro) => Some(TaskModelType::Kling2p6Pro),
|
||||
Some(GenerationModel::Kling3p0Standard) => Some(TaskModelType::Kling3p0Standard),
|
||||
Some(GenerationModel::Kling3p0Pro) => Some(TaskModelType::Kling3p0Pro),
|
||||
Some(GenerationModel::HappyHorse1p0) => Some(TaskModelType::HappyHorse1p0),
|
||||
Some(GenerationModel::Seedance10Lite) => Some(TaskModelType::Seedance10Lite),
|
||||
Some(GenerationModel::Seedance1p5Pro) => Some(TaskModelType::Seedance1p5Pro),
|
||||
Some(GenerationModel::Seedance2p0) => Some(TaskModelType::Seedance2p0),
|
||||
Some(GenerationModel::Seedance2p0Fast) => Some(TaskModelType::Seedance2p0Fast),
|
||||
Some(GenerationModel::Sora2) => Some(TaskModelType::Sora2),
|
||||
Some(GenerationModel::Sora2Pro) => Some(TaskModelType::Sora2Pro),
|
||||
Some(GenerationModel::Veo2) => Some(TaskModelType::Veo2),
|
||||
|
||||
@@ -95,8 +95,6 @@ pub enum GenerationModel {
|
||||
Kling3p0Standard,
|
||||
#[serde(rename = "kling_3p0_pro")]
|
||||
Kling3p0Pro,
|
||||
#[serde(rename = "happy_horse_1p0")]
|
||||
HappyHorse1p0,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
#[serde(rename = "sora")]
|
||||
@@ -111,8 +109,6 @@ pub enum GenerationModel {
|
||||
Seedance1p5Pro,
|
||||
#[serde(rename = "seedance_2p0")]
|
||||
Seedance2p0,
|
||||
#[serde(rename = "seedance_2p0_fast")]
|
||||
Seedance2p0Fast,
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
#[serde(rename = "veo_3")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
/// The version of the ArtCraft desktop app reported in the UI and via Analytics.
|
||||
/// This should match the CI builds.
|
||||
pub const ARTCRAFT_VERSION: &str = "0.31.0";
|
||||
pub const ARTCRAFT_VERSION: &str = "0.30.0";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
|
||||
"productName": "ArtCraft",
|
||||
"version": "0.31.0",
|
||||
"version": "0.30.0",
|
||||
"identifier": "ai.artcraft.app",
|
||||
"build": {
|
||||
"frontendDist": "../../../frontend/apps/artcraft/dist",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
|
||||
"productName": "ArtCraft",
|
||||
"version": "0.31.0",
|
||||
"version": "0.30.0",
|
||||
"identifier": "ai.artcraft.app",
|
||||
"build": {
|
||||
"frontendDist": "../../../frontend/apps/artcraft/dist",
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use log::warn;
|
||||
use sqlx::{Executor, MySql, QueryBuilder};
|
||||
|
||||
use enums::by_table::debug_logs::debug_log_type::DebugLogType;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct BulkInsertDebugLogsArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
/// If provided, all rows will share this event token.
|
||||
/// If not provided, one will be generated.
|
||||
pub apriori_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
|
||||
/// The user associated with these debug log events (if any).
|
||||
pub maybe_creator_user_token: Option<&'e UserToken>,
|
||||
|
||||
/// The events to insert.
|
||||
pub events: Vec<DebugLogEvent>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// A single debug log event to insert.
|
||||
///
|
||||
/// The `message` field should be a pre-serialized JSON string (or any text).
|
||||
/// Callers should use `serde_json::to_string(...)` before constructing this.
|
||||
pub struct DebugLogEvent {
|
||||
pub debug_log_type: DebugLogType,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// Bulk-insert debug log rows. All rows share the same event token.
|
||||
///
|
||||
/// Returns the event token used. No-op (with a warning) if `events` is empty.
|
||||
pub async fn bulk_insert_debug_logs<'e, 'c: 'e, E>(
|
||||
args: BulkInsertDebugLogsArgs<'e, 'c, E>,
|
||||
) -> Result<DebugLogEventToken, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let event_token = match args.apriori_debug_log_event_token {
|
||||
Some(token) => token.clone(),
|
||||
None => DebugLogEventToken::generate(),
|
||||
};
|
||||
|
||||
if args.events.is_empty() {
|
||||
warn!("bulk_insert_debug_logs called with no events; no-op.");
|
||||
return Ok(event_token);
|
||||
}
|
||||
|
||||
let maybe_user_token_str = args.maybe_creator_user_token.map(|t| t.as_str().to_string());
|
||||
|
||||
let mut query_builder = QueryBuilder::new(
|
||||
"INSERT INTO debug_logs (event_token, debug_log_type, maybe_creator_user_token, message) VALUES "
|
||||
);
|
||||
|
||||
for (i, event) in args.events.iter().enumerate() {
|
||||
query_builder.push("(");
|
||||
query_builder.push_bind(event_token.as_str());
|
||||
query_builder.push(", ");
|
||||
query_builder.push_bind(event.debug_log_type.to_str());
|
||||
query_builder.push(", ");
|
||||
query_builder.push_bind(&maybe_user_token_str);
|
||||
query_builder.push(", ");
|
||||
query_builder.push_bind(&event.message);
|
||||
query_builder.push(")");
|
||||
|
||||
if i < args.events.len() - 1 {
|
||||
query_builder.push(", ");
|
||||
}
|
||||
}
|
||||
|
||||
query_builder.build()
|
||||
.execute(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(event_token)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use enums::by_table::debug_logs::debug_log_type::DebugLogType;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct InsertDebugLogArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
/// If provided, the row will use this event token.
|
||||
/// If not provided, one will be generated.
|
||||
pub apriori_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
|
||||
/// The user associated with this debug log event (if any).
|
||||
pub maybe_creator_user_token: Option<&'e UserToken>,
|
||||
|
||||
/// The type of debug log event.
|
||||
pub debug_log_type: DebugLogType,
|
||||
|
||||
/// The log message body (pre-serialized JSON or any text).
|
||||
pub message: &'e str,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Insert a single debug log row.
|
||||
///
|
||||
/// Returns the event token used.
|
||||
pub async fn insert_debug_log<'e, 'c: 'e, E>(
|
||||
args: InsertDebugLogArgs<'e, 'c, E>,
|
||||
) -> Result<DebugLogEventToken, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let event_token = match args.apriori_debug_log_event_token {
|
||||
Some(token) => token.clone(),
|
||||
None => DebugLogEventToken::generate(),
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO debug_logs
|
||||
SET
|
||||
event_token = ?,
|
||||
debug_log_type = ?,
|
||||
maybe_creator_user_token = ?,
|
||||
message = ?
|
||||
"#,
|
||||
event_token.as_str(),
|
||||
args.debug_log_type.to_str(),
|
||||
args.maybe_creator_user_token.map(|t| t.as_str()),
|
||||
args.message,
|
||||
)
|
||||
.execute(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(event_token)
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use enums::by_table::debug_logs::debug_log_type::DebugLogType;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
const DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
pub struct ListDebugLogsForTokenArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub event_token: &'e DebugLogEventToken,
|
||||
pub limit: Option<u32>,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugLogRow {
|
||||
pub id: u64,
|
||||
pub event_token: DebugLogEventToken,
|
||||
pub debug_log_type: DebugLogType,
|
||||
pub maybe_creator_user_token: Option<UserToken>,
|
||||
pub message: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RawDebugLogRow {
|
||||
id: u64,
|
||||
event_token: DebugLogEventToken,
|
||||
debug_log_type: DebugLogType,
|
||||
maybe_creator_user_token: Option<UserToken>,
|
||||
message: String,
|
||||
created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
pub async fn list_debug_logs_for_token<'e, 'c: 'e, E>(
|
||||
args: ListDebugLogsForTokenArgs<'e, 'c, E>,
|
||||
) -> Result<Vec<DebugLogRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let limit = args.limit.unwrap_or(DEFAULT_LIMIT).min(200) as i64;
|
||||
|
||||
let rows = sqlx::query_as!(
|
||||
RawDebugLogRow,
|
||||
r#"
|
||||
SELECT
|
||||
id as `id: u64`,
|
||||
event_token as `event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,
|
||||
debug_log_type as `debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType`,
|
||||
maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
|
||||
message,
|
||||
created_at
|
||||
FROM debug_logs
|
||||
WHERE event_token = ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?
|
||||
"#,
|
||||
args.event_token.as_str(),
|
||||
limit,
|
||||
)
|
||||
.fetch_all(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
let results = rows.into_iter().map(|row| {
|
||||
DebugLogRow {
|
||||
id: row.id,
|
||||
event_token: row.event_token,
|
||||
debug_log_type: row.debug_log_type,
|
||||
maybe_creator_user_token: row.maybe_creator_user_token,
|
||||
message: row.message,
|
||||
created_at: row.created_at.and_utc(),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod bulk_insert_debug_logs;
|
||||
pub mod insert_debug_log;
|
||||
pub mod list_debug_logs_for_token;
|
||||
-5
@@ -9,7 +9,6 @@ use enums::common::job_status_plus::JobStatusPlus;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
@@ -30,7 +29,6 @@ pub struct FalJobDetails {
|
||||
pub creator_ip_address: String,
|
||||
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
pub maybe_debug_log_event_token: Option<DebugLogEventToken>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
@@ -53,7 +51,6 @@ struct RawJobRecord {
|
||||
creator_ip_address: String,
|
||||
|
||||
maybe_prompt_token: Option<PromptToken>,
|
||||
maybe_debug_log_event_token: Option<DebugLogEventToken>,
|
||||
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
@@ -91,7 +88,6 @@ SELECT
|
||||
jobs.creator_ip_address,
|
||||
|
||||
jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,
|
||||
jobs.maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,
|
||||
|
||||
jobs.created_at,
|
||||
jobs.updated_at
|
||||
@@ -133,7 +129,6 @@ fn raw_record_to_public_result(record: RawJobRecord) -> AnyhowResult<FalJobDetai
|
||||
maybe_creator_anonymous_visitor_token: record.maybe_creator_anonymous_visitor_token,
|
||||
creator_ip_address: record.creator_ip_address,
|
||||
maybe_prompt_token: record.maybe_prompt_token,
|
||||
maybe_debug_log_event_token: record.maybe_debug_log_event_token,
|
||||
created_at: record.created_at,
|
||||
updated_at: record.updated_at,
|
||||
})
|
||||
|
||||
-1
@@ -65,7 +65,6 @@ pub async fn insert_generic_inference_job_for_fal_queue<'e, 'c : 'e, E>(args: In
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
mysql_executor: args.mysql_executor,
|
||||
phantom: args.phantom,
|
||||
};
|
||||
|
||||
-1
@@ -68,7 +68,6 @@ pub async fn insert_generic_inference_job_for_fal_queue_mock_failure<'e, 'c: 'e,
|
||||
starting_job_status_override: Some(JobStatusPlus::CompleteFailure),
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
maybe_failure_reason: args.maybe_failure_reason,
|
||||
maybe_debug_log_event_token: None,
|
||||
mysql_executor: args.mysql_executor,
|
||||
phantom: args.phantom,
|
||||
};
|
||||
|
||||
-7
@@ -13,7 +13,6 @@ use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
@@ -48,8 +47,6 @@ pub struct InsertGenericInferenceForFalWithAprioriJobTokenArgs<'e, 'c, E>
|
||||
/// Set to `Some(JobStatusPlus::CompleteFailure)` for mock/test failure jobs.
|
||||
pub starting_job_status_override: Option<JobStatusPlus>,
|
||||
|
||||
pub maybe_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
|
||||
pub maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
pub maybe_failure_reason: Option<&'e str>,
|
||||
|
||||
@@ -142,8 +139,6 @@ SET
|
||||
is_debug_request = FALSE,
|
||||
maybe_routing_tag = NULL,
|
||||
|
||||
maybe_debug_log_event_token = ?,
|
||||
|
||||
frontend_failure_category = ?,
|
||||
failure_reason = ?,
|
||||
|
||||
@@ -169,8 +164,6 @@ SET
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
args.maybe_debug_log_event_token.map(|t| t.as_str()),
|
||||
|
||||
maybe_frontend_failure_category_str,
|
||||
maybe_truncated_failure_reason,
|
||||
|
||||
|
||||
+4
-47
@@ -1,6 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{MySql, MySqlPool};
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
@@ -15,52 +14,10 @@ pub struct MarkJobFailedByTokenArgs<'a> {
|
||||
pub maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
}
|
||||
|
||||
pub struct MarkJobFailedByTokenFromConnectionArgs<'a> {
|
||||
pub mysql_connection: &'a mut PoolConnection<MySql>,
|
||||
pub job_token: &'a InferenceJobToken,
|
||||
pub maybe_public_failure_reason: Option<&'a str>,
|
||||
pub internal_debugging_failure_reason: &'a str,
|
||||
pub maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
}
|
||||
|
||||
/// Permanently mark an inference job as failed, looked up by its token.
|
||||
/// Uses a pool to acquire a connection.
|
||||
/// Unlike `mark_generic_inference_job_failure`, this does not allow retries —
|
||||
/// the job will always land in `complete_failure`.
|
||||
pub async fn mark_job_failed_by_token(args: MarkJobFailedByTokenArgs<'_>) -> AnyhowResult<()> {
|
||||
execute_mark_failed(ExecuteMarkJobFailedByTokenArgs {
|
||||
executor: args.pool,
|
||||
job_token: args.job_token,
|
||||
maybe_public_failure_reason: args.maybe_public_failure_reason,
|
||||
internal_debugging_failure_reason: args.internal_debugging_failure_reason,
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
}).await
|
||||
}
|
||||
|
||||
/// Permanently mark an inference job as failed, looked up by its token.
|
||||
/// Uses an existing connection.
|
||||
pub async fn mark_job_failed_by_token_from_connection(args: MarkJobFailedByTokenFromConnectionArgs<'_>) -> AnyhowResult<()> {
|
||||
execute_mark_failed(ExecuteMarkJobFailedByTokenArgs {
|
||||
executor: &mut **args.mysql_connection,
|
||||
job_token: args.job_token,
|
||||
maybe_public_failure_reason: args.maybe_public_failure_reason,
|
||||
internal_debugging_failure_reason: args.internal_debugging_failure_reason,
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
}).await
|
||||
}
|
||||
|
||||
struct ExecuteMarkJobFailedByTokenArgs<'a, E> {
|
||||
executor: E,
|
||||
job_token: &'a InferenceJobToken,
|
||||
maybe_public_failure_reason: Option<&'a str>,
|
||||
internal_debugging_failure_reason: &'a str,
|
||||
maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
}
|
||||
|
||||
async fn execute_mark_failed<'e, E>(
|
||||
args: ExecuteMarkJobFailedByTokenArgs<'_, E>,
|
||||
) -> AnyhowResult<()>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = MySql>,
|
||||
{
|
||||
let maybe_public_failure_reason = args.maybe_public_failure_reason.map(|reason| {
|
||||
let mut reason = reason.trim().to_string();
|
||||
reason.truncate(512);
|
||||
@@ -89,7 +46,7 @@ WHERE token = ?
|
||||
args.maybe_frontend_failure_category,
|
||||
args.job_token.as_str()
|
||||
)
|
||||
.execute(args.executor)
|
||||
.execute(args.pool)
|
||||
.await;
|
||||
|
||||
match query_result {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod fal;
|
||||
pub mod job;
|
||||
pub mod moderation;
|
||||
pub mod seedance2pro;
|
||||
pub mod web;
|
||||
pub mod worldlabs;
|
||||
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct GetJobByTokenForModerationArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub job_token: &'e InferenceJobToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModerationJobDetails {
|
||||
pub token: InferenceJobToken,
|
||||
pub uuid_idempotency_token: String,
|
||||
pub maybe_job_type: Option<String>,
|
||||
pub maybe_product_category: Option<String>,
|
||||
pub inference_category: String,
|
||||
pub maybe_external_third_party: Option<String>,
|
||||
pub maybe_external_third_party_id: Option<String>,
|
||||
pub maybe_model_type: Option<String>,
|
||||
pub maybe_model_token: Option<String>,
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
pub maybe_inference_args: Option<String>,
|
||||
pub maybe_download_url: Option<String>,
|
||||
pub on_success_result_entity_type: Option<String>,
|
||||
pub on_success_result_entity_token: Option<String>,
|
||||
pub on_success_result_batch_token: Option<String>,
|
||||
pub maybe_creator_user_token: Option<UserToken>,
|
||||
pub maybe_creator_anonymous_visitor_token: Option<String>,
|
||||
pub creator_ip_address: String,
|
||||
pub creator_set_visibility: String,
|
||||
pub maybe_wallet_ledger_entry_token: Option<String>,
|
||||
pub maybe_debug_log_event_token: Option<DebugLogEventToken>,
|
||||
pub status: String,
|
||||
pub attempt_count: u16,
|
||||
pub failure_reason: Option<String>,
|
||||
pub internal_debugging_failure_reason: Option<String>,
|
||||
pub frontend_failure_category: Option<String>,
|
||||
pub maybe_routing_tag: Option<String>,
|
||||
pub assigned_worker: Option<String>,
|
||||
pub assigned_cluster: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RawRow {
|
||||
token: InferenceJobToken,
|
||||
uuid_idempotency_token: String,
|
||||
maybe_job_type: Option<String>,
|
||||
maybe_product_category: Option<String>,
|
||||
inference_category: String,
|
||||
maybe_external_third_party: Option<String>,
|
||||
maybe_external_third_party_id: Option<String>,
|
||||
maybe_model_type: Option<String>,
|
||||
maybe_model_token: Option<String>,
|
||||
maybe_prompt_token: Option<PromptToken>,
|
||||
maybe_inference_args: Option<String>,
|
||||
maybe_download_url: Option<String>,
|
||||
on_success_result_entity_type: Option<String>,
|
||||
on_success_result_entity_token: Option<String>,
|
||||
on_success_result_batch_token: Option<String>,
|
||||
maybe_creator_user_token: Option<UserToken>,
|
||||
maybe_creator_anonymous_visitor_token: Option<String>,
|
||||
creator_ip_address: String,
|
||||
creator_set_visibility: String,
|
||||
maybe_wallet_ledger_entry_token: Option<String>,
|
||||
maybe_debug_log_event_token: Option<DebugLogEventToken>,
|
||||
status: String,
|
||||
attempt_count: u16,
|
||||
failure_reason: Option<String>,
|
||||
internal_debugging_failure_reason: Option<String>,
|
||||
frontend_failure_category: Option<String>,
|
||||
maybe_routing_tag: Option<String>,
|
||||
assigned_worker: Option<String>,
|
||||
assigned_cluster: Option<String>,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub async fn get_job_by_token_for_moderation<'e, 'c: 'e, E>(
|
||||
args: GetJobByTokenForModerationArgs<'e, 'c, E>,
|
||||
) -> Result<Option<ModerationJobDetails>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let maybe_row = sqlx::query_as!(
|
||||
RawRow,
|
||||
r#"
|
||||
SELECT
|
||||
token as `token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,
|
||||
uuid_idempotency_token,
|
||||
job_type as `maybe_job_type`,
|
||||
product_category as `maybe_product_category`,
|
||||
inference_category,
|
||||
maybe_external_third_party,
|
||||
maybe_external_third_party_id,
|
||||
maybe_model_type,
|
||||
maybe_model_token,
|
||||
maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,
|
||||
maybe_inference_args,
|
||||
maybe_download_url,
|
||||
on_success_result_entity_type,
|
||||
on_success_result_entity_token,
|
||||
on_success_result_batch_token,
|
||||
maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
|
||||
maybe_creator_anonymous_visitor_token,
|
||||
creator_ip_address,
|
||||
creator_set_visibility,
|
||||
maybe_wallet_ledger_entry_token,
|
||||
maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,
|
||||
status,
|
||||
attempt_count as `attempt_count: u16`,
|
||||
failure_reason,
|
||||
internal_debugging_failure_reason,
|
||||
frontend_failure_category,
|
||||
maybe_routing_tag,
|
||||
assigned_worker,
|
||||
assigned_cluster,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM generic_inference_jobs
|
||||
WHERE token = ?
|
||||
"#,
|
||||
args.job_token.as_str(),
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(maybe_row.map(|row| ModerationJobDetails {
|
||||
token: row.token,
|
||||
uuid_idempotency_token: row.uuid_idempotency_token,
|
||||
maybe_job_type: row.maybe_job_type,
|
||||
maybe_product_category: row.maybe_product_category,
|
||||
inference_category: row.inference_category,
|
||||
maybe_external_third_party: row.maybe_external_third_party,
|
||||
maybe_external_third_party_id: row.maybe_external_third_party_id,
|
||||
maybe_model_type: row.maybe_model_type,
|
||||
maybe_model_token: row.maybe_model_token,
|
||||
maybe_prompt_token: row.maybe_prompt_token,
|
||||
maybe_inference_args: row.maybe_inference_args,
|
||||
maybe_download_url: row.maybe_download_url,
|
||||
on_success_result_entity_type: row.on_success_result_entity_type,
|
||||
on_success_result_entity_token: row.on_success_result_entity_token,
|
||||
on_success_result_batch_token: row.on_success_result_batch_token,
|
||||
maybe_creator_user_token: row.maybe_creator_user_token,
|
||||
maybe_creator_anonymous_visitor_token: row.maybe_creator_anonymous_visitor_token,
|
||||
creator_ip_address: row.creator_ip_address,
|
||||
creator_set_visibility: row.creator_set_visibility,
|
||||
maybe_wallet_ledger_entry_token: row.maybe_wallet_ledger_entry_token,
|
||||
maybe_debug_log_event_token: row.maybe_debug_log_event_token,
|
||||
status: row.status,
|
||||
attempt_count: row.attempt_count,
|
||||
failure_reason: row.failure_reason,
|
||||
internal_debugging_failure_reason: row.internal_debugging_failure_reason,
|
||||
frontend_failure_category: row.frontend_failure_category,
|
||||
maybe_routing_tag: row.maybe_routing_tag,
|
||||
assigned_worker: row.assigned_worker,
|
||||
assigned_cluster: row.assigned_cluster,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
}))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod get_job_by_token_for_moderation;
|
||||
-7
@@ -11,7 +11,6 @@ use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
|
||||
@@ -41,8 +40,6 @@ pub struct InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs<'e, 'c,
|
||||
pub creator_ip_address: &'e str,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub maybe_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
@@ -106,8 +103,6 @@ SET
|
||||
is_debug_request = FALSE,
|
||||
maybe_routing_tag = NULL,
|
||||
|
||||
maybe_debug_log_event_token = ?,
|
||||
|
||||
status = ?
|
||||
"#,
|
||||
args.apriori_job_token.as_str(),
|
||||
@@ -132,8 +127,6 @@ SET
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
args.maybe_debug_log_event_token.map(|t| t.as_str()),
|
||||
|
||||
STATUS.to_str(),
|
||||
);
|
||||
|
||||
|
||||
+1
-6
@@ -5,15 +5,14 @@ use sqlx::pool::PoolConnection;
|
||||
use sqlx::{FromRow, MySql, MySqlPool, QueryBuilder, Row};
|
||||
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::traits::mysql_from_row::MySqlFromRow as _;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::traits::mysql_from_row::MySqlFromRow as _;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::traits::mysql_token_from_row::MySqlTokenFromRow;
|
||||
|
||||
@@ -98,7 +97,6 @@ SELECT
|
||||
jobs.maybe_model_token,
|
||||
jobs.maybe_raw_inference_text,
|
||||
jobs.maybe_inference_args,
|
||||
jobs.maybe_prompt_token,
|
||||
|
||||
jobs.frontend_failure_category as maybe_frontend_failure_category,
|
||||
jobs.failure_reason,
|
||||
@@ -268,7 +266,6 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
maybe_model_title: maybe_model_title.map(|title| title.to_string()),
|
||||
maybe_raw_inference_text: record.maybe_raw_inference_text,
|
||||
maybe_inference_args: record.maybe_inference_args,
|
||||
maybe_prompt_token: record.maybe_prompt_token,
|
||||
maybe_style_name,
|
||||
},
|
||||
maybe_result_details,
|
||||
@@ -303,7 +300,6 @@ struct RawGenericInferenceJobStatus {
|
||||
pub maybe_model_token: Option<String>,
|
||||
pub maybe_raw_inference_text: Option<String>,
|
||||
pub maybe_inference_args: Option<String>,
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
|
||||
pub maybe_result_entity_type: Option<String>,
|
||||
pub maybe_result_entity_token: Option<String>,
|
||||
@@ -363,7 +359,6 @@ impl FromRow<'_, MySqlRow> for RawGenericInferenceJobStatus {
|
||||
maybe_model_token: row.try_get("maybe_model_token")?,
|
||||
maybe_raw_inference_text: row.try_get("maybe_raw_inference_text")?,
|
||||
maybe_inference_args: row.try_get("maybe_inference_args")?,
|
||||
maybe_prompt_token: PromptToken::try_from_mysql_row_nullable(row, "maybe_prompt_token")?,
|
||||
maybe_result_entity_type: row.try_get("maybe_result_entity_type")?,
|
||||
maybe_result_entity_token: row.try_get("maybe_result_entity_token")?,
|
||||
maybe_result_batch_token: row.try_get("maybe_result_batch_token")?,
|
||||
|
||||
-4
@@ -12,7 +12,6 @@ use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::helpers::boolean_converters::i8_to_bool;
|
||||
@@ -56,7 +55,6 @@ SELECT
|
||||
jobs.maybe_model_token,
|
||||
jobs.maybe_raw_inference_text,
|
||||
jobs.maybe_inference_args,
|
||||
jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,
|
||||
|
||||
jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,
|
||||
jobs.failure_reason,
|
||||
@@ -243,7 +241,6 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
|
||||
maybe_model_title: maybe_model_title.map(|title| title.to_string()),
|
||||
maybe_raw_inference_text: record.maybe_raw_inference_text,
|
||||
maybe_inference_args: record.maybe_inference_args,
|
||||
maybe_prompt_token: record.maybe_prompt_token,
|
||||
maybe_style_name,
|
||||
},
|
||||
maybe_result_details,
|
||||
@@ -276,7 +273,6 @@ struct RawGenericInferenceJobStatus {
|
||||
pub maybe_model_token: Option<String>,
|
||||
pub maybe_raw_inference_text: Option<String>,
|
||||
pub maybe_inference_args: Option<String>,
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
|
||||
pub maybe_result_entity_type: Option<String>,
|
||||
pub maybe_result_entity_token: Option<String>,
|
||||
|
||||
@@ -8,7 +8,6 @@ use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// Shared inference job status struct for several queries (list, get, batch get)
|
||||
@@ -58,9 +57,6 @@ pub struct RequestDetails {
|
||||
/// Raw JSON payload of polymorphic inference args
|
||||
pub maybe_inference_args: Option<String>,
|
||||
|
||||
/// Prompt token linking this job to its prompt record.
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
|
||||
/// For Comfy / Video Style Transfer jobs, this might include
|
||||
/// the name of the selected style.
|
||||
pub maybe_style_name: Option<StyleTransferName>,
|
||||
|
||||
-5
@@ -15,7 +15,6 @@ use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::traits::mysql_token_from_row::MySqlTokenFromRow;
|
||||
|
||||
@@ -164,7 +163,6 @@ SELECT
|
||||
jobs.maybe_model_token,
|
||||
jobs.maybe_raw_inference_text,
|
||||
jobs.maybe_inference_args,
|
||||
jobs.maybe_prompt_token,
|
||||
|
||||
jobs.frontend_failure_category as maybe_frontend_failure_category,
|
||||
jobs.failure_reason,
|
||||
@@ -334,7 +332,6 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
maybe_model_title: maybe_model_title.map(|title| title.to_string()),
|
||||
maybe_raw_inference_text: record.maybe_raw_inference_text,
|
||||
maybe_inference_args: record.maybe_inference_args,
|
||||
maybe_prompt_token: record.maybe_prompt_token,
|
||||
maybe_style_name,
|
||||
},
|
||||
maybe_result_details,
|
||||
@@ -369,7 +366,6 @@ struct RawGenericInferenceJobStatus {
|
||||
pub maybe_model_token: Option<String>,
|
||||
pub maybe_raw_inference_text: Option<String>,
|
||||
pub maybe_inference_args: Option<String>,
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
|
||||
pub maybe_result_entity_type: Option<String>,
|
||||
pub maybe_result_entity_token: Option<String>,
|
||||
@@ -429,7 +425,6 @@ impl FromRow<'_, MySqlRow> for RawGenericInferenceJobStatus {
|
||||
maybe_model_token: row.try_get("maybe_model_token")?,
|
||||
maybe_raw_inference_text: row.try_get("maybe_raw_inference_text")?,
|
||||
maybe_inference_args: row.try_get("maybe_inference_args")?,
|
||||
maybe_prompt_token: PromptToken::try_from_mysql_row_nullable(row, "maybe_prompt_token")?,
|
||||
maybe_result_entity_type: row.try_get("maybe_result_entity_type")?,
|
||||
maybe_result_entity_token: row.try_get("maybe_result_entity_token")?,
|
||||
maybe_result_batch_token: row.try_get("maybe_result_batch_token")?,
|
||||
|
||||
@@ -8,7 +8,6 @@ pub mod billing;
|
||||
pub mod browser_session_logs;
|
||||
pub mod characters;
|
||||
pub mod comments;
|
||||
pub mod debug_logs;
|
||||
pub mod database_time;
|
||||
pub mod email_sender_jobs;
|
||||
pub mod entity_stats;
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
use sqlx::mysql::MySqlRow;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{FromRow, MySql, QueryBuilder, Row};
|
||||
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::traits::mysql_from_row::MySqlFromRow;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::traits::mysql_token_from_row::MySqlTokenFromRow;
|
||||
|
||||
/// A prompt context item with its owning prompt token, for batch queries.
|
||||
pub struct BatchPromptContextItem {
|
||||
pub prompt_token: PromptToken,
|
||||
pub media_token: MediaFileToken,
|
||||
pub context_semantic_type: PromptContextSemanticType,
|
||||
pub public_bucket_directory_hash: String,
|
||||
pub maybe_public_bucket_prefix: Option<String>,
|
||||
pub maybe_public_bucket_extension: Option<String>,
|
||||
}
|
||||
|
||||
impl FromRow<'_, MySqlRow> for BatchPromptContextItem {
|
||||
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
|
||||
Ok(Self {
|
||||
prompt_token: PromptToken::try_from_mysql_row(row, "prompt_token")?,
|
||||
media_token: MediaFileToken::try_from_mysql_row(row, "media_token")?,
|
||||
context_semantic_type: PromptContextSemanticType::try_from_mysql_row(row, "context_semantic_type")?,
|
||||
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
|
||||
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
|
||||
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch fetch all context items for multiple prompts in a single query.
|
||||
/// Returns items grouped by their prompt_token (via the prompt_token field).
|
||||
pub async fn batch_list_prompt_context_items(
|
||||
prompt_tokens: &[PromptToken],
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> Result<Vec<BatchPromptContextItem>, sqlx::Error> {
|
||||
if prompt_tokens.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(r#"
|
||||
SELECT
|
||||
pci.prompt_token,
|
||||
pci.media_token,
|
||||
pci.context_semantic_type,
|
||||
|
||||
m.public_bucket_directory_hash,
|
||||
m.maybe_public_bucket_prefix,
|
||||
m.maybe_public_bucket_extension
|
||||
|
||||
FROM prompt_context_items pci
|
||||
JOIN media_files m ON pci.media_token = m.token
|
||||
WHERE pci.prompt_token IN (
|
||||
"#);
|
||||
|
||||
let mut separated = query_builder.separated(", ");
|
||||
for token in prompt_tokens {
|
||||
separated.push_bind(token.to_string());
|
||||
}
|
||||
separated.push_unseparated(") ");
|
||||
|
||||
let query = query_builder.build_query_as::<BatchPromptContextItem>();
|
||||
|
||||
let results = query.fetch_all(&mut **mysql_connection).await?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod batch_list_prompt_context_items;
|
||||
pub mod insert_batch_prompt_context_items;
|
||||
pub mod list_prompt_context_items;
|
||||
@@ -1,95 +0,0 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::mysql::MySqlRow;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{FromRow, MySql, QueryBuilder, Row};
|
||||
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::traits::mysql_from_row::MySqlFromRow;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::traits::mysql_token_from_row::MySqlTokenFromRow;
|
||||
|
||||
pub struct BatchPromptResult {
|
||||
pub token: PromptToken,
|
||||
pub prompt_type: PromptType,
|
||||
pub maybe_model_type: Option<CommonModelType>,
|
||||
pub maybe_generation_provider: Option<GenerationProvider>,
|
||||
pub maybe_positive_prompt: Option<String>,
|
||||
pub maybe_negative_prompt: Option<String>,
|
||||
pub maybe_generation_mode: Option<CommonGenerationMode>,
|
||||
pub maybe_aspect_ratio: Option<CommonAspectRatio>,
|
||||
pub maybe_resolution: Option<CommonResolution>,
|
||||
pub maybe_batch_count: Option<u8>,
|
||||
pub maybe_generate_audio: Option<bool>,
|
||||
pub maybe_duration_seconds: Option<u32>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl FromRow<'_, MySqlRow> for BatchPromptResult {
|
||||
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
|
||||
Ok(Self {
|
||||
token: PromptToken::try_from_mysql_row(row, "token")?,
|
||||
prompt_type: PromptType::try_from_mysql_row(row, "prompt_type")?,
|
||||
maybe_model_type: row.try_get::<Option<String>, _>("maybe_model_type")?
|
||||
.and_then(|s| CommonModelType::from_str(&s).ok()),
|
||||
maybe_generation_provider: row.try_get::<Option<String>, _>("maybe_generation_provider")?
|
||||
.and_then(|s| GenerationProvider::from_str(&s).ok()),
|
||||
maybe_positive_prompt: row.try_get("maybe_positive_prompt")?,
|
||||
maybe_negative_prompt: row.try_get("maybe_negative_prompt")?,
|
||||
maybe_generation_mode: row.try_get::<Option<String>, _>("maybe_generation_mode")?
|
||||
.and_then(|s| CommonGenerationMode::from_str(&s).ok()),
|
||||
maybe_aspect_ratio: row.try_get::<Option<String>, _>("maybe_aspect_ratio")?
|
||||
.and_then(|s| CommonAspectRatio::from_str(&s).ok()),
|
||||
maybe_resolution: row.try_get::<Option<String>, _>("maybe_resolution")?
|
||||
.and_then(|s| CommonResolution::from_str(&s).ok()),
|
||||
maybe_batch_count: row.try_get("maybe_batch_count")?,
|
||||
maybe_generate_audio: row.try_get("maybe_generate_audio")?,
|
||||
maybe_duration_seconds: row.try_get("maybe_duration_seconds")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn batch_get_prompts(
|
||||
tokens: &[PromptToken],
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> Result<Vec<BatchPromptResult>, sqlx::Error> {
|
||||
if tokens.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(r#"
|
||||
SELECT
|
||||
p.token,
|
||||
p.prompt_type,
|
||||
p.maybe_model_type,
|
||||
p.maybe_generation_provider,
|
||||
p.maybe_positive_prompt,
|
||||
p.maybe_negative_prompt,
|
||||
p.maybe_generation_mode,
|
||||
p.maybe_aspect_ratio,
|
||||
p.maybe_resolution,
|
||||
p.maybe_batch_count,
|
||||
p.maybe_generate_audio,
|
||||
p.maybe_duration_seconds,
|
||||
p.created_at
|
||||
FROM prompts as p
|
||||
WHERE p.token IN (
|
||||
"#);
|
||||
|
||||
let mut separated = query_builder.separated(", ");
|
||||
|
||||
for token in tokens {
|
||||
separated.push_bind(token.to_string());
|
||||
}
|
||||
|
||||
separated.push_unseparated(") ");
|
||||
|
||||
let query = query_builder.build_query_as::<BatchPromptResult>();
|
||||
|
||||
query.fetch_all(&mut **mysql_connection).await
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod batch_get_prompts;
|
||||
pub mod get_prompt;
|
||||
pub mod insert_prompt;
|
||||
@@ -1,160 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::error::enum_error::EnumError;
|
||||
|
||||
/// Maximum serialized string length for database storage.
|
||||
pub const MAX_LENGTH: usize = 24;
|
||||
|
||||
/// Used in the `debug_logs` table in a `VARCHAR(24)` field.
|
||||
///
|
||||
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DebugLogType {
|
||||
HttpRequest,
|
||||
FalRequest,
|
||||
KinoviRequest,
|
||||
FalWebhook,
|
||||
}
|
||||
|
||||
impl_enum_display_and_debug_using_to_str!(DebugLogType);
|
||||
impl_mysql_enum_coders!(DebugLogType);
|
||||
impl_mysql_from_row!(DebugLogType);
|
||||
|
||||
impl DebugLogType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::HttpRequest => "http_request",
|
||||
Self::FalRequest => "fal_request",
|
||||
Self::KinoviRequest => "kinovi_request",
|
||||
Self::FalWebhook => "fal_webhook",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, EnumError> {
|
||||
match value {
|
||||
"http_request" => Ok(Self::HttpRequest),
|
||||
"fal_request" => Ok(Self::FalRequest),
|
||||
"kinovi_request" => Ok(Self::KinoviRequest),
|
||||
"fal_webhook" => Ok(Self::FalWebhook),
|
||||
_ => Err(EnumError::CouldNotConvertFromString(value.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants() -> BTreeSet<Self> {
|
||||
BTreeSet::from([
|
||||
Self::HttpRequest,
|
||||
Self::FalRequest,
|
||||
Self::KinoviRequest,
|
||||
Self::FalWebhook,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::by_table::debug_logs::debug_log_type::DebugLogType;
|
||||
use crate::by_table::debug_logs::debug_log_type::MAX_LENGTH;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
mod explicit_checks {
|
||||
use super::*;
|
||||
use crate::error::enum_error::EnumError;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(DebugLogType::HttpRequest, "http_request");
|
||||
assert_serialization(DebugLogType::FalRequest, "fal_request");
|
||||
assert_serialization(DebugLogType::KinoviRequest, "kinovi_request");
|
||||
assert_serialization(DebugLogType::FalWebhook, "fal_webhook");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(DebugLogType::HttpRequest.to_str(), "http_request");
|
||||
assert_eq!(DebugLogType::FalRequest.to_str(), "fal_request");
|
||||
assert_eq!(DebugLogType::KinoviRequest.to_str(), "kinovi_request");
|
||||
assert_eq!(DebugLogType::FalWebhook.to_str(), "fal_webhook");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(DebugLogType::from_str("http_request").unwrap(), DebugLogType::HttpRequest);
|
||||
assert_eq!(DebugLogType::from_str("fal_request").unwrap(), DebugLogType::FalRequest);
|
||||
assert_eq!(DebugLogType::from_str("kinovi_request").unwrap(), DebugLogType::KinoviRequest);
|
||||
assert_eq!(DebugLogType::from_str("fal_webhook").unwrap(), DebugLogType::FalWebhook);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_err() {
|
||||
let result = DebugLogType::from_str("invalid");
|
||||
assert!(result.is_err());
|
||||
if let Err(EnumError::CouldNotConvertFromString(value)) = result {
|
||||
assert_eq!(value, "invalid");
|
||||
} else {
|
||||
panic!("Expected EnumError::CouldNotConvertFromString");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = DebugLogType::all_variants();
|
||||
assert_eq!(variants.len(), 4);
|
||||
assert_eq!(variants.pop_first(), Some(DebugLogType::HttpRequest));
|
||||
assert_eq!(variants.pop_first(), Some(DebugLogType::FalRequest));
|
||||
assert_eq!(variants.pop_first(), Some(DebugLogType::KinoviRequest));
|
||||
assert_eq!(variants.pop_first(), Some(DebugLogType::FalWebhook));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
mod mechanical_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn variant_length() {
|
||||
use strum::IntoEnumIterator;
|
||||
assert_eq!(DebugLogType::all_variants().len(), DebugLogType::iter().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
for variant in DebugLogType::all_variants() {
|
||||
assert_eq!(variant, DebugLogType::from_str(variant.to_str()).unwrap());
|
||||
assert_eq!(variant, DebugLogType::from_str(&format!("{}", variant)).unwrap());
|
||||
assert_eq!(variant, DebugLogType::from_str(&format!("{:?}", variant)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_length_ok_for_database() {
|
||||
for variant in DebugLogType::all_variants() {
|
||||
let serialized = variant.to_str();
|
||||
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
|
||||
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long via to_str()", variant);
|
||||
}
|
||||
for variant in DebugLogType::all_variants() {
|
||||
let json = serde_json::to_string(&variant).unwrap().replace('"', "");
|
||||
assert!(json.len() <= MAX_LENGTH, "variant {:?} is too long via JSON: {:?}", variant, json);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_names_must_only_contain_lowercase_alphanumeric_and_underscore() {
|
||||
let valid_pattern = regex::Regex::new(r"^[a-z0-9_]+$").unwrap();
|
||||
|
||||
for variant in DebugLogType::all_variants() {
|
||||
let to_str_value = variant.to_str();
|
||||
assert!(valid_pattern.is_match(to_str_value),
|
||||
"to_str() for {:?} contains invalid characters: {:?} (only a-z, 0-9, _ allowed)", variant, to_str_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod debug_log_type;
|
||||
@@ -4,7 +4,6 @@ pub mod batch_generations;
|
||||
pub mod characters;
|
||||
pub mod beta_keys;
|
||||
pub mod comments;
|
||||
pub mod debug_logs;
|
||||
pub mod email_sender_jobs;
|
||||
pub mod entity_stats;
|
||||
pub mod featured_items;
|
||||
|
||||
@@ -77,16 +77,12 @@ pub enum TaskModelType {
|
||||
Kling3p0Standard,
|
||||
#[serde(rename = "kling_3p0_pro")]
|
||||
Kling3p0Pro,
|
||||
#[serde(rename = "happy_horse_1p0")]
|
||||
HappyHorse1p0,
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
#[serde(rename = "seedance_1p5_pro")]
|
||||
Seedance1p5Pro,
|
||||
#[serde(rename = "seedance_2p0")]
|
||||
Seedance2p0,
|
||||
#[serde(rename = "seedance_2p0_fast")]
|
||||
Seedance2p0Fast,
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
#[serde(rename = "sora_2_pro")]
|
||||
@@ -158,11 +154,9 @@ impl TaskModelType {
|
||||
Self::Kling2p6Pro => "kling_2p6_pro",
|
||||
Self::Kling3p0Standard => "kling_3p0_standard",
|
||||
Self::Kling3p0Pro => "kling_3p0_pro",
|
||||
Self::HappyHorse1p0 => "happy_horse_1p0",
|
||||
Self::Seedance10Lite => "seedance_1.0_lite",
|
||||
Self::Seedance1p5Pro => "seedance_1p5_pro",
|
||||
Self::Seedance2p0 => "seedance_2p0",
|
||||
Self::Seedance2p0Fast => "seedance_2p0_fast",
|
||||
Self::Sora2 => "sora_2",
|
||||
Self::Sora2Pro => "sora_2_pro",
|
||||
Self::Veo2 => "veo_2",
|
||||
@@ -214,11 +208,9 @@ impl TaskModelType {
|
||||
"kling_2p6_pro" => Ok(Self::Kling2p6Pro),
|
||||
"kling_3p0_standard" => Ok(Self::Kling3p0Standard),
|
||||
"kling_3p0_pro" => Ok(Self::Kling3p0Pro),
|
||||
"happy_horse_1p0" => Ok(Self::HappyHorse1p0),
|
||||
"seedance_1.0_lite" => Ok(Self::Seedance10Lite),
|
||||
"seedance_1p5_pro" => Ok(Self::Seedance1p5Pro),
|
||||
"seedance_2p0" => Ok(Self::Seedance2p0),
|
||||
"seedance_2p0_fast" => Ok(Self::Seedance2p0Fast),
|
||||
"sora_2" => Ok(Self::Sora2),
|
||||
"sora_2_pro" => Ok(Self::Sora2Pro),
|
||||
"veo_2" => Ok(Self::Veo2),
|
||||
@@ -273,11 +265,9 @@ impl TaskModelType {
|
||||
Self::Kling2p6Pro,
|
||||
Self::Kling3p0Standard,
|
||||
Self::Kling3p0Pro,
|
||||
Self::HappyHorse1p0,
|
||||
Self::Seedance10Lite,
|
||||
Self::Seedance1p5Pro,
|
||||
Self::Seedance2p0,
|
||||
Self::Seedance2p0Fast,
|
||||
Self::Sora2,
|
||||
Self::Sora2Pro,
|
||||
Self::Veo2,
|
||||
@@ -339,11 +329,9 @@ mod tests {
|
||||
assert_serialization(TaskModelType::Kling2p6Pro, "kling_2p6_pro");
|
||||
assert_serialization(TaskModelType::Kling3p0Standard, "kling_3p0_standard");
|
||||
assert_serialization(TaskModelType::Kling3p0Pro, "kling_3p0_pro");
|
||||
assert_serialization(TaskModelType::HappyHorse1p0, "happy_horse_1p0");
|
||||
assert_serialization(TaskModelType::Seedance10Lite, "seedance_1.0_lite");
|
||||
assert_serialization(TaskModelType::Seedance1p5Pro, "seedance_1p5_pro");
|
||||
assert_serialization(TaskModelType::Seedance2p0, "seedance_2p0");
|
||||
assert_serialization(TaskModelType::Seedance2p0Fast, "seedance_2p0_fast");
|
||||
assert_serialization(TaskModelType::Sora2, "sora_2");
|
||||
assert_serialization(TaskModelType::Sora2Pro, "sora_2_pro");
|
||||
assert_serialization(TaskModelType::Veo2, "veo_2");
|
||||
@@ -394,11 +382,9 @@ mod tests {
|
||||
assert_eq!(TaskModelType::Kling2p6Pro.to_str(), "kling_2p6_pro");
|
||||
assert_eq!(TaskModelType::Kling3p0Standard.to_str(), "kling_3p0_standard");
|
||||
assert_eq!(TaskModelType::Kling3p0Pro.to_str(), "kling_3p0_pro");
|
||||
assert_eq!(TaskModelType::HappyHorse1p0.to_str(), "happy_horse_1p0");
|
||||
assert_eq!(TaskModelType::Seedance10Lite.to_str(), "seedance_1.0_lite");
|
||||
assert_eq!(TaskModelType::Seedance1p5Pro.to_str(), "seedance_1p5_pro");
|
||||
assert_eq!(TaskModelType::Seedance2p0.to_str(), "seedance_2p0");
|
||||
assert_eq!(TaskModelType::Seedance2p0Fast.to_str(), "seedance_2p0_fast");
|
||||
assert_eq!(TaskModelType::Sora2.to_str(), "sora_2");
|
||||
assert_eq!(TaskModelType::Sora2Pro.to_str(), "sora_2_pro");
|
||||
assert_eq!(TaskModelType::Veo2.to_str(), "veo_2");
|
||||
@@ -449,11 +435,9 @@ mod tests {
|
||||
assert_eq!(TaskModelType::from_str("kling_2p6_pro").unwrap(), TaskModelType::Kling2p6Pro);
|
||||
assert_eq!(TaskModelType::from_str("kling_3p0_standard").unwrap(), TaskModelType::Kling3p0Standard);
|
||||
assert_eq!(TaskModelType::from_str("kling_3p0_pro").unwrap(), TaskModelType::Kling3p0Pro);
|
||||
assert_eq!(TaskModelType::from_str("happy_horse_1p0").unwrap(), TaskModelType::HappyHorse1p0);
|
||||
assert_eq!(TaskModelType::from_str("seedance_1.0_lite").unwrap(), TaskModelType::Seedance10Lite);
|
||||
assert_eq!(TaskModelType::from_str("seedance_1p5_pro").unwrap(), TaskModelType::Seedance1p5Pro);
|
||||
assert_eq!(TaskModelType::from_str("seedance_2p0").unwrap(), TaskModelType::Seedance2p0);
|
||||
assert_eq!(TaskModelType::from_str("seedance_2p0_fast").unwrap(), TaskModelType::Seedance2p0Fast);
|
||||
assert_eq!(TaskModelType::from_str("sora_2").unwrap(), TaskModelType::Sora2);
|
||||
assert_eq!(TaskModelType::from_str("sora_2_pro").unwrap(), TaskModelType::Sora2Pro);
|
||||
assert_eq!(TaskModelType::from_str("veo_2").unwrap(), TaskModelType::Veo2);
|
||||
@@ -484,7 +468,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TaskModelType::all_variants();
|
||||
assert_eq!(variants.len(), 48);
|
||||
assert_eq!(variants.len(), 46);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Schnell));
|
||||
@@ -517,11 +501,9 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling2p6Pro));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling3p0Standard));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling3p0Pro));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::HappyHorse1p0));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance10Lite));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance1p5Pro));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance2p0));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance2p0Fast));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Sora2));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Sora2Pro));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Veo2));
|
||||
|
||||
@@ -30,7 +30,6 @@ pub(crate) enum TokenPrefix {
|
||||
BrowserSessionLog,
|
||||
Character,
|
||||
Comment,
|
||||
DebugLogEvent,
|
||||
DownloadJob,
|
||||
EmailSenderJob,
|
||||
GoogleSignInAccount,
|
||||
@@ -117,7 +116,6 @@ impl PrefixGenerator for TokenPrefix {
|
||||
Self::BrowserSessionLog => "bsl_",
|
||||
Self::Character => "character_",
|
||||
Self::Comment => "comment_",
|
||||
Self::DebugLogEvent => "dbg_",
|
||||
Self::DownloadJob => "jdown_", // NB: Previously "JGUP:"
|
||||
Self::EmailSenderJob => "email_job_",
|
||||
Self::GoogleSignInAccount => "gsi_",
|
||||
|
||||
@@ -47,6 +47,3 @@ pub mod wallets;
|
||||
pub mod zs_voice_dataset_samples;
|
||||
pub mod zs_voice_datasets;
|
||||
pub mod zs_voices;
|
||||
|
||||
// Non-unique tokens (used as indices, not primary keys)
|
||||
pub mod non_unique;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// A non-unique event token for the `debug_logs` table.
|
||||
/// Multiple log rows can share the same event token.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[cfg_attr(feature = "database", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "database", sqlx(transparent))]
|
||||
pub struct DebugLogEventToken(pub String);
|
||||
|
||||
impl_string_token!(DebugLogEventToken);
|
||||
impl_crockford_generator!(DebugLogEventToken, 32usize, TokenPrefix::DebugLogEvent, CrockfordLower);
|
||||
@@ -1 +0,0 @@
|
||||
pub mod debug_logs_event_token;
|
||||
@@ -94,7 +94,6 @@ fn build_omni_gen_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
character_references_supported: Some(true),
|
||||
character_references_max: Some(9),
|
||||
aspect_ratio_options: Some(vec![
|
||||
CommonAspectRatio::WideTwentyOneByNine,
|
||||
CommonAspectRatio::WideSixteenByNine,
|
||||
CommonAspectRatio::WideFourByThree,
|
||||
CommonAspectRatio::Square,
|
||||
@@ -134,7 +133,6 @@ fn build_omni_gen_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
character_references_supported: Some(true),
|
||||
character_references_max: Some(9),
|
||||
aspect_ratio_options: Some(vec![
|
||||
CommonAspectRatio::WideTwentyOneByNine,
|
||||
CommonAspectRatio::WideSixteenByNine,
|
||||
CommonAspectRatio::WideFourByThree,
|
||||
CommonAspectRatio::Square,
|
||||
|
||||
@@ -85,8 +85,6 @@ use crate::http_server::endpoints::media_files::upsert_upload::write_engine_asse
|
||||
use crate::http_server::endpoints::media_files::upsert_upload::write_error::MediaFileWriteError;
|
||||
use crate::http_server::endpoints::media_files::upsert_upload::write_scene_file::write_scene_file_media_file_handler::*;
|
||||
use crate::http_server::endpoints::model_download::enqueue_gptsovits_model_download_handler::*;
|
||||
use crate::http_server::endpoints::moderation::debug_logs::moderation_list_debug_logs_for_token_handler::*;
|
||||
use crate::http_server::endpoints::moderation::jobs::moderation_get_job_by_token_handler::*;
|
||||
use crate::http_server::endpoints::moderation::user_feature_flags::moderator_edit_user_feature_flags_handler::*;
|
||||
use crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_all_available_user_feature_flags_handler::*;
|
||||
use crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_user_feature_flags_handler::*;
|
||||
@@ -157,7 +155,6 @@ use artcraft_api_defs::media_file::delete_media_file::DeleteMediaFilePathInfo;
|
||||
use artcraft_api_defs::media_file::delete_media_file::DeleteMediaFileRequest;
|
||||
use artcraft_api_defs::prompts::create_prompt::CreatePromptRequest;
|
||||
use artcraft_api_defs::prompts::create_prompt::CreatePromptResponse;
|
||||
use artcraft_api_defs::prompts::batch_get_prompts::*;
|
||||
use artcraft_api_defs::prompts::get_prompt::*;
|
||||
use artcraft_api_defs::users::change_password::{ChangePasswordRequest, ChangePasswordResponse};
|
||||
use artcraft_api_defs::users::edit_email::{EditEmailRequest, EditEmailResponse};
|
||||
@@ -223,7 +220,6 @@ use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEnti
|
||||
use enums::by_table::user_ratings::entity_type::UserRatingEntityType;
|
||||
use enums::by_table::user_ratings::rating_value::UserRatingValue;
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use enums::common::generation::common_model_class::CommonModelClass;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
@@ -406,7 +402,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
crate::http_server::endpoints::moderation::user_feature_flags::moderator_edit_user_feature_flags_handler::moderator_edit_user_feature_flags_handler,
|
||||
crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_all_available_user_feature_flags_handler::moderator_list_all_available_user_feature_flags_handler,
|
||||
crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_user_feature_flags_handler::moderator_list_user_feature_flags_handler,
|
||||
crate::http_server::endpoints::prompts::batch_get_prompts_handler::batch_get_prompts_handler,
|
||||
crate::http_server::endpoints::prompts::create_prompt_handler::create_prompt_handler,
|
||||
crate::http_server::endpoints::prompts::get_prompt_handler::get_prompt_handler,
|
||||
crate::http_server::endpoints::service::status_alert_handler::status_alert_handler,
|
||||
@@ -521,8 +516,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
crate::http_server::endpoints::moderation::wallets::moderator_add_banked_balance_to_wallet_handler::moderator_add_banked_balance_to_wallet_handler,
|
||||
crate::http_server::endpoints::moderation::wallets::moderator_create_wallet_for_user_handler::moderator_create_wallet_for_user_handler,
|
||||
crate::http_server::endpoints::moderation::wallets::moderator_get_wallet_handler::moderator_get_wallet_handler,
|
||||
crate::http_server::endpoints::moderation::debug_logs::moderation_list_debug_logs_for_token_handler::moderation_list_debug_logs_for_token_handler,
|
||||
crate::http_server::endpoints::moderation::jobs::moderation_get_job_by_token_handler::moderation_get_job_by_token_handler,
|
||||
crate::http_server::endpoints::moderation::staff_audit_logs::moderator_list_staff_audit_logs_handler::moderator_list_staff_audit_logs_handler,
|
||||
crate::http_server::endpoints::moderation::user_bans::moderation_ban_user_handler::moderation_ban_user_handler,
|
||||
crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_for_user_handler::moderator_list_user_session_impersonation_requests_for_user_handler,
|
||||
@@ -566,7 +559,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
MediaFileOriginProductCategory,
|
||||
MediaFileSubtype,
|
||||
MediaFileType,
|
||||
CommonModelClass,
|
||||
CommonModelType,
|
||||
CommonAspectRatio,
|
||||
CommonGenerationMode,
|
||||
@@ -629,9 +621,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
BatchGetInferenceJobStatusQueryParams,
|
||||
BatchGetInferenceJobStatusSuccessResponse,
|
||||
BatchGetMediaFilesModelInfo,
|
||||
BatchGetPromptsQuery,
|
||||
BatchGetPromptsResponse,
|
||||
BatchPromptInfo,
|
||||
CreatePromptRequest,
|
||||
CreatePromptResponse,
|
||||
BatchGetMediaFilesQueryParams,
|
||||
@@ -873,6 +862,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
GetMediaFilePathInfo,
|
||||
GetMediaFileSuccessResponse,
|
||||
GetProfilePathInfo,
|
||||
GetPromptError,
|
||||
GetPromptPathInfo,
|
||||
GetPromptSuccessResponse,
|
||||
GetUnifiedQueueStatsError,
|
||||
@@ -913,13 +903,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
ListAllImpersonationRequestsQueryParams,
|
||||
ListAllImpersonationRequestsSuccessResponse,
|
||||
ListImpersonationRequestsPathInfo,
|
||||
GetJobByTokenPathInfo,
|
||||
GetJobByTokenSuccessResponse,
|
||||
ModerationJobResponse,
|
||||
ListDebugLogsPathInfo,
|
||||
ListDebugLogsQueryParams,
|
||||
ListDebugLogsSuccessResponse,
|
||||
DebugLogEntry,
|
||||
ListStaffAuditLogsQueryParams,
|
||||
ListStaffAuditLogsSuccessResponse,
|
||||
ListImpersonationRequestsQueryParams,
|
||||
|
||||
-1
@@ -238,7 +238,6 @@ pub async fn flux_2_lora_edit_image_angle_handler(
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-1
@@ -239,7 +239,6 @@ pub async fn qwen_edit_2511_edit_image_angle_handler(
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-1
@@ -265,7 +265,6 @@ pub async fn gemini_25_flash_edit_image_handler(
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-1
@@ -297,7 +297,6 @@ pub async fn gpt_image_1_edit_image_handler(
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user