mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 01:29:31 -04:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78f692e2de | ||
|
|
f8e400019d | ||
|
|
1b4d3e0f4b | ||
|
|
8964b863d7 | ||
|
|
c350524371 |
Generated
+1
@@ -11603,6 +11603,7 @@ dependencies = [
|
||||
"utoipa-swagger-ui",
|
||||
"videos",
|
||||
"workspace-build-acceleration",
|
||||
"world_labs_api",
|
||||
"wreq",
|
||||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod cost_estimate;
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod splat;
|
||||
pub mod video;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GENERATE_WORLDLABS_MARBLE_0P1_MINI_SPLAT_URL_PATH: &str = "/v1/generate/splat/worldlabs_marble_0p1_mini";
|
||||
|
||||
/// World Labs Marble 0.1-mini Splat Generation
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateWorldlabsMarble0p1MiniSplatRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Optional image to use as input for world generation.
|
||||
pub image_media_file_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional text prompt for world generation.
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateWorldlabsMarble0p1MiniSplatResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GENERATE_WORLDLABS_MARBLE_0P1_PLUS_SPLAT_URL_PATH: &str = "/v1/generate/splat/worldlabs_marble_0p1_plus";
|
||||
|
||||
/// World Labs Marble 0.1-plus Splat Generation
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateWorldlabsMarble0p1PlusSplatRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Optional image to use as input for world generation.
|
||||
pub image_media_file_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional text prompt for world generation.
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateWorldlabsMarble0p1PlusSplatResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod generate_worldlabs_marble_0p1_mini_splat;
|
||||
pub mod generate_worldlabs_marble_0p1_plus_splat;
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod cost_estimate;
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod splat;
|
||||
pub mod video;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_mini_splat::{GenerateWorldlabsMarble0p1MiniSplatRequest, GenerateWorldlabsMarble0p1MiniSplatResponse, GENERATE_WORLDLABS_MARBLE_0P1_MINI_SPLAT_URL_PATH};
|
||||
|
||||
pub async fn generate_worldlabs_marble_0p1_mini_splat(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateWorldlabsMarble0p1MiniSplatRequest,
|
||||
) -> Result<GenerateWorldlabsMarble0p1MiniSplatResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_WORLDLABS_MARBLE_0P1_MINI_SPLAT_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_plus_splat::{GenerateWorldlabsMarble0p1PlusSplatRequest, GenerateWorldlabsMarble0p1PlusSplatResponse, GENERATE_WORLDLABS_MARBLE_0P1_PLUS_SPLAT_URL_PATH};
|
||||
|
||||
pub async fn generate_worldlabs_marble_0p1_plus_splat(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateWorldlabsMarble0p1PlusSplatRequest,
|
||||
) -> Result<GenerateWorldlabsMarble0p1PlusSplatResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_WORLDLABS_MARBLE_0P1_PLUS_SPLAT_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod generate_worldlabs_marble_0p1_mini_splat;
|
||||
pub mod generate_worldlabs_marble_0p1_plus_splat;
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod generate_world;
|
||||
pub(crate) mod http_request;
|
||||
pub mod http_request;
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod fal;
|
||||
pub mod job;
|
||||
pub mod seedance2pro;
|
||||
pub mod web;
|
||||
pub mod worldlabs;
|
||||
|
||||
+2
@@ -186,6 +186,7 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::F5TTS => Some("F5 TTS"),
|
||||
InferenceCategory::SeedVc => Some("Seed VC"),
|
||||
InferenceCategory::ConvertBvhToWorkflow => Some("BVH to Workflow"),
|
||||
InferenceCategory::SplatGeneration => Some("Splat Generation"),
|
||||
InferenceCategory::DeprecatedField => Some("Job"), // TODO(bt,2024-07-16): Fix
|
||||
};
|
||||
}
|
||||
@@ -208,6 +209,7 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::SeedVc => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::LivePortrait => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ConvertBvhToWorkflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::SplatGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::DeprecatedField => (true, None), // TODO(bt,2024-07-16): We'll need to read another field!
|
||||
};
|
||||
|
||||
|
||||
+2
@@ -162,6 +162,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
|
||||
InferenceCategory::F5TTS => Some("F5 TTS"),
|
||||
InferenceCategory::SeedVc => Some("Seed VC"),
|
||||
InferenceCategory::ConvertBvhToWorkflow => Some("BVH to Workflow"),
|
||||
InferenceCategory::SplatGeneration => Some("Splat Generation"),
|
||||
InferenceCategory::DeprecatedField => Some("Job"), // TODO(bt,2024-07-16): Fix
|
||||
};
|
||||
}
|
||||
@@ -184,6 +185,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
|
||||
InferenceCategory::LivePortrait => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::SeedVc => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ConvertBvhToWorkflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::SplatGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::DeprecatedField => (true, None), // TODO(bt,2024-07-16): We'll need to read another field!
|
||||
};
|
||||
|
||||
|
||||
+2
@@ -252,6 +252,7 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::ConvertBvhToWorkflow => Some("BVH to Workflow"),
|
||||
InferenceCategory::LivePortrait => Some("Live Portrait"),
|
||||
InferenceCategory::SeedVc => Some("Seed VC"),
|
||||
InferenceCategory::SplatGeneration => Some("Splat Generation"),
|
||||
InferenceCategory::DeprecatedField => Some("Job"), // TODO(bt,2024-07-16): Fix
|
||||
};
|
||||
}
|
||||
@@ -274,6 +275,7 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
|
||||
InferenceCategory::LivePortrait => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::SeedVc => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::ConvertBvhToWorkflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::SplatGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
|
||||
InferenceCategory::DeprecatedField => (true, None), // TODO(bt,2024-07-16): We'll need to read another field!
|
||||
};
|
||||
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
use anyhow::anyhow;
|
||||
use log::info;
|
||||
use sqlx::{Executor, MySql};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
|
||||
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
|
||||
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::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
|
||||
pub struct InsertGenericInferenceForWorldlabsWithAprioriJobTokenArgs<'e, 'c, E>
|
||||
where E: 'e + Executor<'c, Database = MySql>
|
||||
{
|
||||
pub uuid_idempotency_token: &'e str,
|
||||
|
||||
pub apriori_job_token: &'e InferenceJobToken,
|
||||
|
||||
/// The external operation_id returned by World Labs generate_world call.
|
||||
pub maybe_external_third_party_id: &'e str,
|
||||
|
||||
pub maybe_inference_args: Option<GenericInferenceArgs>,
|
||||
|
||||
pub maybe_prompt_token: Option<&'e PromptToken>,
|
||||
|
||||
pub maybe_wallet_ledger_entry_token: Option<&'e WalletLedgerEntryToken>,
|
||||
|
||||
pub maybe_creator_user_token: Option<&'e UserToken>,
|
||||
pub maybe_avt_token: Option<&'e AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: &'e str,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub mysql_executor: E,
|
||||
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
pub async fn insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token<'e, 'c : 'e, E>(
|
||||
args: InsertGenericInferenceForWorldlabsWithAprioriJobTokenArgs<'e, 'c, E>
|
||||
) -> Result<InferenceJobToken, DatabaseQueryError>
|
||||
where E: 'e + Executor<'c, Database = MySql>
|
||||
{
|
||||
let serialized_args_payload = serde_json::ser::to_string(&args.maybe_inference_args)
|
||||
.map_err(|_e| anyhow!("could not encode inference args"))?;
|
||||
|
||||
const JOB_TYPE: InferenceJobType = InferenceJobType::WorldlabsQueue;
|
||||
const INFERENCE_CATEGORY: InferenceCategory = InferenceCategory::SplatGeneration;
|
||||
const PRODUCT_CATEGORY: InferenceJobProductCategory = InferenceJobProductCategory::WorldlabsSplat;
|
||||
const EXTERNAL_THIRD_PARTY: InferenceJobExternalThirdParty = InferenceJobExternalThirdParty::Worldlabs;
|
||||
const STATUS: JobStatusPlus = JobStatusPlus::Pending;
|
||||
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO generic_inference_jobs
|
||||
SET
|
||||
token = ?,
|
||||
uuid_idempotency_token = ?,
|
||||
|
||||
job_type = ?,
|
||||
|
||||
maybe_external_third_party = ?,
|
||||
maybe_external_third_party_id = ?,
|
||||
|
||||
product_category = ?,
|
||||
inference_category = ?,
|
||||
|
||||
maybe_model_type = NULL,
|
||||
maybe_model_token = NULL,
|
||||
|
||||
maybe_input_source_token = NULL,
|
||||
maybe_input_source_token_type = NULL,
|
||||
|
||||
maybe_download_url = NULL,
|
||||
maybe_cover_image_media_file_token = NULL,
|
||||
|
||||
maybe_prompt_token = ?,
|
||||
|
||||
maybe_wallet_ledger_entry_token = ?,
|
||||
|
||||
maybe_raw_inference_text = NULL,
|
||||
|
||||
maybe_inference_args = ?,
|
||||
|
||||
maybe_creator_user_token = ?,
|
||||
maybe_creator_anonymous_visitor_token = ?,
|
||||
creator_ip_address = ?,
|
||||
creator_set_visibility = ?,
|
||||
|
||||
priority_level = 0,
|
||||
is_keepalive_required = FALSE,
|
||||
max_duration_seconds = 0,
|
||||
|
||||
is_debug_request = FALSE,
|
||||
maybe_routing_tag = NULL,
|
||||
|
||||
status = ?
|
||||
"#,
|
||||
args.apriori_job_token.as_str(),
|
||||
args.uuid_idempotency_token,
|
||||
|
||||
JOB_TYPE.to_str(),
|
||||
|
||||
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()),
|
||||
|
||||
args.maybe_wallet_ledger_entry_token.map(|t| t.to_string()),
|
||||
|
||||
serialized_args_payload,
|
||||
|
||||
args.maybe_creator_user_token.map(|t| t.to_string()),
|
||||
args.maybe_avt_token.map(|t| t.to_string()),
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
STATUS.to_str(),
|
||||
);
|
||||
|
||||
let query_result = query.execute(args.mysql_executor)
|
||||
.await;
|
||||
|
||||
let record_id = match query_result {
|
||||
Err(err) => return Err(DatabaseQueryError::from(err)),
|
||||
Ok(res) => res.last_insert_id(),
|
||||
};
|
||||
|
||||
info!("Insert generic inference job for World Labs queue: {} with record ID {}", args.apriori_job_token, record_id);
|
||||
|
||||
Ok(args.apriori_job_token.clone())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token;
|
||||
+12
-2
@@ -47,7 +47,11 @@ pub enum InferenceCategory {
|
||||
/// FAL-powered 3D object generation
|
||||
#[serde(rename = "object_generation")]
|
||||
ObjectGeneration,
|
||||
|
||||
|
||||
/// Gaussian Splat generation (eg. World Labs Marble)
|
||||
#[serde(rename = "splat_generation")]
|
||||
SplatGeneration,
|
||||
|
||||
/// FAL-powered image background removal
|
||||
#[serde(rename = "background_removal")]
|
||||
BackgroundRemoval,
|
||||
@@ -104,6 +108,7 @@ impl InferenceCategory {
|
||||
Self::ImageGeneration => "image_generation",
|
||||
Self::VideoGeneration => "video_generation",
|
||||
Self::ObjectGeneration => "object_generation",
|
||||
Self::SplatGeneration => "splat_generation",
|
||||
Self::BackgroundRemoval => "background_removal",
|
||||
Self::Mocap => "mocap",
|
||||
Self::Workflow => "workflow",
|
||||
@@ -125,6 +130,7 @@ impl InferenceCategory {
|
||||
"image_generation" => Ok(Self::ImageGeneration),
|
||||
"video_generation" => Ok(Self::VideoGeneration),
|
||||
"object_generation" => Ok(Self::ObjectGeneration),
|
||||
"splat_generation" => Ok(Self::SplatGeneration),
|
||||
"background_removal" => Ok(Self::BackgroundRemoval),
|
||||
"f5_tts" => Ok(Self::F5TTS),
|
||||
"mocap" => Ok(Self::Mocap),
|
||||
@@ -148,6 +154,7 @@ impl InferenceCategory {
|
||||
Self::VoiceConversion,
|
||||
Self::ImageGeneration,
|
||||
Self::ObjectGeneration,
|
||||
Self::SplatGeneration,
|
||||
Self::VideoGeneration,
|
||||
Self::BackgroundRemoval,
|
||||
Self::Mocap,
|
||||
@@ -179,6 +186,7 @@ mod tests {
|
||||
assert_serialization(InferenceCategory::ImageGeneration, "image_generation");
|
||||
assert_serialization(InferenceCategory::VideoGeneration, "video_generation");
|
||||
assert_serialization(InferenceCategory::ObjectGeneration, "object_generation");
|
||||
assert_serialization(InferenceCategory::SplatGeneration, "splat_generation");
|
||||
assert_serialization(InferenceCategory::BackgroundRemoval, "background_removal");
|
||||
assert_serialization(InferenceCategory::Mocap, "mocap");
|
||||
assert_serialization(InferenceCategory::F5TTS, "f5_tts");
|
||||
@@ -199,6 +207,7 @@ mod tests {
|
||||
assert_eq!(InferenceCategory::ImageGeneration.to_str(), "image_generation");
|
||||
assert_eq!(InferenceCategory::VideoGeneration.to_str(), "video_generation");
|
||||
assert_eq!(InferenceCategory::ObjectGeneration.to_str(), "object_generation");
|
||||
assert_eq!(InferenceCategory::SplatGeneration.to_str(), "splat_generation");
|
||||
assert_eq!(InferenceCategory::BackgroundRemoval.to_str(), "background_removal");
|
||||
assert_eq!(InferenceCategory::F5TTS.to_str(), "f5_tts");
|
||||
assert_eq!(InferenceCategory::SeedVc.to_str(), "seed_vc");
|
||||
@@ -219,6 +228,7 @@ mod tests {
|
||||
assert_eq!(InferenceCategory::from_str("image_generation").unwrap(), InferenceCategory::ImageGeneration);
|
||||
assert_eq!(InferenceCategory::from_str("video_generation").unwrap(), InferenceCategory::VideoGeneration);
|
||||
assert_eq!(InferenceCategory::from_str("object_generation").unwrap(), InferenceCategory::ObjectGeneration);
|
||||
assert_eq!(InferenceCategory::from_str("splat_generation").unwrap(), InferenceCategory::SplatGeneration);
|
||||
assert_eq!(InferenceCategory::from_str("background_removal").unwrap(), InferenceCategory::BackgroundRemoval);
|
||||
assert_eq!(InferenceCategory::from_str("f5_tts").unwrap(), InferenceCategory::F5TTS);
|
||||
assert_eq!(InferenceCategory::from_str("seed_vc").unwrap(), InferenceCategory::SeedVc);
|
||||
@@ -233,7 +243,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
// Static check
|
||||
const EXPECTED_COUNT : usize = 16;
|
||||
const EXPECTED_COUNT : usize = 17;
|
||||
|
||||
assert_eq!(InferenceCategory::all_variants().len(), EXPECTED_COUNT);
|
||||
assert_eq!(InferenceCategory::iter().len(), EXPECTED_COUNT);
|
||||
|
||||
+11
-1
@@ -20,6 +20,10 @@ pub enum InferenceJobExternalThirdParty {
|
||||
/// Seedance 2 Pro jobs
|
||||
#[serde(rename = "seedance2pro")]
|
||||
Seedance2Pro,
|
||||
|
||||
/// World Labs jobs
|
||||
#[serde(rename = "worldlabs")]
|
||||
Worldlabs,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
@@ -32,6 +36,7 @@ impl InferenceJobExternalThirdParty {
|
||||
match self {
|
||||
Self::Fal => "fal",
|
||||
Self::Seedance2Pro => "seedance2pro",
|
||||
Self::Worldlabs => "worldlabs",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +44,7 @@ impl InferenceJobExternalThirdParty {
|
||||
match value {
|
||||
"fal" => Ok(Self::Fal),
|
||||
"seedance2pro" => Ok(Self::Seedance2Pro),
|
||||
"worldlabs" => Ok(Self::Worldlabs),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -49,6 +55,7 @@ impl InferenceJobExternalThirdParty {
|
||||
BTreeSet::from([
|
||||
Self::Fal,
|
||||
Self::Seedance2Pro,
|
||||
Self::Worldlabs,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -65,24 +72,27 @@ mod tests {
|
||||
fn test_serialization() {
|
||||
assert_serialization(InferenceJobExternalThirdParty::Fal, "fal");
|
||||
assert_serialization(InferenceJobExternalThirdParty::Seedance2Pro, "seedance2pro");
|
||||
assert_serialization(InferenceJobExternalThirdParty::Worldlabs, "worldlabs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(InferenceJobExternalThirdParty::Fal.to_str(), "fal");
|
||||
assert_eq!(InferenceJobExternalThirdParty::Seedance2Pro.to_str(), "seedance2pro");
|
||||
assert_eq!(InferenceJobExternalThirdParty::Worldlabs.to_str(), "worldlabs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(InferenceJobExternalThirdParty::from_str("fal").unwrap(), InferenceJobExternalThirdParty::Fal);
|
||||
assert_eq!(InferenceJobExternalThirdParty::from_str("seedance2pro").unwrap(), InferenceJobExternalThirdParty::Seedance2Pro);
|
||||
assert_eq!(InferenceJobExternalThirdParty::from_str("worldlabs").unwrap(), InferenceJobExternalThirdParty::Worldlabs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
// Static check
|
||||
const EXPECTED_COUNT : usize = 2;
|
||||
const EXPECTED_COUNT : usize = 3;
|
||||
|
||||
assert_eq!(InferenceJobExternalThirdParty::all_variants().len(), EXPECTED_COUNT);
|
||||
assert_eq!(InferenceJobExternalThirdParty::iter().len(), EXPECTED_COUNT);
|
||||
|
||||
+13
-1
@@ -35,6 +35,12 @@ pub enum InferenceJobProductCategory {
|
||||
#[serde(rename = "seedance2pro_video")]
|
||||
Seedance2ProVideo,
|
||||
|
||||
// =============== WORLD LABS ===============
|
||||
|
||||
/// World Labs: Gaussian Splat Generation
|
||||
#[serde(rename = "worldlabs_splat")]
|
||||
WorldlabsSplat,
|
||||
|
||||
// =============== TEXT TO SPEECH ===============
|
||||
|
||||
/// TTS: GptSoVits
|
||||
@@ -131,6 +137,7 @@ impl InferenceJobProductCategory {
|
||||
Self::FalObject => "fal_object",
|
||||
Self::FalBgRemoval => "fal_bg_removal",
|
||||
Self::Seedance2ProVideo => "seedance2pro_video",
|
||||
Self::WorldlabsSplat => "worldlabs_splat",
|
||||
Self::TtsGptSoVits => "tts_gpt_so_vits",
|
||||
Self::TtsStyleTts2 => "tts_style_tts2",
|
||||
Self::TtsTacotron2 => "tts_tacotron2",
|
||||
@@ -164,6 +171,7 @@ impl InferenceJobProductCategory {
|
||||
"fal_object" => Ok(Self::FalObject),
|
||||
"fal_bg_removal" => Ok(Self::FalBgRemoval),
|
||||
"seedance2pro_video" => Ok(Self::Seedance2ProVideo),
|
||||
"worldlabs_splat" => Ok(Self::WorldlabsSplat),
|
||||
"tts_gpt_so_vits" => Ok(Self::TtsGptSoVits),
|
||||
"tts_style_tts2" => Ok(Self::TtsStyleTts2),
|
||||
"tts_tacotron2" => Ok(Self::TtsTacotron2),
|
||||
@@ -200,6 +208,7 @@ impl InferenceJobProductCategory {
|
||||
Self::FalObject,
|
||||
Self::FalBgRemoval,
|
||||
Self::Seedance2ProVideo,
|
||||
Self::WorldlabsSplat,
|
||||
Self::TtsGptSoVits,
|
||||
Self::TtsStyleTts2,
|
||||
Self::TtsTacotron2,
|
||||
@@ -242,6 +251,7 @@ mod tests {
|
||||
assert_serialization(InferenceJobProductCategory::FalObject, "fal_object");
|
||||
assert_serialization(InferenceJobProductCategory::FalBgRemoval, "fal_bg_removal");
|
||||
assert_serialization(InferenceJobProductCategory::Seedance2ProVideo, "seedance2pro_video");
|
||||
assert_serialization(InferenceJobProductCategory::WorldlabsSplat, "worldlabs_splat");
|
||||
assert_serialization(InferenceJobProductCategory::TtsGptSoVits, "tts_gpt_so_vits");
|
||||
assert_serialization(InferenceJobProductCategory::TtsStyleTts2, "tts_style_tts2");
|
||||
assert_serialization(InferenceJobProductCategory::TtsTacotron2, "tts_tacotron2");
|
||||
@@ -274,6 +284,7 @@ mod tests {
|
||||
assert_eq!(InferenceJobProductCategory::FalObject.to_str(), "fal_object");
|
||||
assert_eq!(InferenceJobProductCategory::FalBgRemoval.to_str(), "fal_bg_removal");
|
||||
assert_eq!(InferenceJobProductCategory::Seedance2ProVideo.to_str(), "seedance2pro_video");
|
||||
assert_eq!(InferenceJobProductCategory::WorldlabsSplat.to_str(), "worldlabs_splat");
|
||||
assert_eq!(InferenceJobProductCategory::TtsGptSoVits.to_str(), "tts_gpt_so_vits");
|
||||
assert_eq!(InferenceJobProductCategory::TtsStyleTts2.to_str(), "tts_style_tts2");
|
||||
assert_eq!(InferenceJobProductCategory::TtsTacotron2.to_str(), "tts_tacotron2");
|
||||
@@ -305,6 +316,7 @@ mod tests {
|
||||
assert_eq!(InferenceJobProductCategory::from_str("fal_object").unwrap(), InferenceJobProductCategory::FalObject);
|
||||
assert_eq!(InferenceJobProductCategory::from_str("fal_bg_removal").unwrap(), InferenceJobProductCategory::FalBgRemoval);
|
||||
assert_eq!(InferenceJobProductCategory::from_str("seedance2pro_video").unwrap(), InferenceJobProductCategory::Seedance2ProVideo);
|
||||
assert_eq!(InferenceJobProductCategory::from_str("worldlabs_splat").unwrap(), InferenceJobProductCategory::WorldlabsSplat);
|
||||
assert_eq!(InferenceJobProductCategory::from_str("tts_gpt_so_vits").unwrap(), InferenceJobProductCategory::TtsGptSoVits);
|
||||
assert_eq!(InferenceJobProductCategory::from_str("tts_style_tts2").unwrap(), InferenceJobProductCategory::TtsStyleTts2);
|
||||
assert_eq!(InferenceJobProductCategory::from_str("tts_tacotron2").unwrap(), InferenceJobProductCategory::TtsTacotron2);
|
||||
@@ -331,7 +343,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
// Static check
|
||||
const EXPECTED_COUNT : usize = 28;
|
||||
const EXPECTED_COUNT : usize = 29;
|
||||
|
||||
assert_eq!(InferenceJobProductCategory::all_variants().len(), EXPECTED_COUNT);
|
||||
assert_eq!(InferenceJobProductCategory::iter().len(), EXPECTED_COUNT);
|
||||
|
||||
+11
-1
@@ -23,6 +23,10 @@ pub enum InferenceJobType {
|
||||
#[serde(rename = "seedance2pro_queue")]
|
||||
Seedance2ProQueue,
|
||||
|
||||
/// World Labs jobs. We poll for results.
|
||||
#[serde(rename = "worldlabs_queue")]
|
||||
WorldlabsQueue,
|
||||
|
||||
/// Storyteller Studio and Video Style Transfer Jobs (which we may want to split).
|
||||
/// These run in Comfy.
|
||||
/// TODO(bt,2024-07-15): We may segregate these two job types in the future
|
||||
@@ -115,6 +119,7 @@ impl InferenceJobType {
|
||||
match self {
|
||||
Self::FalQueue => "fal_queue",
|
||||
Self::Seedance2ProQueue => "seedance2pro_queue",
|
||||
Self::WorldlabsQueue => "worldlabs_queue",
|
||||
Self::VideoRender => "video_render",
|
||||
Self::LivePortrait => "live_portrait",
|
||||
Self::FaceFusion => "face_fusion",
|
||||
@@ -142,6 +147,7 @@ impl InferenceJobType {
|
||||
match value {
|
||||
"fal_queue" => Ok(Self::FalQueue),
|
||||
"seedance2pro_queue" => Ok(Self::Seedance2ProQueue),
|
||||
"worldlabs_queue" => Ok(Self::WorldlabsQueue),
|
||||
"video_render" => Ok(Self::VideoRender),
|
||||
"live_portrait" => Ok(Self::LivePortrait),
|
||||
"face_fusion" => Ok(Self::FaceFusion),
|
||||
@@ -172,6 +178,7 @@ impl InferenceJobType {
|
||||
BTreeSet::from([
|
||||
Self::FalQueue,
|
||||
Self::Seedance2ProQueue,
|
||||
Self::WorldlabsQueue,
|
||||
Self::VideoRender,
|
||||
Self::LivePortrait,
|
||||
Self::FaceFusion,
|
||||
@@ -213,6 +220,7 @@ mod tests {
|
||||
fn test_serialization() {
|
||||
assert_serialization(InferenceJobType::FalQueue, "fal_queue");
|
||||
assert_serialization(InferenceJobType::Seedance2ProQueue, "seedance2pro_queue");
|
||||
assert_serialization(InferenceJobType::WorldlabsQueue, "worldlabs_queue");
|
||||
assert_serialization(InferenceJobType::VideoRender, "video_render");
|
||||
assert_serialization(InferenceJobType::LivePortrait, "live_portrait");
|
||||
assert_serialization(InferenceJobType::FaceFusion, "face_fusion");
|
||||
@@ -239,6 +247,7 @@ mod tests {
|
||||
fn to_str() {
|
||||
assert_eq!(InferenceJobType::FalQueue.to_str(), "fal_queue");
|
||||
assert_eq!(InferenceJobType::Seedance2ProQueue.to_str(), "seedance2pro_queue");
|
||||
assert_eq!(InferenceJobType::WorldlabsQueue.to_str(), "worldlabs_queue");
|
||||
assert_eq!(InferenceJobType::VideoRender.to_str(), "video_render");
|
||||
assert_eq!(InferenceJobType::LivePortrait.to_str(), "live_portrait");
|
||||
assert_eq!(InferenceJobType::FaceFusion.to_str(), "face_fusion");
|
||||
@@ -265,6 +274,7 @@ mod tests {
|
||||
fn from_str() {
|
||||
assert_eq!(InferenceJobType::from_str("fal_queue").unwrap(), InferenceJobType::FalQueue);
|
||||
assert_eq!(InferenceJobType::from_str("seedance2pro_queue").unwrap(), InferenceJobType::Seedance2ProQueue);
|
||||
assert_eq!(InferenceJobType::from_str("worldlabs_queue").unwrap(), InferenceJobType::WorldlabsQueue);
|
||||
assert_eq!(InferenceJobType::from_str("video_render").unwrap(), InferenceJobType::VideoRender);
|
||||
assert_eq!(InferenceJobType::from_str("live_portrait").unwrap(), InferenceJobType::LivePortrait);
|
||||
assert_eq!(InferenceJobType::from_str("face_fusion").unwrap(), InferenceJobType::FaceFusion);
|
||||
@@ -290,7 +300,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
// Static check
|
||||
const EXPECTED_COUNT : usize = 22;
|
||||
const EXPECTED_COUNT : usize = 23;
|
||||
|
||||
assert_eq!(InferenceJobType::all_variants().len(), EXPECTED_COUNT);
|
||||
assert_eq!(InferenceJobType::iter().len(), EXPECTED_COUNT);
|
||||
|
||||
@@ -127,6 +127,12 @@ pub enum ModelType {
|
||||
Hunyuan3d2_1,
|
||||
#[serde(rename = "hunyuan_3d_3")]
|
||||
Hunyuan3d3,
|
||||
|
||||
// Splat generation models (World Labs)
|
||||
#[serde(rename = "marble_0p1_mini")]
|
||||
Marble0p1Mini,
|
||||
#[serde(rename = "marble_0p1_plus")]
|
||||
Marble0p1Plus,
|
||||
}
|
||||
|
||||
impl_enum_display_and_debug_using_to_str!(ModelType);
|
||||
@@ -193,6 +199,10 @@ impl ModelType {
|
||||
Self::Hunyuan3d2_0 => "hunyuan_3d_2p0",
|
||||
Self::Hunyuan3d2_1 => "hunyuan_3d_2p1",
|
||||
Self::Hunyuan3d3 => "hunyuan_3d_3",
|
||||
|
||||
// Splat generation models (World Labs)
|
||||
Self::Marble0p1Mini => "marble_0p1_mini",
|
||||
Self::Marble0p1Plus => "marble_0p1_plus",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +264,10 @@ impl ModelType {
|
||||
"hunyuan_3d_2p1" => Ok(Self::Hunyuan3d2_1),
|
||||
"hunyuan_3d_3" => Ok(Self::Hunyuan3d3),
|
||||
|
||||
// Splat generation models (World Labs)
|
||||
"marble_0p1_mini" => Ok(Self::Marble0p1Mini),
|
||||
"marble_0p1_plus" => Ok(Self::Marble0p1Plus),
|
||||
|
||||
_ => Err(format!("invalid model_type: {:?}", job_status)),
|
||||
}
|
||||
}
|
||||
@@ -317,6 +331,10 @@ impl ModelType {
|
||||
Self::Hunyuan3d2_0,
|
||||
Self::Hunyuan3d2_1,
|
||||
Self::Hunyuan3d3,
|
||||
|
||||
// Splat generation models (World Labs)
|
||||
Self::Marble0p1Mini,
|
||||
Self::Marble0p1Plus,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -384,6 +402,9 @@ mod tests {
|
||||
assert_serialization(ModelType::Hunyuan3d2_0, "hunyuan_3d_2p0");
|
||||
assert_serialization(ModelType::Hunyuan3d2_1, "hunyuan_3d_2p1");
|
||||
assert_serialization(ModelType::Hunyuan3d3, "hunyuan_3d_3");
|
||||
// Splat generation models (World Labs)
|
||||
assert_serialization(ModelType::Marble0p1Mini, "marble_0p1_mini");
|
||||
assert_serialization(ModelType::Marble0p1Plus, "marble_0p1_plus");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -443,6 +464,9 @@ mod tests {
|
||||
assert_eq!(ModelType::Hunyuan3d2_0.to_str(), "hunyuan_3d_2p0");
|
||||
assert_eq!(ModelType::Hunyuan3d2_1.to_str(), "hunyuan_3d_2p1");
|
||||
assert_eq!(ModelType::Hunyuan3d3.to_str(), "hunyuan_3d_3");
|
||||
// Splat generation models (World Labs)
|
||||
assert_eq!(ModelType::Marble0p1Mini.to_str(), "marble_0p1_mini");
|
||||
assert_eq!(ModelType::Marble0p1Plus.to_str(), "marble_0p1_plus");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -501,12 +525,15 @@ mod tests {
|
||||
assert_eq!(ModelType::from_str("hunyuan_3d_2p0").unwrap(), ModelType::Hunyuan3d2_0);
|
||||
assert_eq!(ModelType::from_str("hunyuan_3d_2p1").unwrap(), ModelType::Hunyuan3d2_1);
|
||||
assert_eq!(ModelType::from_str("hunyuan_3d_3").unwrap(), ModelType::Hunyuan3d3);
|
||||
// Splat generation models (World Labs)
|
||||
assert_eq!(ModelType::from_str("marble_0p1_mini").unwrap(), ModelType::Marble0p1Mini);
|
||||
assert_eq!(ModelType::from_str("marble_0p1_plus").unwrap(), ModelType::Marble0p1Plus);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = ModelType::all_variants();
|
||||
assert_eq!(variants.len(), 50);
|
||||
assert_eq!(variants.len(), 52);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell));
|
||||
@@ -560,6 +587,9 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_0));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_1));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d3));
|
||||
// Splat generation models (World Labs)
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Marble0p1Mini));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Marble0p1Plus));
|
||||
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
|
||||
+7
-1
@@ -104,6 +104,10 @@ pub enum PublicInferenceJobType {
|
||||
/// Seedance 2 Pro video generation jobs. We poll for results.
|
||||
#[serde(rename = "seedance2pro_queue")]
|
||||
Seedance2ProQueue,
|
||||
|
||||
/// World Labs splat generation jobs. We poll for results.
|
||||
#[serde(rename = "worldlabs_queue")]
|
||||
WorldlabsQueue,
|
||||
}
|
||||
|
||||
/// NB: Legacy API for older code.
|
||||
@@ -135,9 +139,10 @@ impl PublicInferenceJobType {
|
||||
InferenceJobType::RerenderAVideo => Self::RerenderAVideo,
|
||||
InferenceJobType::ImageGenApi => Self::ImageGenApi,
|
||||
InferenceJobType::Seedance2ProQueue => Self::Seedance2ProQueue,
|
||||
InferenceJobType::WorldlabsQueue => Self::WorldlabsQueue,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn to_enum(&self) -> InferenceJobType {
|
||||
match self {
|
||||
// Renamed variants
|
||||
@@ -159,6 +164,7 @@ impl PublicInferenceJobType {
|
||||
Self::StableDiffusion => InferenceJobType::StableDiffusion,
|
||||
Self::ImageGenApi => InferenceJobType::ImageGenApi,
|
||||
Self::Seedance2ProQueue => InferenceJobType::Seedance2ProQueue,
|
||||
Self::WorldlabsQueue => InferenceJobType::WorldlabsQueue,
|
||||
Self::StyleTTS2 => InferenceJobType::StyleTTS2,
|
||||
Self::Tacotron2 => InferenceJobType::Tacotron2,
|
||||
Self::Unknown => InferenceJobType::Unknown,
|
||||
|
||||
@@ -82,6 +82,7 @@ url_config = { path = "../../../lib/url_config" }
|
||||
user_input_common = { path = "../../../lib/user_input_common" }
|
||||
users.workspace = true
|
||||
videos = { path = "../../../lib/files/videos" }
|
||||
world_labs_api.workspace = true
|
||||
|
||||
# Internal Components
|
||||
billing_component = { path = "../../plugins/billing" }
|
||||
|
||||
@@ -159,6 +159,10 @@ use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::Gener
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dResponse;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dResponse;
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_mini_splat::GenerateWorldlabsMarble0p1MiniSplatRequest;
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_mini_splat::GenerateWorldlabsMarble0p1MiniSplatResponse;
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_plus_splat::GenerateWorldlabsMarble0p1PlusSplatRequest;
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_plus_splat::GenerateWorldlabsMarble0p1PlusSplatResponse;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::GenerateKling16ProAspectRatio;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::GenerateKling16ProDuration;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::GenerateKling16ProImageToVideoRequest;
|
||||
@@ -321,6 +325,8 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
crate::http_server::endpoints::generate::image::edit::gpt_image_1_edit_image_handler::gpt_image_1_edit_image_handler,
|
||||
crate::http_server::endpoints::generate::object::generate_hunyuan_2_1_image_to_3d_handler::generate_hunyuan_2_1_image_to_3d_handler,
|
||||
crate::http_server::endpoints::generate::object::generate_hunyuan_2_0_image_to_3d_handler::generate_hunyuan_2_0_image_to_3d_handler,
|
||||
crate::http_server::endpoints::generate::splat::generate_worldlabs_marble_0p1_mini_splat_handler::generate_worldlabs_marble_0p1_mini_splat_handler,
|
||||
crate::http_server::endpoints::generate::splat::generate_worldlabs_marble_0p1_plus_splat_handler::generate_worldlabs_marble_0p1_plus_splat_handler,
|
||||
crate::http_server::endpoints::generate::video::generate_kling_1_6_pro_video_handler::generate_kling_1_6_pro_video_handler,
|
||||
crate::http_server::endpoints::generate::video::generate_kling_2_1_master_video_handler::generate_kling_2_1_master_video_handler,
|
||||
crate::http_server::endpoints::generate::video::generate_kling_2_1_pro_video_handler::generate_kling_2_1_pro_video_handler,
|
||||
@@ -726,6 +732,10 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
GenerateHunyuan21ImageTo3dResponse,
|
||||
GenerateHunyuan20ImageTo3dRequest,
|
||||
GenerateHunyuan20ImageTo3dResponse,
|
||||
GenerateWorldlabsMarble0p1MiniSplatRequest,
|
||||
GenerateWorldlabsMarble0p1MiniSplatResponse,
|
||||
GenerateWorldlabsMarble0p1PlusSplatRequest,
|
||||
GenerateWorldlabsMarble0p1PlusSplatResponse,
|
||||
GenerateKling16ProAspectRatio,
|
||||
GenerateKling16ProDuration,
|
||||
GenerateKling16ProImageToVideoRequest,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod refund_wallet_after_api_failure;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use log::{error, info};
|
||||
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
|
||||
use sqlx::Acquire;
|
||||
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
|
||||
|
||||
pub async fn refund_wallet_after_api_failure(
|
||||
ledger_entry_token: &WalletLedgerEntryToken,
|
||||
connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
) -> Result<(), CommonWebError> {
|
||||
let mut transaction = connection.begin().await.map_err(|err| {
|
||||
error!(
|
||||
"Failed to begin refund transaction after API failure (ledger {}): {:?}",
|
||||
ledger_entry_token.as_str(), err
|
||||
);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
match try_to_refund_ledger_entry(ledger_entry_token, &mut transaction).await {
|
||||
Ok(WalletRefundOutcome::Refunded(summary)) => {
|
||||
info!(
|
||||
"Refunded {} credits after API failure (ledger {} → refund ledger {}).",
|
||||
summary.refund_amount,
|
||||
ledger_entry_token.as_str(),
|
||||
summary.refund_ledger_entry_token.as_str(),
|
||||
);
|
||||
transaction.commit().await.map_err(|err| {
|
||||
error!(
|
||||
"Failed to commit refund after API failure (ledger {}): {:?}",
|
||||
ledger_entry_token.as_str(), err
|
||||
);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
}
|
||||
Ok(WalletRefundOutcome::AlreadyRefunded) => {
|
||||
info!(
|
||||
"Ledger entry {} was already refunded; no action needed.",
|
||||
ledger_entry_token.as_str()
|
||||
);
|
||||
let _ = transaction.rollback().await;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to refund ledger entry {} after API failure: {:?}",
|
||||
ledger_entry_token.as_str(), err
|
||||
);
|
||||
let _ = transaction.rollback().await;
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,4 +2,5 @@ pub mod common;
|
||||
pub mod cost_estimate;
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod splat;
|
||||
pub mod video;
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
|
||||
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;
|
||||
use crate::util::lookup::lookup_image_urls_as_map::lookup_image_urls_as_map;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_mini_splat::{GenerateWorldlabsMarble0p1MiniSplatRequest, GenerateWorldlabsMarble0p1MiniSplatResponse};
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::worldlabs::insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token::{insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token, InsertGenericInferenceForWorldlabsWithAprioriJobTokenArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
|
||||
use world_labs_api::api::requests::generate_world::generate_world::{generate_world, GenerateWorldArgs};
|
||||
use world_labs_api::api::requests::generate_world::http_request::{ContentReference, WorldPrompt};
|
||||
use world_labs_api::credentials::world_labs_api_creds::WorldLabsApiCreds;
|
||||
use world_labs_api::pricing::check_pricing::{calculate_cost, InputType};
|
||||
|
||||
/// World Labs Marble 0.1-mini Splat Generation
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Splats",
|
||||
path = "/v1/generate/splat/worldlabs_marble_0p1_mini",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = GenerateWorldlabsMarble0p1MiniSplatResponse),
|
||||
),
|
||||
params(
|
||||
("request" = GenerateWorldlabsMarble0p1MiniSplatRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn generate_worldlabs_marble_0p1_mini_splat_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<GenerateWorldlabsMarble0p1MiniSplatRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<GenerateWorldlabsMarble0p1MiniSplatResponse>, CommonWebError> {
|
||||
|
||||
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
|
||||
|
||||
if request.image_media_file_token.is_none() && request.prompt.is_none() {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
"At least one of image_media_file_token or prompt must be provided".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_token = match maybe_user_session.as_ref() {
|
||||
Some(session) => &session.user_token,
|
||||
None => {
|
||||
return Err(CommonWebError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
// Determine input type and calculate cost
|
||||
let input_type = if request.image_media_file_token.is_some() {
|
||||
InputType::ImageNonPanorama
|
||||
} else {
|
||||
InputType::Text
|
||||
};
|
||||
|
||||
let cost = calculate_cost(WorldLabsModel::Marble0p1Mini, input_type);
|
||||
let cost_in_cents = cost.us_dollar_cents as u64;
|
||||
|
||||
info!("Charging wallet: {} cents for Marble 0.1-mini splat", cost_in_cents);
|
||||
|
||||
let wallet_deduction = attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost_in_cents,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
|
||||
// Build WorldPrompt based on input
|
||||
let world_prompt = if let Some(image_token) = &request.image_media_file_token {
|
||||
let image_urls = lookup_image_urls_as_map(
|
||||
&http_request,
|
||||
&mut mysql_connection,
|
||||
server_state.server_environment,
|
||||
&[image_token.clone()],
|
||||
).await?;
|
||||
|
||||
let cdn_url = image_urls.get(image_token)
|
||||
.ok_or_else(|| {
|
||||
warn!("Image token not found in lookup results: {:?}", image_token);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
WorldPrompt::Image {
|
||||
image_prompt: ContentReference::Uri { uri: cdn_url.clone() },
|
||||
text_prompt: request.prompt.clone(),
|
||||
is_pano: None,
|
||||
disable_recaption: None,
|
||||
}
|
||||
} else {
|
||||
WorldPrompt::Text {
|
||||
text_prompt: request.prompt.clone(),
|
||||
disable_recaption: None,
|
||||
}
|
||||
};
|
||||
|
||||
// Call World Labs API to start generation
|
||||
let creds = WorldLabsApiCreds::new(server_state.worldlabs.api_key.clone());
|
||||
|
||||
let generate_result = match generate_world(GenerateWorldArgs {
|
||||
creds: &creds,
|
||||
world_prompt,
|
||||
display_name: None,
|
||||
model: WorldLabsModel::Marble0p1Mini,
|
||||
seed: None,
|
||||
tags: None,
|
||||
permission: None,
|
||||
request_timeout: None,
|
||||
}).await {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
warn!("World Labs generate_world error: {:?}", err);
|
||||
refund_wallet_after_api_failure(&wallet_deduction.ledger_entry_token, &mut mysql_connection).await?;
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
maybe_model_type: Some(ModelType::Marble0p1Mini),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(image_token) = &request.image_media_file_token {
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: vec![
|
||||
PromptContextItem {
|
||||
media_token: image_token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgref,
|
||||
}
|
||||
],
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token(InsertGenericInferenceForWorldlabsWithAprioriJobTokenArgs {
|
||||
apriori_job_token: &apriori_job_token,
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: generate_result.operation_id.as_str(),
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_wallet_ledger_entry_token: Some(&wallet_deduction.ledger_entry_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for WorldLabs queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(GenerateWorldlabsMarble0p1MiniSplatResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
|
||||
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;
|
||||
use crate::util::lookup::lookup_image_urls_as_map::lookup_image_urls_as_map;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::splat::generate_worldlabs_marble_0p1_plus_splat::{GenerateWorldlabsMarble0p1PlusSplatRequest, GenerateWorldlabsMarble0p1PlusSplatResponse};
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::worldlabs::insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token::{insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token, InsertGenericInferenceForWorldlabsWithAprioriJobTokenArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
|
||||
use world_labs_api::api::requests::generate_world::generate_world::{generate_world, GenerateWorldArgs};
|
||||
use world_labs_api::api::requests::generate_world::http_request::{ContentReference, WorldPrompt};
|
||||
use world_labs_api::credentials::world_labs_api_creds::WorldLabsApiCreds;
|
||||
use world_labs_api::pricing::check_pricing::{calculate_cost, InputType};
|
||||
|
||||
/// World Labs Marble 0.1-plus Splat Generation
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Splats",
|
||||
path = "/v1/generate/splat/worldlabs_marble_0p1_plus",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = GenerateWorldlabsMarble0p1PlusSplatResponse),
|
||||
),
|
||||
params(
|
||||
("request" = GenerateWorldlabsMarble0p1PlusSplatRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn generate_worldlabs_marble_0p1_plus_splat_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<GenerateWorldlabsMarble0p1PlusSplatRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<GenerateWorldlabsMarble0p1PlusSplatResponse>, CommonWebError> {
|
||||
|
||||
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
|
||||
|
||||
if request.image_media_file_token.is_none() && request.prompt.is_none() {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
"At least one of image_media_file_token or prompt must be provided".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_token = match maybe_user_session.as_ref() {
|
||||
Some(session) => &session.user_token,
|
||||
None => {
|
||||
return Err(CommonWebError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
// Determine input type and calculate cost
|
||||
let input_type = if request.image_media_file_token.is_some() {
|
||||
InputType::ImageNonPanorama
|
||||
} else {
|
||||
InputType::Text
|
||||
};
|
||||
|
||||
let cost = calculate_cost(WorldLabsModel::Marble0p1Plus, input_type);
|
||||
let cost_in_cents = cost.us_dollar_cents as u64;
|
||||
|
||||
info!("Charging wallet: {} cents for Marble 0.1-plus splat", cost_in_cents);
|
||||
|
||||
let wallet_deduction = attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost_in_cents,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
|
||||
// Build WorldPrompt based on input
|
||||
let world_prompt = if let Some(image_token) = &request.image_media_file_token {
|
||||
let image_urls = lookup_image_urls_as_map(
|
||||
&http_request,
|
||||
&mut mysql_connection,
|
||||
server_state.server_environment,
|
||||
&[image_token.clone()],
|
||||
).await?;
|
||||
|
||||
let cdn_url = image_urls.get(image_token)
|
||||
.ok_or_else(|| {
|
||||
warn!("Image token not found in lookup results: {:?}", image_token);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
WorldPrompt::Image {
|
||||
image_prompt: ContentReference::Uri { uri: cdn_url.clone() },
|
||||
text_prompt: request.prompt.clone(),
|
||||
is_pano: None,
|
||||
disable_recaption: None,
|
||||
}
|
||||
} else {
|
||||
WorldPrompt::Text {
|
||||
text_prompt: request.prompt.clone(),
|
||||
disable_recaption: None,
|
||||
}
|
||||
};
|
||||
|
||||
// Call World Labs API to start generation
|
||||
let creds = WorldLabsApiCreds::new(server_state.worldlabs.api_key.clone());
|
||||
|
||||
let generate_result = match generate_world(GenerateWorldArgs {
|
||||
creds: &creds,
|
||||
world_prompt,
|
||||
display_name: None,
|
||||
model: WorldLabsModel::Marble0p1Plus,
|
||||
seed: None,
|
||||
tags: None,
|
||||
permission: None,
|
||||
request_timeout: None,
|
||||
}).await {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
warn!("World Labs generate_world error: {:?}", err);
|
||||
refund_wallet_after_api_failure(&wallet_deduction.ledger_entry_token, &mut mysql_connection).await?;
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
maybe_model_type: Some(ModelType::Marble0p1Plus),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(image_token) = &request.image_media_file_token {
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: vec![
|
||||
PromptContextItem {
|
||||
media_token: image_token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgref,
|
||||
}
|
||||
],
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_token(InsertGenericInferenceForWorldlabsWithAprioriJobTokenArgs {
|
||||
apriori_job_token: &apriori_job_token,
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: generate_result.operation_id.as_str(),
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_wallet_ledger_entry_token: Some(&wallet_deduction.ledger_entry_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for WorldLabs queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(GenerateWorldlabsMarble0p1PlusSplatResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod generate_worldlabs_marble_0p1_mini_splat_handler;
|
||||
pub mod generate_worldlabs_marble_0p1_plus_splat_handler;
|
||||
+2
-49
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
|
||||
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;
|
||||
@@ -29,7 +30,6 @@ use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_it
|
||||
insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem,
|
||||
};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
|
||||
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use seedance2pro::requests::generate_video::generate_video::{
|
||||
generate_video, BatchCount, GenerateVideoArgs, Resolution,
|
||||
@@ -221,7 +221,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
|
||||
Ok(resp) => resp,
|
||||
Err(err) => {
|
||||
warn!("Error calling seedance2pro generate_video: {:?}", err);
|
||||
refund_after_api_failure(&deduction_result.ledger_entry_token, &mut mysql_connection).await;
|
||||
refund_wallet_after_api_failure(&deduction_result.ledger_entry_token, &mut mysql_connection).await?;
|
||||
|
||||
// TODO: We need to start returning robust failure messages to the caller.
|
||||
return Err(CommonWebError::ServerError);
|
||||
@@ -375,53 +375,6 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
|
||||
}))
|
||||
}
|
||||
|
||||
async fn refund_after_api_failure(
|
||||
ledger_entry_token: &tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken,
|
||||
connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
) {
|
||||
let mut transaction = match connection.begin().await {
|
||||
Ok(tx) => tx,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to begin refund transaction after API failure (ledger {}): {:?}",
|
||||
ledger_entry_token.as_str(), err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match try_to_refund_ledger_entry(ledger_entry_token, &mut transaction).await {
|
||||
Ok(WalletRefundOutcome::Refunded(summary)) => {
|
||||
info!(
|
||||
"Refunded {} credits after API failure (ledger {} → refund ledger {}).",
|
||||
summary.refund_amount,
|
||||
ledger_entry_token.as_str(),
|
||||
summary.refund_ledger_entry_token.as_str(),
|
||||
);
|
||||
if let Err(err) = transaction.commit().await {
|
||||
error!(
|
||||
"Failed to commit refund after API failure (ledger {}): {:?}",
|
||||
ledger_entry_token.as_str(), err
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(WalletRefundOutcome::AlreadyRefunded) => {
|
||||
info!(
|
||||
"Ledger entry {} was already refunded; no action needed.",
|
||||
ledger_entry_token.as_str()
|
||||
);
|
||||
let _ = transaction.rollback().await;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to refund ledger entry {} after API failure: {:?}",
|
||||
ledger_entry_token.as_str(), err
|
||||
);
|
||||
let _ = transaction.rollback().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn upload_to_seedance2pro(
|
||||
session: &Seedance2ProSession,
|
||||
our_cdn_url: &str,
|
||||
|
||||
+1
@@ -85,6 +85,7 @@ pub fn estimate_job_progress(job: &GenericInferenceJobStatus, maybe_args: Option
|
||||
InferenceCategory::Mocap => 0,
|
||||
InferenceCategory::VideoFilter => 0,
|
||||
InferenceCategory::ConvertBvhToWorkflow => 0,
|
||||
InferenceCategory::SplatGeneration => percent(duration_seconds, 60*5),
|
||||
InferenceCategory::DeprecatedField => 0, // TODO(bt,2024-07-16): Read job type instead.
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod common_requests;
|
||||
pub mod common_responses;
|
||||
pub mod endpoint_helpers;
|
||||
pub mod cookies;
|
||||
pub mod deprecated_endpoints;
|
||||
pub mod endpoints;
|
||||
|
||||
+12
@@ -21,6 +21,8 @@ use crate::http_server::endpoints::generate::image::multi_function::nano_banana_
|
||||
use crate::http_server::endpoints::generate::image::multi_function::nano_banana_pro_multi_function_image_gen_handler::nano_banana_pro_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::remove_image_background_handler::remove_image_background_handler;
|
||||
use crate::http_server::endpoints::generate::object::generate_hunyuan_2_0_image_to_3d_handler::generate_hunyuan_2_0_image_to_3d_handler;
|
||||
use crate::http_server::endpoints::generate::splat::generate_worldlabs_marble_0p1_mini_splat_handler::generate_worldlabs_marble_0p1_mini_splat_handler;
|
||||
use crate::http_server::endpoints::generate::splat::generate_worldlabs_marble_0p1_plus_splat_handler::generate_worldlabs_marble_0p1_plus_splat_handler;
|
||||
use crate::http_server::endpoints::generate::object::generate_hunyuan_2_1_image_to_3d_handler::generate_hunyuan_2_1_image_to_3d_handler;
|
||||
use crate::http_server::endpoints::generate::object::multi_function::generate_hunyuan3d_v3_multi_function_object_handler::generate_hunyuan3d_v3_multi_function_object_handler;
|
||||
use crate::http_server::endpoints::generate::video::generate_kling_1_6_pro_video_handler::generate_kling_1_6_pro_video_handler;
|
||||
@@ -248,5 +250,15 @@ where
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(web::scope("/splat")
|
||||
.service(web::resource("/worldlabs_marble_0p1_mini")
|
||||
.route(web::post().to(generate_worldlabs_marble_0p1_mini_splat_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/worldlabs_marble_0p1_plus")
|
||||
.route(web::post().to(generate_worldlabs_marble_0p1_plus_splat_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ use crate::http_server::web_utils::handle_multipart_error::handle_multipart_erro
|
||||
use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator;
|
||||
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
|
||||
use crate::state::memory_cache::model_token_to_info_cache::ModelTokenToInfoCache;
|
||||
use crate::state::server_state::{DurableInMemoryCaches, EnvConfig, EphemeralInMemoryCaches, FalData, InMemoryCaches, OpenAiData, ResendData, Seedance2ProData, ServerInfo, ServerState, StaticFeatureFlags, StripeSettings, TrollBans};
|
||||
use crate::state::server_state::{DurableInMemoryCaches, EnvConfig, EphemeralInMemoryCaches, FalData, InMemoryCaches, OpenAiData, ResendData, Seedance2ProData, ServerInfo, ServerState, StaticFeatureFlags, StripeSettings, TrollBans, WorldLabsData};
|
||||
use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus;
|
||||
use crate::threads::db_health_checker_thread::db_health_checker_thread::db_health_checker_thread;
|
||||
use crate::threads::poll_ip_banlist_thread::poll_ip_bans;
|
||||
@@ -413,6 +413,8 @@ async fn main() -> AnyhowResult<()> {
|
||||
|
||||
let resend_api_key = easyenv::get_env_string_required("RESEND_API_KEY")?;
|
||||
|
||||
let worldlabs_api_key = easyenv::get_env_string_required("WORLDLABS_API_KEY")?;
|
||||
|
||||
let startup_time = Utc::now();
|
||||
|
||||
let server_state = ServerState {
|
||||
@@ -477,6 +479,9 @@ async fn main() -> AnyhowResult<()> {
|
||||
resend: ResendData {
|
||||
api_key: resend_api_key,
|
||||
},
|
||||
worldlabs: WorldLabsData {
|
||||
api_key: worldlabs_api_key,
|
||||
},
|
||||
caches: InMemoryCaches {
|
||||
durable: DurableInMemoryCaches {
|
||||
model_token_info: model_token_info_cache,
|
||||
|
||||
@@ -98,7 +98,9 @@ pub struct ServerState {
|
||||
pub openai: OpenAiData,
|
||||
|
||||
pub resend: ResendData,
|
||||
|
||||
|
||||
pub worldlabs: WorldLabsData,
|
||||
|
||||
/// Where to store audio uploads for w2l
|
||||
pub audio_uploads_bucket_root: String,
|
||||
|
||||
@@ -331,3 +333,9 @@ pub struct OpenAiData {
|
||||
pub struct ResendData {
|
||||
pub api_key: String,
|
||||
}
|
||||
|
||||
/// World Labs integration
|
||||
#[derive(Clone)]
|
||||
pub struct WorldLabsData {
|
||||
pub api_key: String,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user