mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 09:39:31 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c3138d70f | ||
|
|
bf188682ba |
+12
@@ -0,0 +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 status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 14
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "002cdb3d086f3b4181e2997befa54bd8516cb1d3d3bb6e389862695318d5c299"
|
||||
}
|
||||
-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_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": 16
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "29916e73a1784ea0b70ef4f637520b05efccc31e01840f681e9b042f82eb8f7e"
|
||||
}
|
||||
+5
-17
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload,\n on_complete_primary_media_file_token,\n on_complete_primary_media_file_class,\n on_complete_batch_token,\n on_complete_primary_media_file_cdn_url,\n on_complete_primary_media_file_thumbnail_url_template,\n on_failure_type,\n on_failure_message,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n completed_at as \"completed_at: DateTime<Utc>\"\n FROM tasks\n WHERE is_dismissed_by_user == 0\n ",
|
||||
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload,\n on_complete_primary_media_file_token,\n on_complete_primary_media_file_class,\n on_complete_batch_token,\n on_complete_primary_media_file_cdn_url,\n on_complete_primary_media_file_thumbnail_url_template,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n completed_at as \"completed_at: DateTime<Utc>\"\n FROM tasks\n WHERE is_dismissed_by_user == 0\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -73,29 +73,19 @@
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_failure_type",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_failure_message",
|
||||
"ordinal": 15,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 16,
|
||||
"ordinal": 14,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "updated_at: DateTime<Utc>",
|
||||
"ordinal": 17,
|
||||
"ordinal": 15,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "completed_at: DateTime<Utc>",
|
||||
"ordinal": 18,
|
||||
"ordinal": 16,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
@@ -117,12 +107,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "2436eb7bf4b0e23a899311a67a77f71d89eb35461e2113e43cd9d221bfa91c9a"
|
||||
"hash": "47f94c127abc20683b25c257aed3c2190b9bd29eb829d61392f08e10fd986767"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO wallets\nSET\n token = ?,\n wallet_namespace = ?,\n\n owner_user_token = ?,\n\n banked_credits = 0,\n monthly_credits = 0\n ",
|
||||
"query": "\nINSERT INTO wallets\nSET\n token = ?,\n wallet_namespace = ?,\n\n owner_user_token = ?,\n \n banked_credits = 0,\n monthly_credits = 0\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f4fb945b4311bd4458a68d82e417b40f8c36c08d572c07881b89cd157a8a8699"
|
||||
"hash": "4f5100a2cfb465964e77eaacb1ae394daae94de20c56a0966a8222dc9bea63c6"
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n username_is_not_customized as `username_is_not_customized: bool`\nFROM users\nWHERE\n token = ?\n AND user_deleted_at IS NULL\n AND mod_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "username_is_generated: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "username_is_not_customized: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "be9867966f24f44e14f96966cf5113055f00c339dda75334e7e88d355b9bfe72"
|
||||
}
|
||||
@@ -16,9 +16,7 @@
|
||||
-- on_complete_primary_media_file_class,
|
||||
-- on_complete_primary_media_file_cdn_url,
|
||||
-- on_complete_primary_media_file_thumbnail_url_template,
|
||||
-- tasks_v6.sqlite - added two fields:
|
||||
-- on_failure_type
|
||||
-- on_failure_message
|
||||
--
|
||||
|
||||
CREATE TABLE tasks (
|
||||
-- Task auto-incrementing primary key.
|
||||
@@ -86,15 +84,6 @@ CREATE TABLE tasks (
|
||||
-- The URL template for the thumbnail of the primary media file.
|
||||
on_complete_primary_media_file_thumbnail_url_template TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- An enum-like string describing the failure reason (even for non-terminal failures).
|
||||
-- The enum is `TaskFailureType`, which is a close analogue of the backend API's `FrontendFailureCategory`.
|
||||
on_failure_type TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- A human-readable message describing the failure reason (even for non-terminal failures).
|
||||
on_failure_message TEXT,
|
||||
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
completed_at INTEGER DEFAULT NULL
|
||||
|
||||
@@ -9,8 +9,6 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
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";
|
||||
@@ -100,16 +98,9 @@ pub struct ListSessionStatusDetailsResponse {
|
||||
/// This is typically only for non-premium users.
|
||||
pub requires_keepalive: bool,
|
||||
|
||||
/// NB: DO NOT CHANGE: Old clients can't read the new enum payload values as they are introduced.
|
||||
///
|
||||
/// An enum the frontend can use to display localized/I18N error
|
||||
/// messages. These pertain to both transient and permanent failures.
|
||||
#[deprecated(note="use maybe_failure_category_updated")]
|
||||
pub maybe_failure_category: Option<FrontendFailureCategoryForOldClients>,
|
||||
|
||||
/// An enum the frontend can use to display localized/I18N error
|
||||
/// messages. These pertain to both transient and permanent failures.
|
||||
pub maybe_failure_category_updated: Option<FrontendFailureCategoryForApiClients>,
|
||||
pub maybe_failure_category: Option<FrontendFailureCategory>,
|
||||
|
||||
/// This is an integer number between 0 and 100 (both inclusive) that
|
||||
/// reports the completeness.
|
||||
@@ -134,3 +125,4 @@ pub struct ListSessionResultDetailsResponse {
|
||||
|
||||
pub maybe_successfully_completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod list_user_wallets;
|
||||
pub mod moderator_add_banked_balance_to_wallet;
|
||||
pub mod moderator_create_wallet_for_user;
|
||||
pub mod moderator_get_wallet;
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallets::WalletToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const MODERATOR_CREATE_WALLET_FOR_USER_PATH: &str = "/v1/moderation/wallet/create_for_user";
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct ModeratorCreateWalletForUserRequest {
|
||||
pub user_token: Option<UserToken>,
|
||||
pub payments_namespace: Option<PaymentsNamespace>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ModeratorCreateWalletForUserResponse {
|
||||
pub success: bool,
|
||||
pub wallet_token: WalletToken,
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use super::failure_type::FailureType;
|
||||
|
||||
/// A structured failure reason parsed from the raw Seedance2 Pro API response.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FailureReason {
|
||||
/// The original reason string from the API.
|
||||
pub reason: String,
|
||||
|
||||
/// The categorized failure type.
|
||||
pub failure_type: FailureType,
|
||||
}
|
||||
|
||||
impl FailureReason {
|
||||
/// Parse a raw failure reason string into a structured `FailureReason`.
|
||||
///
|
||||
/// First checks for exact string matches against known reasons,
|
||||
/// then falls back to case-insensitive substring matching.
|
||||
pub fn from_reason(reason: &str) -> Self {
|
||||
let failure_type = FailureType::classify_text(reason);
|
||||
FailureReason {
|
||||
reason: reason.to_string(),
|
||||
failure_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
|
||||
/// Categorized failure type for a Seedance2 Pro order.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FailureType {
|
||||
/// User's uploaded image was rejected by platform content rules.
|
||||
RuleBansUserImage,
|
||||
/// User's uploaded image was rejected because it contains faces.
|
||||
RuleBansUserImageWithFaces,
|
||||
/// User's text prompt was rejected by platform content rules.
|
||||
RuleBansUserTextPrompt,
|
||||
/// User's content (image or text) was rejected by platform content rules (generic).
|
||||
RuleBansUserContent,
|
||||
/// The generated video was rejected by platform content review.
|
||||
RuleBansGeneratedVideo,
|
||||
/// The generated audio was rejected by platform content rules.
|
||||
RuleBansGeneratedAudio,
|
||||
/// The generated content (video/audio/other) was rejected by platform content rules (generic).
|
||||
RuleBansGeneratedContent,
|
||||
/// Video generation failed (timeout, server error, processing error, etc.)
|
||||
GenerationFailed,
|
||||
/// An unrecognized or absent failure reason.
|
||||
OtherUnknownReason,
|
||||
}
|
||||
|
||||
impl FailureType {
|
||||
pub fn classify_text(reason: &str) -> Self {
|
||||
// --- Exact matches first ---
|
||||
match reason {
|
||||
"Your uploaded image violates platform rules. Please modify and try again." =>
|
||||
return Self::RuleBansUserImage,
|
||||
"Face detected in uploaded media. Please adjust your media and try again." =>
|
||||
return Self::RuleBansUserImageWithFaces,
|
||||
"Your input text violates platform rules. Please modify and try again." =>
|
||||
return Self::RuleBansUserTextPrompt,
|
||||
"Content violates platform rules. Please modify and try again." =>
|
||||
return Self::RuleBansUserContent,
|
||||
"The generated video did not pass review. Credits will not be deducted." =>
|
||||
return Self::RuleBansGeneratedVideo,
|
||||
"The generated audio violates platform rules. Please adjust your prompt or images and try again." =>
|
||||
return Self::RuleBansGeneratedAudio,
|
||||
"The generated content violates platform rules. Please adjust your prompt or images and try again." =>
|
||||
return Self::RuleBansGeneratedContent,
|
||||
"Video generation failed. Please try again." =>
|
||||
return Self::GenerationFailed,
|
||||
"Generation timed out. Please try again." =>
|
||||
return Self::GenerationFailed,
|
||||
"Server error. Please try again later." =>
|
||||
return Self::GenerationFailed,
|
||||
"Your content could not be processed. Please try different images or adjust your prompt." =>
|
||||
return Self::GenerationFailed,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// --- Substring matches (case-insensitive) ---
|
||||
let lower = reason.to_lowercase();
|
||||
|
||||
if lower.contains("face detected") || lower.contains("ensure no faces") {
|
||||
return Self::RuleBansUserImageWithFaces;
|
||||
}
|
||||
if lower.contains("uploaded image violates") {
|
||||
return Self::RuleBansUserImage;
|
||||
}
|
||||
if lower.contains("input text violates") {
|
||||
return Self::RuleBansUserTextPrompt;
|
||||
}
|
||||
if lower.contains("generated video") && lower.contains("not pass review") {
|
||||
return Self::RuleBansGeneratedVideo;
|
||||
}
|
||||
if lower.contains("generated audio") && lower.contains("violates") {
|
||||
return Self::RuleBansGeneratedAudio;
|
||||
}
|
||||
if lower.contains("generated content") && lower.contains("violates") {
|
||||
return Self::RuleBansGeneratedContent;
|
||||
}
|
||||
if lower.contains("content violates") || lower.contains("platform rules") {
|
||||
return Self::RuleBansUserContent;
|
||||
}
|
||||
if lower.contains("video generation failed") || lower.contains("timed out") || lower.contains("server error") {
|
||||
return Self::GenerationFailed;
|
||||
}
|
||||
|
||||
Self::OtherUnknownReason
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,2 @@
|
||||
pub mod failure_reason;
|
||||
pub mod failure_type;
|
||||
pub mod poll_orders;
|
||||
mod request_types;
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_client_error::Seedance2ProClientError;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::error::seedance2pro_generic_api_error::Seedance2ProGenericApiError;
|
||||
use crate::requests::poll_orders::failure_reason::FailureReason;
|
||||
use crate::requests::poll_orders::request_types::*;
|
||||
use crate::utils::common_headers::FIREFOX_USER_AGENT;
|
||||
use log::info;
|
||||
@@ -11,6 +10,17 @@ use wreq_util::Emulation;
|
||||
|
||||
const GET_ORDERS_BASE_URL: &str = "https://seedance2-pro.com/api/trpc/userOrder.getOrders";
|
||||
|
||||
/// Builds the tRPC `input` JSON for the getOrders endpoint.
|
||||
/// When `cursor` is `Some`, it is included in the JSON payload.
|
||||
fn build_input_json(cursor: Option<u64>) -> String {
|
||||
match cursor {
|
||||
None => r#"{"0":{"json":{"limit":30,"format":null,"direction":"forward"},"meta":{"values":{"format":["undefined"]},"v":1}}}"#.to_string(),
|
||||
Some(c) => format!(
|
||||
r#"{{"0":{{"json":{{"limit":30,"format":null,"cursor":{cursor},"direction":"forward"}},"meta":{{"values":{{"format":["undefined"]}},"v":1}}}}}}"#,
|
||||
cursor = c
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Args & response ---
|
||||
|
||||
@@ -87,8 +97,8 @@ pub struct OrderStatus {
|
||||
/// Detailed result entries. Typically one entry per video.
|
||||
pub results: Vec<VideoResult>,
|
||||
|
||||
/// Structured failure reason. Populated when `task_status` is `Failed` or `fail_reason` is present.
|
||||
pub fail_reason: Option<FailureReason>,
|
||||
/// Failure reason. Populated when `task_status` is `Failed`.
|
||||
pub fail_reason: Option<String>,
|
||||
|
||||
/// ISO 8601 creation timestamp (e.g. `"2026-02-19T01:20:50.398Z"`).
|
||||
pub created_at: String,
|
||||
@@ -162,25 +172,18 @@ pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result<PollOrdersResponse,
|
||||
|
||||
let orders: Vec<OrderStatus> = json.orders
|
||||
.into_iter()
|
||||
.map(|o| {
|
||||
let task_status = TaskStatus::from_str(&o.task_status);
|
||||
let fail_reason = match (&o.fail_reason, &task_status) {
|
||||
(Some(reason), _) => Some(FailureReason::from_reason(reason)),
|
||||
(None, TaskStatus::Failed) => Some(FailureReason::from_reason("(no reason)")),
|
||||
_ => None,
|
||||
};
|
||||
OrderStatus {
|
||||
order_id: o.order_id,
|
||||
task_status,
|
||||
result_url: o.result_url,
|
||||
results: o.results.into_iter().map(|r| VideoResult {
|
||||
url: r.url,
|
||||
width: r.width,
|
||||
height: r.height,
|
||||
}).collect(),
|
||||
fail_reason,
|
||||
created_at: o.created_at,
|
||||
}
|
||||
.map(|o| OrderStatus {
|
||||
order_id: o.order_id,
|
||||
task_status: TaskStatus::from_str(&o.task_status),
|
||||
result_url: o.result_url,
|
||||
results: o.results.into_iter().map(|r| VideoResult {
|
||||
url: r.url,
|
||||
width: r.width,
|
||||
height: r.height,
|
||||
// ratio: r.ratio,
|
||||
}).collect(),
|
||||
fail_reason: o.fail_reason,
|
||||
created_at: o.created_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -189,18 +192,6 @@ pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result<PollOrdersResponse,
|
||||
Ok(PollOrdersResponse { orders, next_cursor })
|
||||
}
|
||||
|
||||
/// Builds the tRPC `input` JSON for the getOrders endpoint.
|
||||
/// When `cursor` is `Some`, it is included in the JSON payload.
|
||||
fn build_input_json(cursor: Option<u64>) -> String {
|
||||
match cursor {
|
||||
None => r#"{"0":{"json":{"limit":30,"format":null,"direction":"forward"},"meta":{"values":{"format":["undefined"]},"v":1}}}"#.to_string(),
|
||||
Some(c) => format!(
|
||||
r#"{{"0":{{"json":{{"limit":30,"format":null,"cursor":{cursor},"direction":"forward"}},"meta":{{"values":{{"format":["undefined"]}},"v":1}}}}}}"#,
|
||||
cursor = c
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,2 +1,27 @@
|
||||
pub mod run;
|
||||
pub mod seedance2pro;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "support-tool", about = "Production support CLI")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: TopLevelCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum TopLevelCommand {
|
||||
/// Seedance2 Pro support commands
|
||||
Seedance2pro {
|
||||
#[command(subcommand)]
|
||||
command: seedance2pro::Seedance2proCommand,
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn run(cli: Cli) -> anyhow::Result<()> {
|
||||
match cli.command {
|
||||
TopLevelCommand::Seedance2pro { command } => {
|
||||
seedance2pro::run(command).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
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"];
|
||||
names.extend_from_slice(seedance2pro::dispatch::SUBCOMMAND_NAMES);
|
||||
names
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "support-tool", about = "Production support CLI")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: TopLevelCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[command(rename_all = "snake_case")]
|
||||
pub enum TopLevelCommand {
|
||||
/// Seedance2 Pro support commands
|
||||
Seedance2pro {
|
||||
#[command(subcommand)]
|
||||
command: seedance2pro::Seedance2proCommand,
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn run(cli: Cli) -> anyhow::Result<()> {
|
||||
match cli.command {
|
||||
TopLevelCommand::Seedance2pro { command } => {
|
||||
seedance2pro::run(command).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,10 @@ use clap::Subcommand;
|
||||
use super::state::Seedance2ProState;
|
||||
use super::subcommands;
|
||||
|
||||
/// All canonical subcommand names for this module.
|
||||
/// Used by the underscore-insensitive arg normalizer.
|
||||
pub const SUBCOMMAND_NAMES: &[&str] = &["find_job", "failed_job_histogram"];
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[command(rename_all = "snake_case")]
|
||||
pub enum Seedance2proCommand {
|
||||
/// Find a job by its order ID across all pages
|
||||
FindJob(subcommands::find_job::FindJobArgs),
|
||||
|
||||
/// Scan all jobs and print a histogram of failure reasons
|
||||
FailedJobHistogram,
|
||||
Findjob(subcommands::findjob::FindjobArgs),
|
||||
}
|
||||
|
||||
pub async fn run(command: Seedance2proCommand) -> anyhow::Result<()> {
|
||||
@@ -25,7 +17,6 @@ pub async fn run(command: Seedance2proCommand) -> anyhow::Result<()> {
|
||||
let state = Seedance2ProState { cookies };
|
||||
|
||||
match command {
|
||||
Seedance2proCommand::FindJob(args) => subcommands::find_job::run(&state, args).await,
|
||||
Seedance2proCommand::FailedJobHistogram => subcommands::failed_job_histogram::run(&state).await,
|
||||
Seedance2proCommand::Findjob(args) => subcommands::findjob::run(&state, args).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod dispatch;
|
||||
mod dispatch;
|
||||
mod state;
|
||||
mod subcommands;
|
||||
|
||||
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use log::info;
|
||||
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use seedance2pro::requests::poll_orders::failure_type::FailureType;
|
||||
use seedance2pro::requests::poll_orders::poll_orders::{poll_orders, PollOrdersArgs, TaskStatus};
|
||||
|
||||
use super::super::state::Seedance2ProState;
|
||||
|
||||
pub async fn run(state: &Seedance2ProState) -> anyhow::Result<()> {
|
||||
let session = Seedance2ProSession::from_cookies_string(state.cookies.clone());
|
||||
|
||||
let mut cursor: Option<u64> = None;
|
||||
let mut page = 0usize;
|
||||
let mut total_orders = 0usize;
|
||||
let mut failed_count = 0usize;
|
||||
let mut histogram: BTreeMap<String, usize> = BTreeMap::new();
|
||||
|
||||
loop {
|
||||
page += 1;
|
||||
info!("Fetching page {} (cursor: {:?})...", page, cursor);
|
||||
|
||||
let result = poll_orders(PollOrdersArgs {
|
||||
session: &session,
|
||||
cursor,
|
||||
}).await
|
||||
.map_err(|err| anyhow!("Error polling orders on page {}: {:?}", page, err))?;
|
||||
|
||||
let page_count = result.orders.len();
|
||||
total_orders += page_count;
|
||||
|
||||
for order in &result.orders {
|
||||
if order.task_status == TaskStatus::Failed {
|
||||
failed_count += 1;
|
||||
let reason = order.fail_reason.as_ref()
|
||||
.map(|fr| fr.reason.as_str())
|
||||
.unwrap_or("(no reason)");
|
||||
*histogram.entry(reason.to_string()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Page {}: {} orders ({} failed so far).", page, page_count, failed_count);
|
||||
|
||||
cursor = result.next_cursor;
|
||||
if cursor.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nScanned {} orders across {} pages.", total_orders, page);
|
||||
println!("Failed: {}\n", failed_count);
|
||||
|
||||
if histogram.is_empty() {
|
||||
println!("No failed jobs found.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Sort by count descending.
|
||||
let mut sorted: Vec<(&String, &usize)> = histogram.iter().collect();
|
||||
sorted.sort_by(|a, b| b.1.cmp(a.1));
|
||||
|
||||
println!("{:<6} {}", "Count", "Failure Reason");
|
||||
println!("{:<6} {}", "-----", "--------------");
|
||||
for (reason, count) in &sorted {
|
||||
println!("{:<6} {}", count, reason);
|
||||
}
|
||||
|
||||
// Build a second histogram by FailureType.
|
||||
let mut type_histogram: BTreeMap<String, usize> = BTreeMap::new();
|
||||
for (reason, count) in &histogram {
|
||||
let failure_type = FailureType::classify_text(reason);
|
||||
*type_histogram.entry(format!("{:?}", failure_type)).or_insert(0) += count;
|
||||
}
|
||||
|
||||
let mut type_sorted: Vec<(&String, &usize)> = type_histogram.iter().collect();
|
||||
type_sorted.sort_by(|a, b| b.1.cmp(a.1));
|
||||
|
||||
println!("\n{:<6} {}", "Count", "Failure Type");
|
||||
println!("{:<6} {}", "-----", "------------");
|
||||
for (failure_type, count) in &type_sorted {
|
||||
println!("{:<6} {}", count, failure_type);
|
||||
}
|
||||
|
||||
// Print reason → type mapping table.
|
||||
println!("\n{:<30} {}", "Failure Type", "Failure Reason");
|
||||
println!("{:<30} {}", "------------", "--------------");
|
||||
for (reason, _) in &sorted {
|
||||
let failure_type = FailureType::classify_text(reason);
|
||||
println!("{:<30} {}", format!("{:?}", failure_type), reason);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+3
-4
@@ -7,13 +7,13 @@ use seedance2pro::requests::poll_orders::poll_orders::{poll_orders, PollOrdersAr
|
||||
use super::super::state::Seedance2ProState;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct FindJobArgs {
|
||||
pub struct FindjobArgs {
|
||||
/// The order ID (job token) to search for
|
||||
#[arg(long)]
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
pub async fn run(state: &Seedance2ProState, args: FindJobArgs) -> anyhow::Result<()> {
|
||||
pub async fn run(state: &Seedance2ProState, args: FindjobArgs) -> anyhow::Result<()> {
|
||||
let session = Seedance2ProSession::from_cookies_string(state.cookies.clone());
|
||||
|
||||
let mut cursor: Option<u64> = None;
|
||||
@@ -53,8 +53,7 @@ fn order_to_json(order: &seedance2pro::requests::poll_orders::poll_orders::Order
|
||||
"order_id": order.order_id,
|
||||
"task_status": format!("{:?}", order.task_status),
|
||||
"result_url": order.result_url,
|
||||
"fail_reason": order.fail_reason.as_ref().map(|fr| fr.reason.as_str()),
|
||||
"failure_type": order.fail_reason.as_ref().map(|fr| format!("{:?}", fr.failure_type)),
|
||||
"fail_reason": order.fail_reason,
|
||||
"created_at": order.created_at,
|
||||
"results": order.results.iter().map(|r| {
|
||||
serde_json::json!({
|
||||
@@ -1,2 +1 @@
|
||||
pub mod failed_job_histogram;
|
||||
pub mod find_job;
|
||||
pub mod findjob;
|
||||
|
||||
@@ -2,17 +2,12 @@ mod commands;
|
||||
mod utils;
|
||||
|
||||
use clap::Parser;
|
||||
use commands::run::{Cli, all_canonical_names};
|
||||
use utils::normalize_subcommands::normalize_subcommand_args;
|
||||
use commands::Cli;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Normalize args so underscores in subcommand names are optional.
|
||||
let canonical_names = all_canonical_names();
|
||||
let args = normalize_subcommand_args(std::env::args(), &canonical_names);
|
||||
|
||||
// Parse CLI args first so --help works without the env file.
|
||||
let cli = Cli::parse_from(args);
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Load secrets from .env file.
|
||||
easyenv::from_filename(".env-support-tool-secrets")
|
||||
@@ -20,5 +15,5 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
easyenv::init_env_logger(Some("info"));
|
||||
|
||||
commands::run::run(cli).await
|
||||
commands::run(cli).await
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
pub mod normalize_subcommands;
|
||||
// Shared utilities for support-tool commands.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/// Normalize CLI args so that underscores in subcommand names are optional.
|
||||
///
|
||||
/// For each arg that isn't a flag (doesn't start with `-`), if removing all
|
||||
/// underscores from the arg matches removing all underscores from a canonical
|
||||
/// name, the arg is replaced with the canonical name.
|
||||
///
|
||||
/// This lets users type `findjob`, `find_job`, or even `f_ind_job` and all
|
||||
/// resolve to the canonical `find_job`.
|
||||
pub fn normalize_subcommand_args(
|
||||
args: impl IntoIterator<Item = String>,
|
||||
canonical_names: &[&str],
|
||||
) -> Vec<String> {
|
||||
args.into_iter()
|
||||
.map(|arg| {
|
||||
if arg.starts_with('-') {
|
||||
return arg;
|
||||
}
|
||||
let stripped = arg.replace('_', "").to_lowercase();
|
||||
for &name in canonical_names {
|
||||
if name.replace('_', "").to_lowercase() == stripped {
|
||||
return name.to_string();
|
||||
}
|
||||
}
|
||||
arg
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -18,7 +18,6 @@ use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::list_tasks_for_frontend::list_tasks_for_frontend;
|
||||
use artcraft_client::endpoints::media_files::delete_media_file::delete_media_file;
|
||||
use tauri::{AppHandle, State};
|
||||
use enums::tauri::tasks::task_failure_type::TaskFailureType;
|
||||
use enums::tauri::tasks::task_media_file_class::TaskMediaFileClass;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
@@ -32,20 +31,12 @@ pub struct GetTaskQueueCommandResponse {
|
||||
#[derive(Serialize)]
|
||||
pub struct TaskQueueItem {
|
||||
pub id: TaskId,
|
||||
|
||||
pub task_status: TaskStatus,
|
||||
pub task_type: TaskType,
|
||||
pub model_type: Option<TaskModelType>,
|
||||
|
||||
pub provider: Option<GenerationProvider>,
|
||||
pub provider_job_id: Option<String>,
|
||||
|
||||
/// If the item is done, these will be filled out.
|
||||
pub completed_item: Option<CompletedItemData>,
|
||||
|
||||
/// If the item failed, this will be filled out.
|
||||
pub failure_reason: Option<FailedItemData>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
@@ -72,12 +63,6 @@ pub struct MediaFileData {
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FailedItemData {
|
||||
pub failure_type: TaskFailureType,
|
||||
pub failure_message: Option<String>,
|
||||
}
|
||||
|
||||
impl SerializeMarker for GetTaskQueueCommandResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -118,7 +103,6 @@ pub async fn handle_request(
|
||||
|
||||
for task in tasks.tasks.into_iter() {
|
||||
let mut completed_item = None;
|
||||
let mut failure_reason = None;
|
||||
|
||||
if task.status == TaskStatus::CompleteSuccess {
|
||||
let token_and_url = task.on_complete_primary_media_file_token
|
||||
@@ -139,19 +123,6 @@ pub async fn handle_request(
|
||||
} else {
|
||||
warn!("Task {} is marked complete but has no primary media file token or URL.", task.id);
|
||||
}
|
||||
} else {
|
||||
// If either failure field is present, fill out the failure report.
|
||||
if let Some(failure_message) = task.on_failure_message.as_deref() {
|
||||
failure_reason = Some(FailedItemData {
|
||||
failure_type: task.on_failure_type.unwrap_or(TaskFailureType::Unknown),
|
||||
failure_message: Some(failure_message.to_string()),
|
||||
});
|
||||
} else if let Some(failure_type) = task.on_failure_type {
|
||||
failure_reason = Some(FailedItemData {
|
||||
failure_type,
|
||||
failure_message: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
transformed_tasks.push(TaskQueueItem {
|
||||
@@ -165,7 +136,6 @@ pub async fn handle_request(
|
||||
updated_at: task.updated_at,
|
||||
completed_at: task.completed_at,
|
||||
completed_item,
|
||||
failure_reason,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::Artcraft
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::storyteller::threads::storyteller_activity_thread::storyteller_activity_thread;
|
||||
use crate::services::storyteller::threads::storyteller_task_polling_thread::storyteller_task_polling_thread::storyteller_task_polling_thread;
|
||||
use crate::services::storyteller::threads::storyteller_task_polling_thread::storyteller_task_polling_thread;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
/// We increment this whenever we make a change to the database schema.
|
||||
/// This prevents deadlock on startup if the schema has changed from the
|
||||
/// previous version.
|
||||
pub const TASK_DATABASE_VERSION: u32 = 6;
|
||||
pub const TASK_DATABASE_VERSION: u32 = 5;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod events;
|
||||
pub mod storyteller_activity_thread;
|
||||
pub mod storyteller_task_polling_thread;
|
||||
+21
-3
@@ -1,16 +1,34 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::artcraft_platform_info::ArtcraftPlatformInfo;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::state::os_platform::OsPlatform;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::analytics::log_active_user::LogAppActiveUserRequest;
|
||||
use artcraft_client::endpoints::analytics::log_active_user_v2::log_active_user_v2;
|
||||
use artcraft_client::error::api_error::ApiError;
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use artcraft_api_defs::jobs::list_session_jobs::ListSessionJobsItem;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use log::{debug, error, info};
|
||||
use os_info::Info;
|
||||
use reqwest::Url;
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs};
|
||||
use sqlite_tasks::queries::update_task_status::{update_task_status, UpdateTaskArgs};
|
||||
use std::time::Instant;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::endpoints::analytics::log_active_user::log_active_user;
|
||||
use artcraft_client::endpoints::analytics::log_active_user_v2::log_active_user_v2;
|
||||
use artcraft_client::endpoints::jobs::list_session_jobs::{list_session_jobs, States};
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use artcraft_client::error::api_error::ApiError;
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::app_session::AppSessionToken;
|
||||
|
||||
const CLIENT_NAME : &str = "artcraft";
|
||||
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_complete_event::GenerationCompleteEvent;
|
||||
use crate::core::events::generation_events::generation_failed_event::GenerationFailedEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::core::utils::enum_conversion::generation_provider::to_generation_service_provider;
|
||||
use crate::core::utils::enum_conversion::task_type::to_generation_action;
|
||||
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 crate::services::storyteller::threads::events::maybe_handle_frontend_caller_notification::maybe_handle_frontend_caller_notification;
|
||||
use crate::services::storyteller::threads::events::maybe_handle_inpainting_complete_event::maybe_handle_inpainting_complete_event;
|
||||
use crate::services::storyteller::threads::events::maybe_handle_text_to_image_complete_event::maybe_handle_text_to_image_complete_event;
|
||||
use crate::services::storyteller::threads::events::maybe_send_background_removal_complete_event::maybe_send_background_removal_complete_event;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::jobs::list_session_jobs::ListSessionJobsItem;
|
||||
use artcraft_api_defs::utils::media_links_to_thumbnail_template::media_links_to_thumbnail_template;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::tauri::tasks::task_media_file_class::TaskMediaFileClass;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use reqwest::Url;
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs};
|
||||
use sqlite_tasks::queries::task::Task;
|
||||
use sqlite_tasks::queries::update_successful_task_status_with_metadata::{update_successful_task_status_with_metadata, UpdateSuccessfulTaskArgs};
|
||||
use sqlite_tasks::queries::update_task_status::{update_task_status, UpdateTaskArgs};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::endpoints::jobs::list_session_jobs::{list_session_jobs, States};
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use artcraft_client::error::api_error::ApiError;
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use tauri::AppHandle;
|
||||
use tempdir::TempDir;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn storyteller_task_polling_thread(
|
||||
app_handle: AppHandle,
|
||||
app_env_configs: AppEnvConfigs,
|
||||
task_database: TaskDatabase,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_handle,
|
||||
&app_env_configs,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
).await;
|
||||
if let Err(err) = res {
|
||||
error!("An error occurred: {:?}", err);
|
||||
}
|
||||
// NB: Only sleep if an error occurs.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(30_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn polling_loop(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
// Wait before next request for jobs.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(5_000)).await;
|
||||
|
||||
let creds = storyteller_creds_manager.get_credentials()?;
|
||||
|
||||
let result = list_session_jobs(
|
||||
&app_env_configs.storyteller_host,
|
||||
creds.as_ref(),
|
||||
States::All,
|
||||
).await;
|
||||
|
||||
let jobs = match result {
|
||||
Ok(result) => result.jobs,
|
||||
Err(err) => {
|
||||
match &err {
|
||||
StorytellerError::Api(ApiError::TooManyRequests(message)) => {
|
||||
error!("Too many requests (sleeping): {:?}", message);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(60_000)).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Err(anyhow!(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_ids = jobs.iter()
|
||||
.map(|job| job.job_token.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tasks = list_tasks_by_provider_and_tokens(ListTasksArgs {
|
||||
db: task_database.get_connection(),
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_ids: Some(job_ids),
|
||||
}).await?;
|
||||
|
||||
let tasks = tasks.tasks;
|
||||
|
||||
let jobs_by_id = jobs.iter()
|
||||
.map(|job| (job.job_token.to_string(), job))
|
||||
.collect::<HashMap<String, _>>();
|
||||
|
||||
let tasks_by_provider_job_id = tasks.iter()
|
||||
.filter_map(|task| {
|
||||
if let Some(provider_job_id) = &task.provider_job_id {
|
||||
Some((provider_job_id.clone(), task.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
//.map(|task| (task.provider_job_id.clone(), task.clone()))
|
||||
.collect::<HashMap<String, Task>>();
|
||||
|
||||
for job in jobs.iter() {
|
||||
let task = match tasks_by_provider_job_id.get(job.job_token.as_str()) {
|
||||
Some(task) => task,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
match job.status.status {
|
||||
JobStatusPlus::CompleteSuccess => {
|
||||
match task.status {
|
||||
TaskStatus::CompleteSuccess => continue,
|
||||
_ => {}
|
||||
}
|
||||
handle_successful_job(app_handle, app_env_configs, creds.as_ref(), job, task, task_database).await?;
|
||||
}
|
||||
JobStatusPlus::CompleteFailure => {
|
||||
match task.status {
|
||||
TaskStatus::CompleteFailure => continue,
|
||||
_ => {}
|
||||
}
|
||||
handle_failed_job(app_handle, job, task, task_database).await?;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_successful_job(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: Option<&StorytellerCredentialSet>,
|
||||
job: &ListSessionJobsItem,
|
||||
task: &Task,
|
||||
task_database: &TaskDatabase,
|
||||
) -> AnyhowResult<()> {
|
||||
let maybe_primary_media_file_token = job.maybe_result
|
||||
.as_ref()
|
||||
.map(|result| MediaFileToken::new_from_str(&result.entity_token));
|
||||
|
||||
let updated = update_successful_task_status_with_metadata(UpdateSuccessfulTaskArgs {
|
||||
db: task_database.get_connection(),
|
||||
task_id: &task.id,
|
||||
maybe_batch_token: job.maybe_result
|
||||
.as_ref()
|
||||
.map(|result| result.maybe_batch_token.as_ref())
|
||||
.flatten(),
|
||||
maybe_primary_media_file_token: maybe_primary_media_file_token.as_ref(),
|
||||
maybe_primary_media_file_class: get_media_file_class(job),
|
||||
maybe_primary_media_file_thumbnail_url_template: get_thumbnail_template(job),
|
||||
maybe_primary_media_file_cdn_url: job.maybe_result
|
||||
.as_ref()
|
||||
.map(|result| result.media_links.cdn_url.as_str()),
|
||||
}).await?;
|
||||
|
||||
if !updated {
|
||||
return Ok(()); // If anything breaks with queries, don't spam events.
|
||||
}
|
||||
|
||||
send_additional_success_events(app_handle, app_env_configs, creds, job, task).await;
|
||||
|
||||
let service = to_generation_service_provider(task.provider);
|
||||
let action = to_generation_action(task.task_type);
|
||||
|
||||
let event = GenerationCompleteEvent {
|
||||
action: Some(action),
|
||||
service,
|
||||
model: None, // TODO
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app_handle) {
|
||||
error!("Failed to send GenerationCompleteEvent: {:?}", err); // Fail open
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_failed_job(
|
||||
app_handle: &AppHandle,
|
||||
job: &ListSessionJobsItem,
|
||||
task: &Task,
|
||||
task_database: &TaskDatabase,
|
||||
) -> AnyhowResult<()> {
|
||||
info!("Marking storyteller job as failed: {:?}", task.id);
|
||||
|
||||
update_task_status(UpdateTaskArgs {
|
||||
db: task_database.get_connection(),
|
||||
task_id: &task.id,
|
||||
status: TaskStatus::CompleteFailure,
|
||||
}).await?;
|
||||
|
||||
let service = to_generation_service_provider(task.provider);
|
||||
let action = to_generation_action(task.task_type);
|
||||
|
||||
let event = GenerationFailedEvent {
|
||||
action,
|
||||
service,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
event.send_infallible(app_handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_additional_success_events(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: Option<&StorytellerCredentialSet>,
|
||||
job: &ListSessionJobsItem,
|
||||
task: &Task
|
||||
) {
|
||||
info!("Attempting to dispatch events for completed Storyteller job: {:?}", task);
|
||||
|
||||
let result = maybe_handle_frontend_caller_notification(
|
||||
app_handle,
|
||||
app_env_configs,
|
||||
creds,
|
||||
task,
|
||||
job,
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to send generation complete event: {:?}", err);
|
||||
}
|
||||
|
||||
//let result = maybe_handle_text_to_image_complete_event(
|
||||
// app_handle,
|
||||
// app_env_configs,
|
||||
// creds,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send text-to-image complete event: {:?}", err);
|
||||
//}
|
||||
|
||||
//let result = maybe_handle_inpainting_complete_event(
|
||||
// app_handle,
|
||||
// app_env_configs,
|
||||
// creds,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send image inpainting complete event: {:?}", err);
|
||||
//}
|
||||
|
||||
//let result = maybe_send_background_removal_complete_event(
|
||||
// app_handle,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send background removal complete event: {:?}", err);
|
||||
//}
|
||||
}
|
||||
|
||||
fn get_thumbnail_template<'a>(job: &'a ListSessionJobsItem) -> Option<&'a str> {
|
||||
let links = match job.maybe_result.as_ref() {
|
||||
None => return None,
|
||||
Some(result) => &result.media_links,
|
||||
};
|
||||
|
||||
media_links_to_thumbnail_template(links)
|
||||
}
|
||||
|
||||
fn get_media_file_class<'a>(job: &'a ListSessionJobsItem) -> Option<TaskMediaFileClass> {
|
||||
match job.request.inference_category {
|
||||
InferenceCategory::BackgroundRemoval => return Some(TaskMediaFileClass::Image),
|
||||
InferenceCategory::ImageGeneration => return Some(TaskMediaFileClass::Image),
|
||||
InferenceCategory::VideoGeneration => return Some(TaskMediaFileClass::Video),
|
||||
InferenceCategory::ObjectGeneration => return Some(TaskMediaFileClass::Dimensional),
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
let result = match job.maybe_result.as_ref() {
|
||||
None => return None,
|
||||
Some(result) => result,
|
||||
};
|
||||
|
||||
let url = result.media_links.cdn_url.as_str();
|
||||
|
||||
if url.ends_with("jpg")
|
||||
|| url.ends_with("jpeg")
|
||||
|| url.ends_with("png")
|
||||
{
|
||||
return Some(TaskMediaFileClass::Image);
|
||||
}
|
||||
|
||||
if url.ends_with("mp4")
|
||||
|| url.ends_with("webm")
|
||||
{
|
||||
return Some(TaskMediaFileClass::Video);
|
||||
}
|
||||
|
||||
if url.ends_with("glb") {
|
||||
return Some(TaskMediaFileClass::Dimensional);
|
||||
}
|
||||
|
||||
if url.ends_with("wav")
|
||||
|| url.ends_with("mp3")
|
||||
{
|
||||
return Some(TaskMediaFileClass::Audio);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::generation_failed_event::GenerationFailedEvent;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::core::utils::enum_conversion::generation_provider::to_generation_service_provider;
|
||||
use crate::core::utils::enum_conversion::task_type::to_generation_action;
|
||||
use artcraft_api_defs::jobs::list_session_jobs::ListSessionJobsItem;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use sqlite_tasks::queries::task::Task;
|
||||
use sqlite_tasks::queries::update_task_status::{update_task_status, UpdateTaskArgs};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_failed_job(
|
||||
app_handle: &AppHandle,
|
||||
job: &ListSessionJobsItem,
|
||||
task: &Task,
|
||||
task_database: &TaskDatabase,
|
||||
) -> AnyhowResult<()> {
|
||||
info!("Marking storyteller job as failed: {:?}", task.id);
|
||||
|
||||
update_task_status(UpdateTaskArgs {
|
||||
db: task_database.get_connection(),
|
||||
task_id: &task.id,
|
||||
status: TaskStatus::CompleteFailure,
|
||||
}).await?;
|
||||
|
||||
let service = to_generation_service_provider(task.provider);
|
||||
let action = to_generation_action(task.task_type);
|
||||
|
||||
let event = GenerationFailedEvent {
|
||||
action,
|
||||
service,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
event.send_infallible(app_handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::GenerationAction;
|
||||
use crate::core::events::generation_events::generation_complete_event::GenerationCompleteEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::core::utils::enum_conversion::generation_provider::to_generation_service_provider;
|
||||
use crate::core::utils::enum_conversion::task_type::to_generation_action;
|
||||
use super::events::maybe_handle_frontend_caller_notification::maybe_handle_frontend_caller_notification;
|
||||
use artcraft_api_defs::jobs::list_session_jobs::ListSessionJobsItem;
|
||||
use artcraft_api_defs::utils::media_links_to_thumbnail_template::media_links_to_thumbnail_template;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::tauri::tasks::task_media_file_class::TaskMediaFileClass;
|
||||
use errors::AnyhowResult;
|
||||
use log::error;
|
||||
use log::info;
|
||||
use sqlite_tasks::queries::task::Task;
|
||||
use sqlite_tasks::queries::update_successful_task_status_with_metadata::{update_successful_task_status_with_metadata, UpdateSuccessfulTaskArgs};
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_successful_job(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: Option<&StorytellerCredentialSet>,
|
||||
job: &ListSessionJobsItem,
|
||||
task: &Task,
|
||||
task_database: &TaskDatabase,
|
||||
) -> AnyhowResult<()> {
|
||||
let maybe_primary_media_file_token = job.maybe_result
|
||||
.as_ref()
|
||||
.map(|result| MediaFileToken::new_from_str(&result.entity_token));
|
||||
|
||||
let updated = update_successful_task_status_with_metadata(UpdateSuccessfulTaskArgs {
|
||||
db: task_database.get_connection(),
|
||||
task_id: &task.id,
|
||||
maybe_batch_token: job.maybe_result
|
||||
.as_ref()
|
||||
.map(|result| result.maybe_batch_token.as_ref())
|
||||
.flatten(),
|
||||
maybe_primary_media_file_token: maybe_primary_media_file_token.as_ref(),
|
||||
maybe_primary_media_file_class: get_media_file_class(job),
|
||||
maybe_primary_media_file_thumbnail_url_template: get_thumbnail_template(job),
|
||||
maybe_primary_media_file_cdn_url: job.maybe_result
|
||||
.as_ref()
|
||||
.map(|result| result.media_links.cdn_url.as_str()),
|
||||
}).await?;
|
||||
|
||||
if !updated {
|
||||
return Ok(()); // If anything breaks with queries, don't spam events.
|
||||
}
|
||||
|
||||
send_additional_success_events(app_handle, app_env_configs, creds, job, task).await;
|
||||
|
||||
let service = to_generation_service_provider(task.provider);
|
||||
let action = to_generation_action(task.task_type);
|
||||
|
||||
let event = GenerationCompleteEvent {
|
||||
action: Some(action),
|
||||
service,
|
||||
model: None, // TODO
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app_handle) {
|
||||
error!("Failed to send GenerationCompleteEvent: {:?}", err); // Fail open
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_additional_success_events(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: Option<&StorytellerCredentialSet>,
|
||||
job: &ListSessionJobsItem,
|
||||
task: &Task
|
||||
) {
|
||||
info!("Attempting to dispatch events for completed Storyteller job: {:?}", task);
|
||||
|
||||
let result = maybe_handle_frontend_caller_notification(
|
||||
app_handle,
|
||||
app_env_configs,
|
||||
creds,
|
||||
task,
|
||||
job,
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to send generation complete event: {:?}", err);
|
||||
}
|
||||
|
||||
//let result = maybe_handle_text_to_image_complete_event(
|
||||
// app_handle,
|
||||
// app_env_configs,
|
||||
// creds,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send text-to-image complete event: {:?}", err);
|
||||
//}
|
||||
|
||||
//let result = maybe_handle_inpainting_complete_event(
|
||||
// app_handle,
|
||||
// app_env_configs,
|
||||
// creds,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send image inpainting complete event: {:?}", err);
|
||||
//}
|
||||
|
||||
//let result = maybe_send_background_removal_complete_event(
|
||||
// app_handle,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send background removal complete event: {:?}", err);
|
||||
//}
|
||||
}
|
||||
|
||||
fn get_thumbnail_template<'a>(job: &'a ListSessionJobsItem) -> Option<&'a str> {
|
||||
let links = match job.maybe_result.as_ref() {
|
||||
None => return None,
|
||||
Some(result) => &result.media_links,
|
||||
};
|
||||
|
||||
media_links_to_thumbnail_template(links)
|
||||
}
|
||||
|
||||
fn get_media_file_class(job: &ListSessionJobsItem) -> Option<TaskMediaFileClass> {
|
||||
match job.request.inference_category {
|
||||
InferenceCategory::BackgroundRemoval => return Some(TaskMediaFileClass::Image),
|
||||
InferenceCategory::ImageGeneration => return Some(TaskMediaFileClass::Image),
|
||||
InferenceCategory::VideoGeneration => return Some(TaskMediaFileClass::Video),
|
||||
InferenceCategory::ObjectGeneration => return Some(TaskMediaFileClass::Dimensional),
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
let result = match job.maybe_result.as_ref() {
|
||||
None => return None,
|
||||
Some(result) => result,
|
||||
};
|
||||
|
||||
let url = result.media_links.cdn_url.as_str();
|
||||
|
||||
if url.ends_with("jpg")
|
||||
|| url.ends_with("jpeg")
|
||||
|| url.ends_with("png")
|
||||
{
|
||||
return Some(TaskMediaFileClass::Image);
|
||||
}
|
||||
|
||||
if url.ends_with("mp4")
|
||||
|| url.ends_with("webm")
|
||||
{
|
||||
return Some(TaskMediaFileClass::Video);
|
||||
}
|
||||
|
||||
if url.ends_with("glb") {
|
||||
return Some(TaskMediaFileClass::Dimensional);
|
||||
}
|
||||
|
||||
if url.ends_with("wav")
|
||||
|| url.ends_with("mp3")
|
||||
{
|
||||
return Some(TaskMediaFileClass::Audio);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
mod events;
|
||||
mod handle_storyteller_failed_job;
|
||||
mod handle_storyteller_successful_job;
|
||||
pub mod storyteller_task_polling_thread;
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::storyteller::threads::storyteller_task_polling_thread::handle_storyteller_failed_job::handle_failed_job;
|
||||
use crate::services::storyteller::threads::storyteller_task_polling_thread::handle_storyteller_successful_job::handle_successful_job;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::endpoints::jobs::list_session_jobs::{list_session_jobs, States};
|
||||
use artcraft_client::error::api_error::ApiError;
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use errors::AnyhowResult;
|
||||
use log::error;
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs};
|
||||
use sqlite_tasks::queries::task::Task;
|
||||
use std::collections::HashMap;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn storyteller_task_polling_thread(
|
||||
app_handle: AppHandle,
|
||||
app_env_configs: AppEnvConfigs,
|
||||
task_database: TaskDatabase,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_handle,
|
||||
&app_env_configs,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
).await;
|
||||
if let Err(err) = res {
|
||||
error!("An error occurred: {:?}", err);
|
||||
}
|
||||
// NB: Only sleep if an error occurs.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(30_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn polling_loop(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
// Wait before next request for jobs.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(5_000)).await;
|
||||
|
||||
let creds = storyteller_creds_manager.get_credentials()?;
|
||||
|
||||
let result = list_session_jobs(
|
||||
&app_env_configs.storyteller_host,
|
||||
creds.as_ref(),
|
||||
States::All,
|
||||
).await;
|
||||
|
||||
let jobs = match result {
|
||||
Ok(result) => result.jobs,
|
||||
Err(err) => {
|
||||
match &err {
|
||||
StorytellerError::Api(ApiError::TooManyRequests(message)) => {
|
||||
error!("Too many requests (sleeping): {:?}", message);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(60_000)).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Err(anyhow!(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_ids = jobs.iter()
|
||||
.map(|job| job.job_token.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tasks = list_tasks_by_provider_and_tokens(ListTasksArgs {
|
||||
db: task_database.get_connection(),
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_ids: Some(job_ids),
|
||||
}).await?;
|
||||
|
||||
let tasks = tasks.tasks;
|
||||
|
||||
let jobs_by_id = jobs.iter()
|
||||
.map(|job| (job.job_token.to_string(), job))
|
||||
.collect::<HashMap<String, _>>();
|
||||
|
||||
let tasks_by_provider_job_id = tasks.iter()
|
||||
.filter_map(|task| {
|
||||
if let Some(provider_job_id) = &task.provider_job_id {
|
||||
Some((provider_job_id.clone(), task.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
//.map(|task| (task.provider_job_id.clone(), task.clone()))
|
||||
.collect::<HashMap<String, Task>>();
|
||||
|
||||
for job in jobs.iter() {
|
||||
let task = match tasks_by_provider_job_id.get(job.job_token.as_str()) {
|
||||
Some(task) => task,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
match job.status.status {
|
||||
JobStatusPlus::CompleteSuccess => {
|
||||
match task.status {
|
||||
TaskStatus::CompleteSuccess => continue, // NB: We're done with this task.
|
||||
_ => {}
|
||||
}
|
||||
handle_successful_job(app_handle, app_env_configs, creds.as_ref(), job, task, task_database).await?;
|
||||
}
|
||||
JobStatusPlus::CompleteFailure => {
|
||||
match task.status {
|
||||
TaskStatus::CompleteFailure => continue, // NB: We're done with this task.
|
||||
_ => {}
|
||||
}
|
||||
handle_failed_job(app_handle, job, task, task_database).await?;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-2
@@ -63,8 +63,6 @@ pub async fn insert_generic_inference_job_for_fal_queue<'e, 'c : 'e, E>(args: In
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
mysql_executor: args.mysql_executor,
|
||||
phantom: args.phantom,
|
||||
};
|
||||
|
||||
-6
@@ -2,7 +2,6 @@ use idempotency::uuid::generate_random_uuid;
|
||||
use sqlx::{Executor, MySql};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
@@ -38,9 +37,6 @@ pub struct InsertGenericInferenceForFalMockFailureArgs<'e, 'c, E>
|
||||
pub creator_ip_address: &'e str,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
pub maybe_failure_reason: Option<&'e str>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
@@ -66,8 +62,6 @@ pub async fn insert_generic_inference_job_for_fal_queue_mock_failure<'e, 'c: 'e,
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
starting_job_status_override: Some(JobStatusPlus::CompleteFailure),
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
maybe_failure_reason: args.maybe_failure_reason,
|
||||
mysql_executor: args.mysql_executor,
|
||||
phantom: args.phantom,
|
||||
};
|
||||
|
||||
+14
-25
@@ -4,38 +4,41 @@ use sqlx::MySqlPool;
|
||||
use sqlx::{Executor, MySql};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_input_source_token_type::InferenceInputSourceTokenType;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
|
||||
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
use crate::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
|
||||
use crate::queries::tts::tts_inference_jobs::kill_tts_inference_jobs::JobStatus;
|
||||
|
||||
|
||||
pub struct InsertGenericInferenceForFalWithAprioriJobTokenArgs<'e, 'c, E>
|
||||
pub struct InsertGenericInferenceForFalWithAprioriJobTokenArgs<'e, 'c, E>
|
||||
where E: 'e + Executor<'c, Database = MySql>
|
||||
{
|
||||
pub uuid_idempotency_token: &'e str,
|
||||
|
||||
|
||||
// NOTE: We'll generate this ahead of time so we can save it with billing info!
|
||||
pub apriori_job_token: &'e InferenceJobToken,
|
||||
|
||||
/// The external primary key identifier for the job.
|
||||
pub maybe_external_third_party_id: &'e str,
|
||||
|
||||
|
||||
pub fal_category: FalCategory,
|
||||
|
||||
pub maybe_inference_args: Option<GenericInferenceArgs>,
|
||||
|
||||
|
||||
pub maybe_prompt_token: Option<&'e PromptToken>,
|
||||
|
||||
pub maybe_creator_user_token: Option<&'e UserToken>,
|
||||
@@ -47,11 +50,8 @@ 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_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
pub maybe_failure_reason: Option<&'e str>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
|
||||
|
||||
// TODO: Not sure if this works to tell the compiler we need the lifetime annotation.
|
||||
// See: https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-lifetime-parameters
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
@@ -87,16 +87,11 @@ pub async fn insert_generic_inference_job_for_fal_queue_with_apriori_job_token<'
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
let maybe_external_third_party = InferenceJobExternalThirdParty::Fal;
|
||||
|
||||
|
||||
let status = args.starting_job_status_override.unwrap_or(JobStatusPlus::Pending);
|
||||
|
||||
let maybe_frontend_failure_category_str = args.maybe_frontend_failure_category
|
||||
.map(|c| c.to_str());
|
||||
|
||||
let maybe_truncated_failure_reason = args.maybe_failure_reason
|
||||
.map(|s| if s.len() > 255 { &s[..255] } else { s });
|
||||
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO generic_inference_jobs
|
||||
@@ -120,7 +115,7 @@ SET
|
||||
|
||||
maybe_download_url = NULL,
|
||||
maybe_cover_image_media_file_token = NULL,
|
||||
|
||||
|
||||
maybe_prompt_token = ?,
|
||||
|
||||
maybe_raw_inference_text = NULL,
|
||||
@@ -139,22 +134,19 @@ SET
|
||||
is_debug_request = FALSE,
|
||||
maybe_routing_tag = NULL,
|
||||
|
||||
frontend_failure_category = ?,
|
||||
failure_reason = ?,
|
||||
|
||||
status = ?
|
||||
"#,
|
||||
args.apriori_job_token.as_str(),
|
||||
args.uuid_idempotency_token,
|
||||
|
||||
JOB_TYPE.to_str(),
|
||||
|
||||
|
||||
maybe_external_third_party.to_str(),
|
||||
args.maybe_external_third_party_id,
|
||||
|
||||
product_category.to_str(),
|
||||
inference_category.to_str(),
|
||||
|
||||
|
||||
args.maybe_prompt_token.map(|t| t.to_string()),
|
||||
|
||||
serialized_args_payload,
|
||||
@@ -164,9 +156,6 @@ SET
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
maybe_frontend_failure_category_str,
|
||||
maybe_truncated_failure_reason,
|
||||
|
||||
status.to_str(),
|
||||
);
|
||||
|
||||
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
use log::warn;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
use crate::errors::select_optional_record_error::SelectOptionalRecordError;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct UsernameDetails {
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub username_is_generated: bool,
|
||||
pub username_is_not_customized: bool,
|
||||
}
|
||||
|
||||
pub async fn get_username_by_user_token(
|
||||
user_token: &UserToken,
|
||||
pool: &MySqlPool,
|
||||
) -> Result<Option<UsernameDetails>, SelectOptionalRecordError> {
|
||||
let result = sqlx::query_as!(
|
||||
UsernameDetails,
|
||||
r#"
|
||||
SELECT
|
||||
username,
|
||||
display_name,
|
||||
username_is_generated as `username_is_generated: bool`,
|
||||
username_is_not_customized as `username_is_not_customized: bool`
|
||||
FROM users
|
||||
WHERE
|
||||
token = ?
|
||||
AND user_deleted_at IS NULL
|
||||
AND mod_deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
user_token.as_str(),
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(maybe_record) => Ok(maybe_record),
|
||||
Err(err) => {
|
||||
warn!("get_username_by_user_token query error: {:?}", err);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod get_user_token_by_username;
|
||||
pub mod get_username_by_user_token;
|
||||
pub mod lookup_user_for_login_by_email;
|
||||
pub mod lookup_user_for_login_by_email_with_transactor;
|
||||
pub mod lookup_user_for_login_by_username;
|
||||
|
||||
+34
-2
@@ -1,12 +1,44 @@
|
||||
use crate::queries::wallets::create_new_wallet_for_owner_user::create_new_wallet_for_owner_user;
|
||||
use crate::queries::wallet_ledger_entries::internal_insert_wallet_created_ledger_entry::internal_insert_wallet_created_ledger_entry;
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use errors::AnyhowResult;
|
||||
use log::error;
|
||||
use sqlx::MySql;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallets::WalletToken;
|
||||
|
||||
const ARTCRAFT_NAMESPACE: &str = PaymentsNamespace::Artcraft.to_str();
|
||||
|
||||
pub async fn create_new_artcraft_wallet_for_owner_user(
|
||||
user_token: &UserToken,
|
||||
transaction: &mut sqlx::Transaction<'_, MySql>,
|
||||
) -> Result<WalletToken, sqlx::Error> {
|
||||
create_new_wallet_for_owner_user(user_token, PaymentsNamespace::Artcraft, transaction).await
|
||||
let token = WalletToken::generate();
|
||||
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO wallets
|
||||
SET
|
||||
token = ?,
|
||||
wallet_namespace = ?,
|
||||
|
||||
owner_user_token = ?,
|
||||
|
||||
banked_credits = 0,
|
||||
monthly_credits = 0
|
||||
"#,
|
||||
token.as_str(),
|
||||
ARTCRAFT_NAMESPACE,
|
||||
user_token.as_str()
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Error while inserting wallet: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
internal_insert_wallet_created_ledger_entry(&token, transaction).await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
use crate::queries::wallet_ledger_entries::internal_insert_wallet_created_ledger_entry::internal_insert_wallet_created_ledger_entry;
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use log::error;
|
||||
use sqlx::MySql;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallets::WalletToken;
|
||||
|
||||
pub async fn create_new_wallet_for_owner_user(
|
||||
user_token: &UserToken,
|
||||
namespace: PaymentsNamespace,
|
||||
transaction: &mut sqlx::Transaction<'_, MySql>,
|
||||
) -> Result<WalletToken, sqlx::Error> {
|
||||
let token = WalletToken::generate();
|
||||
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO wallets
|
||||
SET
|
||||
token = ?,
|
||||
wallet_namespace = ?,
|
||||
|
||||
owner_user_token = ?,
|
||||
|
||||
banked_credits = 0,
|
||||
monthly_credits = 0
|
||||
"#,
|
||||
token.as_str(),
|
||||
namespace.to_str(),
|
||||
user_token.as_str()
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Error while inserting wallet: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
internal_insert_wallet_created_ledger_entry(&token, transaction).await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pub (crate) mod internal_select_wallet_balance_for_update;
|
||||
pub mod add_durable_banked_balance_to_wallet;
|
||||
pub mod create_new_artcraft_wallet_for_owner_user;
|
||||
pub mod create_new_wallet_for_owner_user;
|
||||
pub mod find_primary_wallet_for_owner;
|
||||
pub mod find_primary_wallet_token_for_owner;
|
||||
pub mod refill_monthly_credits_balance_on_wallet;
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::connection::TaskDbConnection;
|
||||
use crate::error::SqliteTasksError;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_failure_type::TaskFailureType;
|
||||
use enums::tauri::tasks::task_media_file_class::TaskMediaFileClass;
|
||||
use enums::tauri::tasks::task_model_type::TaskModelType;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
@@ -31,8 +30,6 @@ pub struct TaskItem {
|
||||
pub on_complete_batch_token: Option<BatchGenerationToken>,
|
||||
pub on_complete_primary_media_file_cdn_url: Option<String>,
|
||||
pub on_complete_primary_media_file_thumbnail_url_template: Option<String>,
|
||||
pub on_failure_type: Option<TaskFailureType>,
|
||||
pub on_failure_message: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
@@ -59,8 +56,6 @@ pub async fn list_tasks_for_frontend(
|
||||
on_complete_batch_token,
|
||||
on_complete_primary_media_file_cdn_url,
|
||||
on_complete_primary_media_file_thumbnail_url_template,
|
||||
on_failure_type,
|
||||
on_failure_message,
|
||||
created_at as "created_at: DateTime<Utc>",
|
||||
updated_at as "updated_at: DateTime<Utc>",
|
||||
completed_at as "completed_at: DateTime<Utc>"
|
||||
@@ -97,10 +92,6 @@ pub async fn list_tasks_for_frontend(
|
||||
on_complete_batch_token: raw.on_complete_batch_token.map(|t| BatchGenerationToken::new_from_str(&t)),
|
||||
on_complete_primary_media_file_cdn_url: raw.on_complete_primary_media_file_cdn_url,
|
||||
on_complete_primary_media_file_thumbnail_url_template: raw.on_complete_primary_media_file_thumbnail_url_template,
|
||||
on_failure_type: raw.on_failure_type
|
||||
.map(|t| TaskFailureType::from_str(&t))
|
||||
.transpose()?,
|
||||
on_failure_message: raw.on_failure_message,
|
||||
created_at: raw.created_at,
|
||||
updated_at: raw.updated_at,
|
||||
completed_at: raw.completed_at,
|
||||
@@ -127,8 +118,6 @@ struct TaskItemRaw {
|
||||
on_complete_batch_token: Option<String>,
|
||||
on_complete_primary_media_file_cdn_url: Option<String>,
|
||||
on_complete_primary_media_file_thumbnail_url_template: Option<String>,
|
||||
on_failure_type: Option<String>,
|
||||
on_failure_message: Option<String>,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
completed_at: Option<DateTime<Utc>>,
|
||||
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
use crate::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// A forward-compatible version of `FrontendFailureCategory` for API clients.
|
||||
///
|
||||
/// Contains all known variants plus an `Unknown(String)` catch-all so that
|
||||
/// newer server-side values never cause deserialization failures on the client.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FrontendFailureCategoryForApiClients {
|
||||
FaceNotDetected,
|
||||
KeepAliveElapsed,
|
||||
NotYetImplemented,
|
||||
RetryableWorkerError,
|
||||
ModelRulesViolation,
|
||||
RuleBansUserImage,
|
||||
RuleBansUserImageWithFaces,
|
||||
RuleBansUserTextPrompt,
|
||||
RuleBansUserContent,
|
||||
RuleBansGeneratedVideo,
|
||||
RuleBansGeneratedAudio,
|
||||
RuleBansGeneratedContent,
|
||||
GenerationFailed,
|
||||
|
||||
/// Catch-all for values the client doesn't yet know about.
|
||||
/// The contained string is the raw serialized value from the server.
|
||||
#[serde(untagged)]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl FrontendFailureCategoryForApiClients {
|
||||
pub fn from_db_enum(value: FrontendFailureCategory) -> Self {
|
||||
match value {
|
||||
FrontendFailureCategory::FaceNotDetected => Self::FaceNotDetected,
|
||||
FrontendFailureCategory::KeepAliveElapsed => Self::KeepAliveElapsed,
|
||||
FrontendFailureCategory::NotYetImplemented => Self::NotYetImplemented,
|
||||
FrontendFailureCategory::RetryableWorkerError => Self::RetryableWorkerError,
|
||||
FrontendFailureCategory::ModelRulesViolation => Self::ModelRulesViolation,
|
||||
FrontendFailureCategory::RuleBansUserImage => Self::RuleBansUserImage,
|
||||
FrontendFailureCategory::RuleBansUserImageWithFaces => Self::RuleBansUserImageWithFaces,
|
||||
FrontendFailureCategory::RuleBansUserTextPrompt => Self::RuleBansUserTextPrompt,
|
||||
FrontendFailureCategory::RuleBansUserContent => Self::RuleBansUserContent,
|
||||
FrontendFailureCategory::RuleBansGeneratedVideo => Self::RuleBansGeneratedVideo,
|
||||
FrontendFailureCategory::RuleBansGeneratedAudio => Self::RuleBansGeneratedAudio,
|
||||
FrontendFailureCategory::RuleBansGeneratedContent => Self::RuleBansGeneratedContent,
|
||||
FrontendFailureCategory::GenerationFailed => Self::GenerationFailed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_known_variants() {
|
||||
let cases = vec![
|
||||
("\"face_not_detected\"", FrontendFailureCategoryForApiClients::FaceNotDetected),
|
||||
("\"keep_alive_elapsed\"", FrontendFailureCategoryForApiClients::KeepAliveElapsed),
|
||||
("\"not_yet_implemented\"", FrontendFailureCategoryForApiClients::NotYetImplemented),
|
||||
("\"retryable_worker_error\"", FrontendFailureCategoryForApiClients::RetryableWorkerError),
|
||||
("\"model_rules_violation\"", FrontendFailureCategoryForApiClients::ModelRulesViolation),
|
||||
("\"rule_bans_user_image\"", FrontendFailureCategoryForApiClients::RuleBansUserImage),
|
||||
("\"rule_bans_user_image_with_faces\"", FrontendFailureCategoryForApiClients::RuleBansUserImageWithFaces),
|
||||
("\"rule_bans_user_text_prompt\"", FrontendFailureCategoryForApiClients::RuleBansUserTextPrompt),
|
||||
("\"rule_bans_user_content\"", FrontendFailureCategoryForApiClients::RuleBansUserContent),
|
||||
("\"rule_bans_generated_video\"", FrontendFailureCategoryForApiClients::RuleBansGeneratedVideo),
|
||||
("\"rule_bans_generated_audio\"", FrontendFailureCategoryForApiClients::RuleBansGeneratedAudio),
|
||||
("\"rule_bans_generated_content\"", FrontendFailureCategoryForApiClients::RuleBansGeneratedContent),
|
||||
("\"generation_failed\"", FrontendFailureCategoryForApiClients::GenerationFailed),
|
||||
];
|
||||
|
||||
for (json, expected) in cases {
|
||||
let parsed: FrontendFailureCategoryForApiClients = serde_json::from_str(json)
|
||||
.unwrap_or_else(|e| panic!("failed to parse {}: {}", json, e));
|
||||
assert_eq!(parsed, expected, "mismatch for {}", json);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_unknown_variant() {
|
||||
let json = "\"some_future_category\"";
|
||||
let parsed: FrontendFailureCategoryForApiClients = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(parsed, FrontendFailureCategoryForApiClients::Unknown("some_future_category".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_another_unknown_variant() {
|
||||
let json = "\"totally_new_thing\"";
|
||||
let parsed: FrontendFailureCategoryForApiClients = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(parsed, FrontendFailureCategoryForApiClients::Unknown("totally_new_thing".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_known_variants() {
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForApiClients::FaceNotDetected).unwrap(), "\"face_not_detected\"");
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForApiClients::ModelRulesViolation).unwrap(), "\"model_rules_violation\"");
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForApiClients::GenerationFailed).unwrap(), "\"generation_failed\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_unknown_variant() {
|
||||
let unknown = FrontendFailureCategoryForApiClients::Unknown("some_future_category".to_string());
|
||||
assert_eq!(serde_json::to_string(&unknown).unwrap(), "\"some_future_category\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_db_enum() {
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForApiClients::from_db_enum(FrontendFailureCategory::FaceNotDetected),
|
||||
FrontendFailureCategoryForApiClients::FaceNotDetected
|
||||
);
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForApiClients::from_db_enum(FrontendFailureCategory::GenerationFailed),
|
||||
FrontendFailureCategoryForApiClients::GenerationFailed
|
||||
);
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForApiClients::from_db_enum(FrontendFailureCategory::RuleBansGeneratedContent),
|
||||
FrontendFailureCategoryForApiClients::RuleBansGeneratedContent
|
||||
);
|
||||
}
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
use crate::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// A restricted subset of `FrontendFailureCategory` that only includes values
|
||||
/// known to old deployed clients. New failure categories are omitted so that
|
||||
/// old clients never receive an enum value they cannot deserialize.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, ToSchema)]
|
||||
pub enum FrontendFailureCategoryForOldClients {
|
||||
#[serde(rename = "face_not_detected")]
|
||||
FaceNotDetected,
|
||||
|
||||
#[serde(rename = "keep_alive_elapsed")]
|
||||
KeepAliveElapsed,
|
||||
|
||||
#[serde(rename = "not_yet_implemented")]
|
||||
NotYetImplemented,
|
||||
|
||||
#[serde(rename = "retryable_worker_error")]
|
||||
RetryableWorkerError,
|
||||
}
|
||||
|
||||
impl FrontendFailureCategoryForOldClients {
|
||||
/// Attempts to convert a database-level `FrontendFailureCategory` into the
|
||||
/// restricted old-client enum. Returns `None` for categories that old
|
||||
/// clients do not understand.
|
||||
pub fn try_from_db_enum(value: FrontendFailureCategory) -> Option<Self> {
|
||||
match value {
|
||||
FrontendFailureCategory::FaceNotDetected => Some(Self::FaceNotDetected),
|
||||
FrontendFailureCategory::KeepAliveElapsed => Some(Self::KeepAliveElapsed),
|
||||
FrontendFailureCategory::NotYetImplemented => Some(Self::NotYetImplemented),
|
||||
FrontendFailureCategory::RetryableWorkerError => Some(Self::RetryableWorkerError),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_known_variants_convert() {
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::FaceNotDetected),
|
||||
Some(FrontendFailureCategoryForOldClients::FaceNotDetected)
|
||||
);
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::KeepAliveElapsed),
|
||||
Some(FrontendFailureCategoryForOldClients::KeepAliveElapsed)
|
||||
);
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::NotYetImplemented),
|
||||
Some(FrontendFailureCategoryForOldClients::NotYetImplemented)
|
||||
);
|
||||
assert_eq!(
|
||||
FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RetryableWorkerError),
|
||||
Some(FrontendFailureCategoryForOldClients::RetryableWorkerError)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_variants_return_none() {
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::ModelRulesViolation), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansUserImage), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansUserImageWithFaces), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansUserTextPrompt), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansUserContent), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansGeneratedVideo), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansGeneratedAudio), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::RuleBansGeneratedContent), None);
|
||||
assert_eq!(FrontendFailureCategoryForOldClients::try_from_db_enum(FrontendFailureCategory::GenerationFailed), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForOldClients::FaceNotDetected).unwrap(), "\"face_not_detected\"");
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForOldClients::KeepAliveElapsed).unwrap(), "\"keep_alive_elapsed\"");
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForOldClients::NotYetImplemented).unwrap(), "\"not_yet_implemented\"");
|
||||
assert_eq!(serde_json::to_string(&FrontendFailureCategoryForOldClients::RetryableWorkerError).unwrap(), "\"retryable_worker_error\"");
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod frontend_failure_category_for_old_clients;
|
||||
pub mod frontend_failure_category_for_api_clients;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod generic_inference_jobs;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod by_table;
|
||||
+1
-108
@@ -47,56 +47,6 @@ pub enum FrontendFailureCategory {
|
||||
/// Eg. Seedance 2 will report: "your input text violates platform rules. please modify and try again"
|
||||
#[serde(rename = "model_rules_violation")]
|
||||
ModelRulesViolation,
|
||||
|
||||
/// Model content violation
|
||||
/// e.g. "Your uploaded image violates platform rules. Please modify and try again." (seedance2pro)
|
||||
/// Model content rules prohibit the uploaded image.
|
||||
#[serde(rename = "rule_bans_user_image")]
|
||||
RuleBansUserImage,
|
||||
|
||||
/// Model content violation
|
||||
/// Model content rules prohibit user uploaded images containing faces (Seedance 2.0)
|
||||
/// e.g. "The generated video did not pass review. Credits will not be deducted." (seedance2pro)
|
||||
#[serde(rename = "rule_bans_user_image_with_faces")]
|
||||
RuleBansUserImageWithFaces,
|
||||
|
||||
/// Model content violation
|
||||
/// Model content rules prohibit the user's given text prompt (this fails early).
|
||||
/// e.g. "The generated video did not pass review. Credits will not be deducted." (seedance2pro)
|
||||
#[serde(rename = "rule_bans_user_text_prompt")]
|
||||
RuleBansUserTextPrompt,
|
||||
|
||||
/// Model content violation
|
||||
/// Model content rules prohibit user content. (I think this check happens early.)
|
||||
/// e.g. "Content violates platform rules. Please modify and try again." (seedance2pro)
|
||||
#[serde(rename = "rule_bans_user_content")]
|
||||
RuleBansUserContent,
|
||||
|
||||
/// Model content violation
|
||||
/// The video didn't pass checks after it finished generation (this fails at the very end of the generation).
|
||||
/// e.g. "The generated video did not pass review. Credits will not be deducted." (seedance2pro)
|
||||
#[serde(rename = "rule_bans_generated_video")]
|
||||
RuleBansGeneratedVideo,
|
||||
|
||||
/// Model content violation
|
||||
/// The audio (even in video!) didn't pass checks after it finished generation (this fails at the very end of the generation).
|
||||
/// e.g. "The generated audio violates platform rules. Please adjust your prompt or images and try again." (seedance2pro)
|
||||
#[serde(rename = "rule_bans_generated_audio")]
|
||||
RuleBansGeneratedAudio,
|
||||
|
||||
/// Model content violation
|
||||
/// The content didn't pass checks after it finished generation (this fails at the very end of the generation).
|
||||
/// e.g. "The generated content violates platform rules. Please adjust your prompt or images and try again." (seedance2pro)
|
||||
#[serde(rename = "rule_bans_generated_content")]
|
||||
RuleBansGeneratedContent,
|
||||
|
||||
/// Generation failed (no reason)
|
||||
/// Unspecified failure reason
|
||||
/// Various example failures:
|
||||
/// - "The generated video did not pass review. Credits will not be deducted." (seedance2pro)
|
||||
/// - "Server error. Please try again later." (seedance2pro)
|
||||
#[serde(rename = "generation_failed")]
|
||||
GenerationFailed,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
@@ -112,14 +62,6 @@ impl FrontendFailureCategory {
|
||||
Self::NotYetImplemented => "not_yet_implemented",
|
||||
Self::RetryableWorkerError => "retryable_worker_error",
|
||||
Self::ModelRulesViolation => "model_rules_violation",
|
||||
Self::RuleBansUserImage => "rule_bans_user_image",
|
||||
Self::RuleBansUserImageWithFaces => "rule_bans_user_image_with_faces",
|
||||
Self::RuleBansUserTextPrompt => "rule_bans_user_text_prompt",
|
||||
Self::RuleBansUserContent => "rule_bans_user_content",
|
||||
Self::RuleBansGeneratedVideo => "rule_bans_generated_video",
|
||||
Self::RuleBansGeneratedAudio => "rule_bans_generated_audio",
|
||||
Self::RuleBansGeneratedContent => "rule_bans_generated_content",
|
||||
Self::GenerationFailed => "generation_failed",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,14 +72,6 @@ impl FrontendFailureCategory {
|
||||
"not_yet_implemented" => Ok(Self::NotYetImplemented),
|
||||
"retryable_worker_error" => Ok(Self::RetryableWorkerError),
|
||||
"model_rules_violation" => Ok(Self::ModelRulesViolation),
|
||||
"rule_bans_user_image" => Ok(Self::RuleBansUserImage),
|
||||
"rule_bans_user_image_with_faces" => Ok(Self::RuleBansUserImageWithFaces),
|
||||
"rule_bans_user_text_prompt" => Ok(Self::RuleBansUserTextPrompt),
|
||||
"rule_bans_user_content" => Ok(Self::RuleBansUserContent),
|
||||
"rule_bans_generated_video" => Ok(Self::RuleBansGeneratedVideo),
|
||||
"rule_bans_generated_audio" => Ok(Self::RuleBansGeneratedAudio),
|
||||
"rule_bans_generated_content" => Ok(Self::RuleBansGeneratedContent),
|
||||
"generation_failed" => Ok(Self::GenerationFailed),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -151,14 +85,6 @@ impl FrontendFailureCategory {
|
||||
Self::NotYetImplemented,
|
||||
Self::RetryableWorkerError,
|
||||
Self::ModelRulesViolation,
|
||||
Self::RuleBansUserImage,
|
||||
Self::RuleBansUserImageWithFaces,
|
||||
Self::RuleBansUserTextPrompt,
|
||||
Self::RuleBansUserContent,
|
||||
Self::RuleBansGeneratedVideo,
|
||||
Self::RuleBansGeneratedAudio,
|
||||
Self::RuleBansGeneratedContent,
|
||||
Self::GenerationFailed,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -178,14 +104,6 @@ mod tests {
|
||||
assert_serialization(FrontendFailureCategory::NotYetImplemented, "not_yet_implemented");
|
||||
assert_serialization(FrontendFailureCategory::RetryableWorkerError, "retryable_worker_error");
|
||||
assert_serialization(FrontendFailureCategory::ModelRulesViolation, "model_rules_violation");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansUserImage, "rule_bans_user_image");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansUserImageWithFaces, "rule_bans_user_image_with_faces");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansUserTextPrompt, "rule_bans_user_text_prompt");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansUserContent, "rule_bans_user_content");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansGeneratedVideo, "rule_bans_generated_video");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansGeneratedAudio, "rule_bans_generated_audio");
|
||||
assert_serialization(FrontendFailureCategory::RuleBansGeneratedContent, "rule_bans_generated_content");
|
||||
assert_serialization(FrontendFailureCategory::GenerationFailed, "generation_failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -195,14 +113,6 @@ mod tests {
|
||||
assert_eq!(FrontendFailureCategory::NotYetImplemented.to_str(), "not_yet_implemented");
|
||||
assert_eq!(FrontendFailureCategory::RetryableWorkerError.to_str(), "retryable_worker_error");
|
||||
assert_eq!(FrontendFailureCategory::ModelRulesViolation.to_str(), "model_rules_violation");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansUserImage.to_str(), "rule_bans_user_image");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansUserImageWithFaces.to_str(), "rule_bans_user_image_with_faces");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansUserTextPrompt.to_str(), "rule_bans_user_text_prompt");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansUserContent.to_str(), "rule_bans_user_content");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansGeneratedVideo.to_str(), "rule_bans_generated_video");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansGeneratedAudio.to_str(), "rule_bans_generated_audio");
|
||||
assert_eq!(FrontendFailureCategory::RuleBansGeneratedContent.to_str(), "rule_bans_generated_content");
|
||||
assert_eq!(FrontendFailureCategory::GenerationFailed.to_str(), "generation_failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -212,34 +122,17 @@ mod tests {
|
||||
assert_eq!(FrontendFailureCategory::from_str("not_yet_implemented").unwrap(), FrontendFailureCategory::NotYetImplemented);
|
||||
assert_eq!(FrontendFailureCategory::from_str("retryable_worker_error").unwrap(), FrontendFailureCategory::RetryableWorkerError);
|
||||
assert_eq!(FrontendFailureCategory::from_str("model_rules_violation").unwrap(), FrontendFailureCategory::ModelRulesViolation);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_user_image").unwrap(), FrontendFailureCategory::RuleBansUserImage);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_user_image_with_faces").unwrap(), FrontendFailureCategory::RuleBansUserImageWithFaces);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_user_text_prompt").unwrap(), FrontendFailureCategory::RuleBansUserTextPrompt);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_user_content").unwrap(), FrontendFailureCategory::RuleBansUserContent);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_generated_video").unwrap(), FrontendFailureCategory::RuleBansGeneratedVideo);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_generated_audio").unwrap(), FrontendFailureCategory::RuleBansGeneratedAudio);
|
||||
assert_eq!(FrontendFailureCategory::from_str("rule_bans_generated_content").unwrap(), FrontendFailureCategory::RuleBansGeneratedContent);
|
||||
assert_eq!(FrontendFailureCategory::from_str("generation_failed").unwrap(), FrontendFailureCategory::GenerationFailed);
|
||||
assert_eq!(FrontendFailureCategory::from_str("invalid_value").is_err(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = FrontendFailureCategory::all_variants();
|
||||
assert_eq!(variants.len(), 13);
|
||||
assert_eq!(variants.len(), 5);
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::FaceNotDetected));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::KeepAliveElapsed));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::NotYetImplemented));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RetryableWorkerError));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::ModelRulesViolation));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansUserImage));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansUserImageWithFaces));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansUserTextPrompt));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansUserContent));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansGeneratedVideo));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansGeneratedAudio));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::RuleBansGeneratedContent));
|
||||
assert_eq!(variants.pop_first(), Some(FrontendFailureCategory::GenerationFailed));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,3 @@ pub mod error;
|
||||
pub mod no_table;
|
||||
pub mod tauri;
|
||||
pub mod traits;
|
||||
pub mod api_safe;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod task_failure_type;
|
||||
pub mod task_media_file_class;
|
||||
pub mod task_model_type;
|
||||
pub mod task_status;
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use crate::error::enum_error::EnumError;
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Failure type for tasks in the Tauri desktop app.
|
||||
///
|
||||
/// Mirrors the relevant variants from `FrontendFailureCategory` so the desktop
|
||||
/// client can display localized failure information without depending / breaking on the
|
||||
/// server-side enum directly.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TaskFailureType {
|
||||
/// Catch-all for unknown failures.
|
||||
Unknown,
|
||||
|
||||
RuleBansUserImage,
|
||||
RuleBansUserImageWithFaces,
|
||||
RuleBansUserTextPrompt,
|
||||
RuleBansUserContent,
|
||||
|
||||
RuleBansGeneratedVideo,
|
||||
RuleBansGeneratedAudio,
|
||||
RuleBansGeneratedContent,
|
||||
|
||||
/// No reason given for generation failure, but this matches what we were told.
|
||||
GenerationFailed,
|
||||
}
|
||||
|
||||
impl_enum_display_and_debug_using_to_str!(TaskFailureType);
|
||||
|
||||
impl TaskFailureType {
|
||||
|
||||
/// Convert the web API's `FrontendFailureCategory` to a Tauri-facing type, if there is a matching variant.
|
||||
/// If there isn't a matching variant, return `Unknown`.
|
||||
pub fn from_frontend_failure_category(category: FrontendFailureCategory) -> Self {
|
||||
match category {
|
||||
FrontendFailureCategory::ModelRulesViolation => Self::RuleBansUserContent, // NB: This is a legacy enum value.
|
||||
FrontendFailureCategory::RuleBansUserImage => Self::RuleBansUserImage,
|
||||
FrontendFailureCategory::RuleBansUserImageWithFaces => Self::RuleBansUserImageWithFaces,
|
||||
FrontendFailureCategory::RuleBansUserTextPrompt => Self::RuleBansUserTextPrompt,
|
||||
FrontendFailureCategory::RuleBansUserContent => Self::RuleBansUserContent,
|
||||
FrontendFailureCategory::RuleBansGeneratedVideo => Self::RuleBansGeneratedVideo,
|
||||
FrontendFailureCategory::RuleBansGeneratedAudio => Self::RuleBansGeneratedAudio,
|
||||
FrontendFailureCategory::RuleBansGeneratedContent => Self::RuleBansGeneratedContent,
|
||||
FrontendFailureCategory::GenerationFailed => Self::GenerationFailed,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Unknown => "unknown",
|
||||
Self::RuleBansUserImage => "rule_bans_user_image",
|
||||
Self::RuleBansUserImageWithFaces => "rule_bans_user_image_with_faces",
|
||||
Self::RuleBansUserTextPrompt => "rule_bans_user_text_prompt",
|
||||
Self::RuleBansUserContent => "rule_bans_user_content",
|
||||
Self::RuleBansGeneratedVideo => "rule_bans_generated_video",
|
||||
Self::RuleBansGeneratedAudio => "rule_bans_generated_audio",
|
||||
Self::RuleBansGeneratedContent => "rule_bans_generated_content",
|
||||
Self::GenerationFailed => "generation_failed",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, EnumError> {
|
||||
match value {
|
||||
"unknown" => Ok(Self::Unknown),
|
||||
"rule_bans_user_image" => Ok(Self::RuleBansUserImage),
|
||||
"rule_bans_user_image_with_faces" => Ok(Self::RuleBansUserImageWithFaces),
|
||||
"rule_bans_user_text_prompt" => Ok(Self::RuleBansUserTextPrompt),
|
||||
"rule_bans_user_content" => Ok(Self::RuleBansUserContent),
|
||||
"rule_bans_generated_video" => Ok(Self::RuleBansGeneratedVideo),
|
||||
"rule_bans_generated_audio" => Ok(Self::RuleBansGeneratedAudio),
|
||||
"rule_bans_generated_content" => Ok(Self::RuleBansGeneratedContent),
|
||||
"generation_failed" => Ok(Self::GenerationFailed),
|
||||
_ => Err(EnumError::CouldNotConvertFromString(value.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants() -> BTreeSet<Self> {
|
||||
BTreeSet::from([
|
||||
Self::Unknown,
|
||||
Self::RuleBansUserImage,
|
||||
Self::RuleBansUserImageWithFaces,
|
||||
Self::RuleBansUserTextPrompt,
|
||||
Self::RuleBansUserContent,
|
||||
Self::RuleBansGeneratedVideo,
|
||||
Self::RuleBansGeneratedAudio,
|
||||
Self::RuleBansGeneratedContent,
|
||||
Self::GenerationFailed,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tauri::tasks::task_failure_type::TaskFailureType;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
mod explicit_checks {
|
||||
use super::*;
|
||||
use crate::error::enum_error::EnumError;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(TaskFailureType::Unknown, "unknown");
|
||||
assert_serialization(TaskFailureType::RuleBansUserImage, "rule_bans_user_image");
|
||||
assert_serialization(TaskFailureType::RuleBansUserImageWithFaces, "rule_bans_user_image_with_faces");
|
||||
assert_serialization(TaskFailureType::RuleBansUserTextPrompt, "rule_bans_user_text_prompt");
|
||||
assert_serialization(TaskFailureType::RuleBansUserContent, "rule_bans_user_content");
|
||||
assert_serialization(TaskFailureType::RuleBansGeneratedVideo, "rule_bans_generated_video");
|
||||
assert_serialization(TaskFailureType::RuleBansGeneratedAudio, "rule_bans_generated_audio");
|
||||
assert_serialization(TaskFailureType::RuleBansGeneratedContent, "rule_bans_generated_content");
|
||||
assert_serialization(TaskFailureType::GenerationFailed, "generation_failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(TaskFailureType::Unknown.to_str(), "unknown");
|
||||
assert_eq!(TaskFailureType::RuleBansUserImage.to_str(), "rule_bans_user_image");
|
||||
assert_eq!(TaskFailureType::RuleBansUserImageWithFaces.to_str(), "rule_bans_user_image_with_faces");
|
||||
assert_eq!(TaskFailureType::RuleBansUserTextPrompt.to_str(), "rule_bans_user_text_prompt");
|
||||
assert_eq!(TaskFailureType::RuleBansUserContent.to_str(), "rule_bans_user_content");
|
||||
assert_eq!(TaskFailureType::RuleBansGeneratedVideo.to_str(), "rule_bans_generated_video");
|
||||
assert_eq!(TaskFailureType::RuleBansGeneratedAudio.to_str(), "rule_bans_generated_audio");
|
||||
assert_eq!(TaskFailureType::RuleBansGeneratedContent.to_str(), "rule_bans_generated_content");
|
||||
assert_eq!(TaskFailureType::GenerationFailed.to_str(), "generation_failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(TaskFailureType::from_str("unknown").unwrap(), TaskFailureType::Unknown);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_user_image").unwrap(), TaskFailureType::RuleBansUserImage);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_user_image_with_faces").unwrap(), TaskFailureType::RuleBansUserImageWithFaces);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_user_text_prompt").unwrap(), TaskFailureType::RuleBansUserTextPrompt);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_user_content").unwrap(), TaskFailureType::RuleBansUserContent);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_generated_video").unwrap(), TaskFailureType::RuleBansGeneratedVideo);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_generated_audio").unwrap(), TaskFailureType::RuleBansGeneratedAudio);
|
||||
assert_eq!(TaskFailureType::from_str("rule_bans_generated_content").unwrap(), TaskFailureType::RuleBansGeneratedContent);
|
||||
assert_eq!(TaskFailureType::from_str("generation_failed").unwrap(), TaskFailureType::GenerationFailed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_err() {
|
||||
let result = TaskFailureType::from_str("asdf");
|
||||
assert!(result.is_err());
|
||||
if let Err(EnumError::CouldNotConvertFromString(value)) = result {
|
||||
assert_eq!(value, "asdf");
|
||||
} else {
|
||||
panic!("Expected EnumError::CouldNotConvertFromString");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TaskFailureType::all_variants();
|
||||
assert_eq!(variants.len(), 9);
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::Unknown));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansUserImage));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansUserImageWithFaces));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansUserTextPrompt));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansUserContent));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansGeneratedVideo));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansGeneratedAudio));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::RuleBansGeneratedContent));
|
||||
assert_eq!(variants.pop_first(), Some(TaskFailureType::GenerationFailed));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
mod mechanical_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn variant_length() {
|
||||
use strum::IntoEnumIterator;
|
||||
assert_eq!(TaskFailureType::all_variants().len(), TaskFailureType::iter().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
for variant in TaskFailureType::all_variants() {
|
||||
assert_eq!(variant, TaskFailureType::from_str(variant.to_str()).unwrap());
|
||||
assert_eq!(variant, TaskFailureType::from_str(&format!("{}", variant)).unwrap());
|
||||
assert_eq!(variant, TaskFailureType::from_str(&format!("{:?}", variant)).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use enums::by_table::generic_inference_jobs::frontend_failure_category::Frontend
|
||||
use mysql_queries::queries::generic_inference::job::mark_job_failed_by_token::{mark_job_failed_by_token, MarkJobFailedByTokenArgs};
|
||||
use mysql_queries::queries::generic_inference::seedance2pro::list_pending_seedance2pro_jobs::PendingSeedance2ProJob;
|
||||
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
|
||||
use seedance2pro::requests::poll_orders::failure_type::FailureType;
|
||||
use seedance2pro::requests::poll_orders::poll_orders::OrderStatus;
|
||||
|
||||
use crate::job_dependencies::JobDependencies;
|
||||
@@ -16,8 +15,7 @@ pub async fn process_failed_job(
|
||||
) {
|
||||
let reason = order
|
||||
.fail_reason
|
||||
.as_ref()
|
||||
.map(|fr| fr.reason.as_str())
|
||||
.as_deref()
|
||||
.unwrap_or("unknown failure reason");
|
||||
|
||||
// --- Step 1: Attempt the refund before touching the job status. ---
|
||||
@@ -91,19 +89,17 @@ pub async fn process_failed_job(
|
||||
|
||||
// --- Step 2: Mark the job record as failed. ---
|
||||
|
||||
let frontend_failure_category = order.fail_reason.as_ref().map(|fr| {
|
||||
match fr.failure_type {
|
||||
FailureType::RuleBansUserImage => FrontendFailureCategory::RuleBansUserImage,
|
||||
FailureType::RuleBansUserImageWithFaces => FrontendFailureCategory::RuleBansUserImageWithFaces,
|
||||
FailureType::RuleBansUserTextPrompt => FrontendFailureCategory::RuleBansUserTextPrompt,
|
||||
FailureType::RuleBansUserContent => FrontendFailureCategory::RuleBansUserContent,
|
||||
FailureType::RuleBansGeneratedVideo => FrontendFailureCategory::RuleBansGeneratedVideo,
|
||||
FailureType::RuleBansGeneratedAudio => FrontendFailureCategory::RuleBansGeneratedAudio,
|
||||
FailureType::RuleBansGeneratedContent => FrontendFailureCategory::RuleBansGeneratedContent,
|
||||
FailureType::GenerationFailed => FrontendFailureCategory::GenerationFailed,
|
||||
FailureType::OtherUnknownReason => FrontendFailureCategory::GenerationFailed,
|
||||
}
|
||||
});
|
||||
let reason_lower = reason.to_lowercase();
|
||||
|
||||
let platform_rules_violation = reason_lower.contains("violates") ||
|
||||
reason_lower.contains("platform rules") ||
|
||||
reason_lower.contains("please modify");
|
||||
|
||||
let frontend_failure_category = if platform_rules_violation {
|
||||
Some(FrontendFailureCategory::ModelRulesViolation)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
warn!(
|
||||
"Order {} failed: {}. Marking job {} failed.",
|
||||
|
||||
@@ -281,7 +281,6 @@ use artcraft_api_defs::moderation::wallet_ledger_entries::list_wallet_ledger_ent
|
||||
use artcraft_api_defs::moderation::wallet_ledger_entries::moderator_get_wallet_ledger_entry::*;
|
||||
use artcraft_api_defs::moderation::wallets::list_user_wallets::*;
|
||||
use artcraft_api_defs::moderation::wallets::moderator_add_banked_balance_to_wallet::*;
|
||||
use artcraft_api_defs::moderation::wallets::moderator_create_wallet_for_user::*;
|
||||
use artcraft_api_defs::moderation::wallets::moderator_get_wallet::*;
|
||||
use artcraft_api_defs::web_referrals::log_web_referral::*;
|
||||
use crate::http_server::endpoints::web_referrals::log_web_referral_handler::*;
|
||||
@@ -469,7 +468,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
crate::http_server::endpoints::moderation::wallet_ledger_entries::moderator_get_wallet_ledger_entry_handler::moderator_get_wallet_ledger_entry_handler,
|
||||
crate::http_server::endpoints::moderation::wallets::list_user_wallets_handler::list_user_wallets_handler,
|
||||
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,
|
||||
// Credits
|
||||
crate::http_server::endpoints::credits::get_session_credits_handler::get_session_credits_handler,
|
||||
@@ -668,8 +666,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
ModeratorAddBankedBalanceToWalletPathInfo,
|
||||
ModeratorAddBankedBalanceToWalletRequest,
|
||||
ModeratorAddBankedBalanceToWalletResponse,
|
||||
ModeratorCreateWalletForUserRequest,
|
||||
ModeratorCreateWalletForUserResponse,
|
||||
ModeratorGetWalletPathInfo,
|
||||
ModeratorGetWalletResponse,
|
||||
ModeratorGetWalletDetails,
|
||||
|
||||
+8
-21
@@ -1,7 +1,5 @@
|
||||
use crate::http_server::common_responses::media::cdn_link;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use server_environment::ServerEnvironment;
|
||||
use url::Url;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
@@ -26,27 +24,24 @@ impl CoverImageLinks {
|
||||
|
||||
pub fn from_media_path(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
bucket_path: &MediaFileBucketPath,
|
||||
) -> Option<Self> {
|
||||
let rooted_path = bucket_path.get_full_object_path_str();
|
||||
Self::from_rooted_path(domain, server_environment, rooted_path)
|
||||
Self::from_rooted_path(domain, rooted_path)
|
||||
}
|
||||
|
||||
pub fn from_maybe_media_path(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
bucket_path: Option<&MediaFileBucketPath>,
|
||||
) -> Option<Self> {
|
||||
match bucket_path {
|
||||
None => None,
|
||||
Some(bucket_path) => Self::from_media_path(domain, server_environment, bucket_path),
|
||||
Some(bucket_path) => Self::from_media_path(domain, bucket_path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parts(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
maybe_cover_image_public_bucket_extension: Option<&str>,
|
||||
@@ -60,12 +55,11 @@ impl CoverImageLinks {
|
||||
maybe_cover_image_public_bucket_prefix,
|
||||
maybe_cover_image_public_bucket_extension
|
||||
);
|
||||
Self::from_media_path(domain, server_environment, &bucket_path)
|
||||
Self::from_media_path(domain, &bucket_path)
|
||||
}
|
||||
|
||||
pub fn from_rooted_path(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
rooted_path: &str,
|
||||
) -> Option<Self> {
|
||||
if !rooted_path.ends_with(".jpg")
|
||||
@@ -74,24 +68,17 @@ impl CoverImageLinks {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut cdn_url = cdn_link::new_cdn_url(domain, server_environment);
|
||||
let mut cdn_url = domain.new_cdn_url();
|
||||
cdn_url.set_path(rooted_path);
|
||||
|
||||
Some(Self {
|
||||
cdn_url,
|
||||
thumbnail_template: thumbnail_template(domain, server_environment, rooted_path),
|
||||
thumbnail_template: thumbnail_template(domain, rooted_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn thumbnail_template(media_domain: MediaDomain, server_environment: ServerEnvironment, rooted_path: &str) -> String {
|
||||
let host = cdn_link::get_cdn_host(media_domain, server_environment);
|
||||
match server_environment {
|
||||
ServerEnvironment::Production => {
|
||||
format!("{host}/cdn-cgi/image/width={{WIDTH}},quality={QUALITY}{rooted_path}")
|
||||
}
|
||||
ServerEnvironment::Development => {
|
||||
format!("{host}{rooted_path}")
|
||||
}
|
||||
}
|
||||
fn thumbnail_template(media_domain: MediaDomain, rooted_path: &str) -> String {
|
||||
let host = media_domain.cdn_url_str();
|
||||
format!("{host}/cdn-cgi/image/width={{WIDTH}},quality={QUALITY}{rooted_path}")
|
||||
}
|
||||
|
||||
+8
-21
@@ -1,8 +1,6 @@
|
||||
use crate::http_server::common_responses::media::cdn_link;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use artcraft_api_defs::common::responses::cover_image_links::CoverImageLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use server_environment::ServerEnvironment;
|
||||
|
||||
// TODO(bt,2024-09-18): Consolidate thumbnail builder code and vars
|
||||
// TODO(bt,2024-09-05): Worth reducing the quality at all?
|
||||
@@ -14,27 +12,24 @@ impl CoverImageLinksBuilder {
|
||||
|
||||
pub fn from_media_path(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
bucket_path: &MediaFileBucketPath,
|
||||
) -> Option<CoverImageLinks> {
|
||||
let rooted_path = bucket_path.get_full_object_path_str();
|
||||
Self::from_rooted_path(domain, server_environment, rooted_path)
|
||||
Self::from_rooted_path(domain, rooted_path)
|
||||
}
|
||||
|
||||
pub fn from_maybe_media_path(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
bucket_path: Option<&MediaFileBucketPath>,
|
||||
) -> Option<CoverImageLinks> {
|
||||
match bucket_path {
|
||||
None => None,
|
||||
Some(bucket_path) => Self::from_media_path(domain, server_environment, bucket_path),
|
||||
Some(bucket_path) => Self::from_media_path(domain, bucket_path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parts(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
maybe_cover_image_public_bucket_extension: Option<&str>,
|
||||
@@ -48,12 +43,11 @@ impl CoverImageLinksBuilder {
|
||||
maybe_cover_image_public_bucket_prefix,
|
||||
maybe_cover_image_public_bucket_extension
|
||||
);
|
||||
Self::from_media_path(domain, server_environment, &bucket_path)
|
||||
Self::from_media_path(domain, &bucket_path)
|
||||
}
|
||||
|
||||
pub fn from_rooted_path(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
rooted_path: &str,
|
||||
) -> Option<CoverImageLinks> {
|
||||
if !rooted_path.ends_with(".jpg")
|
||||
@@ -62,24 +56,17 @@ impl CoverImageLinksBuilder {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut cdn_url = cdn_link::new_cdn_url(domain, server_environment);
|
||||
let mut cdn_url = domain.new_cdn_url();
|
||||
cdn_url.set_path(rooted_path);
|
||||
|
||||
Some(CoverImageLinks {
|
||||
cdn_url,
|
||||
thumbnail_template: thumbnail_template(domain, server_environment, rooted_path),
|
||||
thumbnail_template: thumbnail_template(domain, rooted_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn thumbnail_template(media_domain: MediaDomain, server_environment: ServerEnvironment, rooted_path: &str) -> String {
|
||||
let host = cdn_link::get_cdn_host(media_domain, server_environment);
|
||||
match server_environment {
|
||||
ServerEnvironment::Production => {
|
||||
format!("{host}/cdn-cgi/image/width={{WIDTH}},quality={QUALITY}{rooted_path}")
|
||||
}
|
||||
ServerEnvironment::Development => {
|
||||
format!("{host}{rooted_path}")
|
||||
}
|
||||
}
|
||||
fn thumbnail_template(media_domain: MediaDomain, rooted_path: &str) -> String {
|
||||
let host = media_domain.cdn_url_str();
|
||||
format!("{host}/cdn-cgi/image/width={{WIDTH}},quality={QUALITY}{rooted_path}")
|
||||
}
|
||||
|
||||
+10
-156
@@ -3,11 +3,10 @@ use std::hash::{Hash, Hasher};
|
||||
use url::Url;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::http_server::common_responses::media::cdn_link;
|
||||
use crate::http_server::common_responses::media::cover_image_links::CoverImageLinks;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_from_media_path::bucket_url_from_media_path;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use server_environment::ServerEnvironment;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
/// There are currently 25 cover images numbered 0 to 24 (0-indexed).
|
||||
@@ -63,11 +62,7 @@ impl MediaFileCoverImageDetails {
|
||||
}
|
||||
|
||||
/// For non-media file tokens (eg. emulated TTS results)
|
||||
pub fn from_legacy_token_str(token: &str) -> Self {
|
||||
Self::from_token_str(token)
|
||||
}
|
||||
|
||||
fn from_token_str(token: &str) -> Self {
|
||||
pub fn from_token_str(token: &str) -> Self {
|
||||
Self {
|
||||
// TODO(bt,2024-04-07): Add column to schema to support + CRUD to add.
|
||||
maybe_cover_image_public_bucket_path: None,
|
||||
@@ -81,7 +76,6 @@ impl MediaFileCoverImageDetails {
|
||||
pub fn from_optional_db_fields(
|
||||
token: &MediaFileToken,
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
maybe_cover_image_public_bucket_extension: Option<&str>,
|
||||
@@ -89,17 +83,15 @@ impl MediaFileCoverImageDetails {
|
||||
Self::from_optional_db_str_fields(
|
||||
token.as_str(),
|
||||
domain,
|
||||
server_environment,
|
||||
maybe_cover_image_public_bucket_path,
|
||||
maybe_cover_image_public_bucket_prefix,
|
||||
maybe_cover_image_public_bucket_extension
|
||||
)
|
||||
}
|
||||
|
||||
fn from_optional_db_str_fields(
|
||||
pub fn from_optional_db_str_fields(
|
||||
token: &str,
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
maybe_cover_image_public_bucket_extension: Option<&str>,
|
||||
@@ -120,19 +112,19 @@ impl MediaFileCoverImageDetails {
|
||||
// NB: Fail construction open.
|
||||
let maybe_cover_image_public_bucket_url = maybe_bucket_path
|
||||
.as_ref()
|
||||
.and_then(|bucket_path| {
|
||||
let rooted_path = bucket_path.get_full_object_path_str();
|
||||
let mut url = cdn_link::new_cdn_url(domain, server_environment);
|
||||
url.set_path(rooted_path);
|
||||
Some(url)
|
||||
});
|
||||
.map(|bucket_path| bucket_url_from_media_path(bucket_path).ok())
|
||||
.flatten();
|
||||
|
||||
let maybe_links = CoverImageLinks::from_maybe_media_path(
|
||||
domain, server_environment, maybe_bucket_path.as_ref());
|
||||
domain, maybe_bucket_path.as_ref());
|
||||
|
||||
// let maybe_media_links = maybe_bucket_path
|
||||
// .map(|path| MediaLinks::from_media_path(domain, &path));
|
||||
|
||||
Self {
|
||||
maybe_cover_image_public_bucket_path,
|
||||
maybe_cover_image_public_bucket_url,
|
||||
//maybe_media_links,
|
||||
maybe_links,
|
||||
default_cover: MediaFileDefaultCover::from_token_str(token),
|
||||
}
|
||||
@@ -189,142 +181,4 @@ mod tests {
|
||||
assert_eq!(cover.color_index, 0);
|
||||
assert_eq!(cover.image_index, 23);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cover_image_url_production() {
|
||||
let token = MediaFileToken::new_from_str("test_token");
|
||||
let domain = super::MediaDomain::FakeYou;
|
||||
let env = super::ServerEnvironment::Production;
|
||||
|
||||
let details = super::MediaFileCoverImageDetails::from_optional_db_fields(
|
||||
&token,
|
||||
domain,
|
||||
env,
|
||||
Some("bucket_hash"),
|
||||
Some("image_"),
|
||||
Some(".png"),
|
||||
);
|
||||
|
||||
let url = details.maybe_cover_image_public_bucket_url.unwrap();
|
||||
assert!(
|
||||
url.as_str().starts_with("https://cdn-2.fakeyou.com"),
|
||||
"Production URL should start with https://cdn-2.fakeyou.com, got: {}",
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cover_image_url_development() {
|
||||
let token = MediaFileToken::new_from_str("test_token");
|
||||
let domain = super::MediaDomain::FakeYou;
|
||||
let env = super::ServerEnvironment::Development;
|
||||
|
||||
let details = super::MediaFileCoverImageDetails::from_optional_db_fields(
|
||||
&token,
|
||||
domain,
|
||||
env,
|
||||
Some("bucket_hash"),
|
||||
Some("image_"),
|
||||
Some(".png"),
|
||||
);
|
||||
|
||||
let url = details.maybe_cover_image_public_bucket_url.unwrap();
|
||||
assert!(
|
||||
url.as_str().starts_with("https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/"),
|
||||
"Development URL should start with https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/, got: {}",
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cover_image_links_cdn_url_production() {
|
||||
let token = MediaFileToken::new_from_str("test_token");
|
||||
let domain = super::MediaDomain::FakeYou;
|
||||
let env = super::ServerEnvironment::Production;
|
||||
|
||||
let details = super::MediaFileCoverImageDetails::from_optional_db_fields(
|
||||
&token,
|
||||
domain,
|
||||
env,
|
||||
Some("bucket_hash"),
|
||||
Some("image_"),
|
||||
Some(".png"),
|
||||
);
|
||||
|
||||
let links = details.maybe_links.unwrap();
|
||||
assert!(
|
||||
links.cdn_url.as_str().starts_with("https://cdn-2.fakeyou.com"),
|
||||
"Production cdn_url should start with https://cdn-2.fakeyou.com, got: {}",
|
||||
links.cdn_url
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cover_image_links_cdn_url_development() {
|
||||
let token = MediaFileToken::new_from_str("test_token");
|
||||
let domain = super::MediaDomain::FakeYou;
|
||||
let env = super::ServerEnvironment::Development;
|
||||
|
||||
let details = super::MediaFileCoverImageDetails::from_optional_db_fields(
|
||||
&token,
|
||||
domain,
|
||||
env,
|
||||
Some("bucket_hash"),
|
||||
Some("image_"),
|
||||
Some(".png"),
|
||||
);
|
||||
|
||||
let links = details.maybe_links.unwrap();
|
||||
assert!(
|
||||
links.cdn_url.as_str().starts_with("https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/"),
|
||||
"Development cdn_url should start with https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/, got: {}",
|
||||
links.cdn_url
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cover_image_links_thumbnail_template_production() {
|
||||
let token = MediaFileToken::new_from_str("test_token");
|
||||
let domain = super::MediaDomain::FakeYou;
|
||||
let env = super::ServerEnvironment::Production;
|
||||
|
||||
let details = super::MediaFileCoverImageDetails::from_optional_db_fields(
|
||||
&token,
|
||||
domain,
|
||||
env,
|
||||
Some("bucket_hash"),
|
||||
Some("image_"),
|
||||
Some(".png"),
|
||||
);
|
||||
|
||||
let links = details.maybe_links.unwrap();
|
||||
assert!(
|
||||
links.thumbnail_template.starts_with("https://cdn-2.fakeyou.com"),
|
||||
"Production thumbnail_template should start with https://cdn-2.fakeyou.com, got: {}",
|
||||
links.thumbnail_template
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cover_image_links_thumbnail_template_development() {
|
||||
let token = MediaFileToken::new_from_str("test_token");
|
||||
let domain = super::MediaDomain::FakeYou;
|
||||
let env = super::ServerEnvironment::Development;
|
||||
|
||||
let details = super::MediaFileCoverImageDetails::from_optional_db_fields(
|
||||
&token,
|
||||
domain,
|
||||
env,
|
||||
Some("bucket_hash"),
|
||||
Some("image_"),
|
||||
Some(".png"),
|
||||
);
|
||||
|
||||
let links = details.maybe_links.unwrap();
|
||||
assert!(
|
||||
links.thumbnail_template.starts_with("https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/"),
|
||||
"Development thumbnail_template should start with https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/, got: {}",
|
||||
links.thumbnail_template
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-5
@@ -1,8 +1,8 @@
|
||||
use crate::http_server::common_responses::media::cover_image_links_builder::CoverImageLinksBuilder;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_from_media_path::bucket_url_from_media_path;
|
||||
use artcraft_api_defs::common::responses::media_file_cover_image_details::{MediaFileCoverImageDetails, MediaFileDefaultCover};
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use server_environment::ServerEnvironment;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
@@ -42,7 +42,6 @@ impl MediaFileCoverImageDetailsBuilder {
|
||||
pub fn from_optional_db_fields(
|
||||
token: &MediaFileToken,
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
maybe_cover_image_public_bucket_extension: Option<&str>,
|
||||
@@ -50,7 +49,6 @@ impl MediaFileCoverImageDetailsBuilder {
|
||||
Self::from_optional_db_str_fields(
|
||||
token.as_str(),
|
||||
domain,
|
||||
server_environment,
|
||||
maybe_cover_image_public_bucket_path,
|
||||
maybe_cover_image_public_bucket_prefix,
|
||||
maybe_cover_image_public_bucket_extension
|
||||
@@ -60,7 +58,6 @@ impl MediaFileCoverImageDetailsBuilder {
|
||||
pub fn from_optional_db_str_fields(
|
||||
token: &str,
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
maybe_cover_image_public_bucket_extension: Option<&str>,
|
||||
@@ -72,10 +69,29 @@ impl MediaFileCoverImageDetailsBuilder {
|
||||
maybe_cover_image_public_bucket_extension
|
||||
));
|
||||
|
||||
let maybe_cover_image_public_bucket_path = maybe_bucket_path
|
||||
.as_ref()
|
||||
.map(|bucket_path| bucket_path
|
||||
.get_full_object_path_str()
|
||||
.to_string());
|
||||
|
||||
// NB: Fail construction open.
|
||||
let maybe_cover_image_public_bucket_url = maybe_bucket_path
|
||||
.as_ref()
|
||||
.map(|bucket_path| bucket_url_from_media_path(bucket_path).ok())
|
||||
.flatten();
|
||||
|
||||
let maybe_links = CoverImageLinksBuilder::from_maybe_media_path(
|
||||
domain, server_environment, maybe_bucket_path.as_ref());
|
||||
domain, maybe_bucket_path.as_ref());
|
||||
|
||||
// let maybe_media_links = maybe_bucket_path
|
||||
// .map(|path| MediaLinks::from_media_path(domain, &path));
|
||||
|
||||
MediaFileCoverImageDetails {
|
||||
// NB: The code using this builder does not populate or read these legacy fields:
|
||||
// maybe_cover_image_public_bucket_path,
|
||||
// maybe_cover_image_public_bucket_url,
|
||||
// maybe_media_links,
|
||||
maybe_links,
|
||||
default_cover: MediaFileDefaultCoverBuilder::from_token_str(token),
|
||||
}
|
||||
|
||||
+6
-12
@@ -1,13 +1,12 @@
|
||||
use url::Url;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::http_server::common_responses::media::cdn_link;
|
||||
use crate::http_server::common_responses::media::cover_image_links::CoverImageLinks;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_from_media_path::bucket_url_from_media_path;
|
||||
use crate::util::placeholder_images::cover_images::default_cover_image_color_from_token::default_cover_image_color_from_token;
|
||||
use crate::util::placeholder_images::cover_images::default_cover_image_from_token::default_cover_image_from_token;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use server_environment::ServerEnvironment;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
/// Everything we need to create a cover image.
|
||||
@@ -46,7 +45,6 @@ impl WeightsCoverImageDetails {
|
||||
|
||||
pub fn from_optional_db_fields(
|
||||
domain: MediaDomain,
|
||||
server_environment: ServerEnvironment,
|
||||
model_weight_token: &ModelWeightToken,
|
||||
maybe_cover_image_public_bucket_path: Option<&str>,
|
||||
maybe_cover_image_public_bucket_prefix: Option<&str>,
|
||||
@@ -68,15 +66,13 @@ impl WeightsCoverImageDetails {
|
||||
// NB: Fail construction open.
|
||||
let maybe_cover_image_public_bucket_url = maybe_bucket_path
|
||||
.as_ref()
|
||||
.and_then(|bucket_path| {
|
||||
let rooted_path = bucket_path.get_full_object_path_str();
|
||||
let mut url = cdn_link::new_cdn_url(domain, server_environment);
|
||||
url.set_path(rooted_path);
|
||||
Some(url)
|
||||
});
|
||||
.map(|bucket_path| bucket_url_from_media_path(bucket_path).ok())
|
||||
.flatten();
|
||||
|
||||
let image_index = default_cover_image_from_token(model_weight_token);
|
||||
|
||||
let maybe_links = CoverImageLinks::from_maybe_media_path(
|
||||
domain, server_environment, maybe_bucket_path.as_ref());
|
||||
domain, maybe_bucket_path.as_ref());
|
||||
|
||||
Self {
|
||||
maybe_cover_image_public_bucket_path,
|
||||
@@ -100,7 +96,6 @@ impl WeightsDefaultCoverInfo {
|
||||
mod tests {
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::common_responses::media::weights_cover_image_details::WeightsCoverImageDetails;
|
||||
use server_environment::ServerEnvironment;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use url::Url;
|
||||
|
||||
@@ -114,7 +109,6 @@ mod tests {
|
||||
|
||||
let cover_image = WeightsCoverImageDetails::from_optional_db_fields(
|
||||
domain,
|
||||
ServerEnvironment::Production,
|
||||
&token,
|
||||
maybe_public_bucket_hash,
|
||||
maybe_prefix,
|
||||
|
||||
-82
@@ -1,31 +1,3 @@
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
|
||||
pub struct SyntheticFailure {
|
||||
pub frontend_failure_category: FrontendFailureCategory,
|
||||
pub frontend_failure_message: Option<String>,
|
||||
}
|
||||
|
||||
/// If the prompt contains a trigger phrase, parse a `FrontendFailureCategory`
|
||||
/// from the remaining words (falling back to `GenerationFailed`) and return a
|
||||
/// `SyntheticFailure`. Returns `None` when the trigger phrase is absent.
|
||||
pub fn test_synthetic_failure_reason(prompt: &str) -> Option<SyntheticFailure> {
|
||||
let lower = prompt.to_ascii_lowercase();
|
||||
if !lower.contains("simulate_artcraft_failure") && !lower.contains("test_artcraft_failure") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let maybe_category = prompt
|
||||
.split_whitespace()
|
||||
.find_map(|word| FrontendFailureCategory::from_str(word).ok());
|
||||
|
||||
let category = maybe_category.unwrap_or(FrontendFailureCategory::GenerationFailed);
|
||||
|
||||
Some(SyntheticFailure {
|
||||
frontend_failure_category: category,
|
||||
frontend_failure_message: Some("This was a simulated failure".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the prompt contains the secret test phrase that triggers
|
||||
/// a synthetic job failure. The job record is inserted with
|
||||
/// `JobStatusPlus::CompleteFailure` immediately, without calling any external
|
||||
@@ -33,57 +5,3 @@ pub fn test_synthetic_failure_reason(prompt: &str) -> Option<SyntheticFailure> {
|
||||
pub fn is_job_failure_test(prompt: &str) -> bool {
|
||||
prompt.to_ascii_lowercase().contains("artcraft_test_job_failure")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_trigger_returns_none() {
|
||||
assert!(test_synthetic_failure_reason("a normal prompt").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_simulate_with_no_category_defaults_to_generation_failed() {
|
||||
let result = test_synthetic_failure_reason("simulate_artcraft_failure").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::GenerationFailed);
|
||||
assert_eq!(result.frontend_failure_message.as_deref(), Some("This was a simulated failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_test_with_no_category_defaults_to_generation_failed() {
|
||||
let result = test_synthetic_failure_reason("test_artcraft_failure").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::GenerationFailed);
|
||||
assert_eq!(result.frontend_failure_message.as_deref(), Some("This was a simulated failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_with_known_category() {
|
||||
let result = test_synthetic_failure_reason("simulate_artcraft_failure face_not_detected").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::FaceNotDetected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_with_category_before_keyword() {
|
||||
let result = test_synthetic_failure_reason("rule_bans_user_image simulate_artcraft_failure").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::RuleBansUserImage);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_with_model_rules_violation() {
|
||||
let result = test_synthetic_failure_reason("test_artcraft_failure model_rules_violation please").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::ModelRulesViolation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_is_case_insensitive() {
|
||||
let result = test_synthetic_failure_reason("SIMULATE_ARTCRAFT_FAILURE").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::GenerationFailed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_embedded_in_text() {
|
||||
let result = test_synthetic_failure_reason("hello world simulate_artcraft_failure keep_alive_elapsed goodbye").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::KeepAliveElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod job_failure_test;
|
||||
pub mod payments_error_test;
|
||||
mod test_synthetic_failure_reason;
|
||||
pub mod payments_error_test;
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
|
||||
pub struct SyntheticFailure {
|
||||
pub frontend_failure_category: FrontendFailureCategory,
|
||||
pub frontend_failure_message: Option<String>,
|
||||
}
|
||||
|
||||
/// If the prompt contains the secret trigger phrase, parse a
|
||||
/// `FrontendFailureCategory` from the remaining words (falling back to
|
||||
/// `GenerationFailed`) and return a `SyntheticFailure`.
|
||||
///
|
||||
/// Returns `None` when the trigger phrase is absent.
|
||||
pub fn test_synthetic_failure_reason(prompt: &str) -> Option<SyntheticFailure> {
|
||||
let lower = prompt.to_ascii_lowercase();
|
||||
if !lower.contains("simulate_artcraft_failure") && !lower.contains("test_artcraft_failure") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let maybe_category = prompt
|
||||
.split_whitespace()
|
||||
.find_map(|word| FrontendFailureCategory::from_str(word).ok());
|
||||
|
||||
let category = maybe_category.unwrap_or(FrontendFailureCategory::GenerationFailed);
|
||||
|
||||
Some(SyntheticFailure {
|
||||
frontend_failure_category: category,
|
||||
frontend_failure_message: Some("This was a simulated failure".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_trigger_returns_none() {
|
||||
assert!(test_synthetic_failure_reason("a normal prompt").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_simulate_with_no_category_defaults_to_generation_failed() {
|
||||
let result = test_synthetic_failure_reason("simulate_artcraft_failure").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::GenerationFailed);
|
||||
assert_eq!(result.frontend_failure_message.as_deref(), Some("This was a simulated failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_test_with_no_category_defaults_to_generation_failed() {
|
||||
let result = test_synthetic_failure_reason("test_artcraft_failure").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::GenerationFailed);
|
||||
assert_eq!(result.frontend_failure_message.as_deref(), Some("This was a simulated failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_with_known_category() {
|
||||
let result = test_synthetic_failure_reason("simulate_artcraft_failure face_not_detected").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::FaceNotDetected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_with_category_before_keyword() {
|
||||
let result = test_synthetic_failure_reason("rule_bans_user_image simulate_artcraft_failure").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::RuleBansUserImage);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_with_model_rules_violation() {
|
||||
let result = test_synthetic_failure_reason("test_artcraft_failure model_rules_violation please").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::ModelRulesViolation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_is_case_insensitive() {
|
||||
let result = test_synthetic_failure_reason("SIMULATE_ARTCRAFT_FAILURE").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::GenerationFailed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_embedded_in_text() {
|
||||
let result = test_synthetic_failure_reason("hello world simulate_artcraft_failure keep_alive_elapsed goodbye").unwrap();
|
||||
assert_eq!(result.frontend_failure_category, FrontendFailureCategory::KeepAliveElapsed);
|
||||
}
|
||||
}
|
||||
-2
@@ -216,8 +216,6 @@ pub async fn flux_2_lora_edit_image_angle_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -217,8 +217,6 @@ pub async fn qwen_edit_2511_edit_image_angle_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -279,8 +279,6 @@ pub async fn gemini_25_flash_edit_image_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -301,8 +301,6 @@ pub async fn gpt_image_1_edit_image_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -253,8 +253,6 @@ pub async fn qwen_edit_image_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
+2
-9
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::billing::wallets::temporary_test_wallet_deduction::temporary_test_wallet_deduction;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::job_failure_test::test_synthetic_failure_reason;
|
||||
use crate::http_server::endpoints::generate::common::job_failure_test::is_job_failure_test;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::state::server_state::ServerState;
|
||||
@@ -10,7 +10,6 @@ use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::FluxProKontextMaxEditImageNumImages;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageAspectRatio, GenerateFlux1DevTextToImageNumImages, GenerateFlux1DevTextToImageRequest, GenerateFlux1DevTextToImageResponse};
|
||||
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
@@ -96,15 +95,13 @@ pub async fn generate_flux_1_dev_text_to_image_handler(
|
||||
})?;
|
||||
|
||||
// Secret test hook: insert a synthetic "complete_failure" job without calling Fal.
|
||||
if let Some(synthetic_failure) = test_synthetic_failure_reason(request.prompt.as_deref().unwrap_or("")) {
|
||||
if is_job_failure_test(request.prompt.as_deref().unwrap_or("")) {
|
||||
return insert_mock_failure_job(
|
||||
&http_request,
|
||||
request.prompt.as_deref(),
|
||||
&request.uuid_idempotency_token,
|
||||
maybe_user_session.as_ref().map(|s| &s.user_token),
|
||||
maybe_avt_token.as_ref(),
|
||||
Some(synthetic_failure.frontend_failure_category),
|
||||
synthetic_failure.frontend_failure_message.as_deref(),
|
||||
&mut mysql_connection,
|
||||
).await;
|
||||
}
|
||||
@@ -255,8 +252,6 @@ async fn insert_mock_failure_job(
|
||||
uuid_idempotency_token: &str,
|
||||
maybe_creator_user_token: Option<&UserToken>,
|
||||
maybe_avt_token: Option<&AnonymousVisitorTrackingToken>,
|
||||
maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
maybe_failure_reason: Option<&str>,
|
||||
mysql_connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
) -> Result<Json<GenerateFlux1DevTextToImageResponse>, CommonWebError> {
|
||||
let ip_address = get_request_ip(http_request);
|
||||
@@ -300,8 +295,6 @@ async fn insert_mock_failure_job(
|
||||
maybe_avt_token,
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_frontend_failure_category,
|
||||
maybe_failure_reason,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
-2
@@ -188,8 +188,6 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -192,8 +192,6 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
+1
-3
@@ -235,8 +235,6 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
@@ -247,7 +245,7 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
|
||||
-2
@@ -275,8 +275,6 @@ pub async fn bytedance_seedream_5_lite_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -300,8 +300,6 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -298,8 +298,6 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -388,8 +388,6 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -298,8 +298,6 @@ pub async fn nano_banana_2_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -331,8 +331,6 @@ pub async fn nano_banana_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -346,8 +346,6 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -249,8 +249,6 @@ pub async fn generate_kling_1_6_pro_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -234,8 +234,6 @@ pub async fn generate_kling_2_1_master_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -249,8 +249,6 @@ pub async fn generate_kling_2_1_pro_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -264,8 +264,6 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
+1
-3
@@ -241,8 +241,6 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
@@ -253,7 +251,7 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
|
||||
-2
@@ -247,8 +247,6 @@ pub async fn generate_veo_2_image_to_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -240,8 +240,6 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -248,8 +248,6 @@ pub async fn generate_veo_3_image_to_video_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -318,8 +318,6 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -317,8 +317,6 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -366,8 +366,6 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -366,8 +366,6 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -371,8 +371,6 @@ pub async fn seedance_1p5_pro_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -337,8 +337,6 @@ pub async fn sora_2_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
-2
@@ -339,8 +339,6 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
+1
-3
@@ -402,8 +402,6 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
@@ -414,7 +412,7 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
|
||||
+1
-3
@@ -402,8 +402,6 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
@@ -414,7 +412,7 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
|
||||
+1
-8
@@ -21,8 +21,6 @@ use artcraft_api_defs::jobs::list_session_jobs::{ListSessionJobsItem, ListSessio
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use bucket_paths::legacy::typified_paths::public::voice_conversion_results::bucket_file_path::VoiceConversionResultOriginalFilePath;
|
||||
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::frontend_failure_category::FrontendFailureCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
@@ -292,12 +290,7 @@ fn db_record_to_response_payload(
|
||||
maybe_first_started_at: record.maybe_first_started_at,
|
||||
attempt_count: record.attempt_count as u8,
|
||||
requires_keepalive: record.is_keepalive_required,
|
||||
maybe_failure_category: record
|
||||
.maybe_frontend_failure_category
|
||||
.and_then(|val| FrontendFailureCategoryForOldClients::try_from_db_enum(val)),
|
||||
maybe_failure_category_updated: record
|
||||
.maybe_frontend_failure_category
|
||||
.map(|val| FrontendFailureCategoryForApiClients::from_db_enum(val)),
|
||||
maybe_failure_category: record.maybe_frontend_failure_category,
|
||||
progress_percentage,
|
||||
maybe_current_execution_duration_seconds,
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user