Compare commits

...
8 Commits
31 changed files with 613 additions and 90 deletions
+1 -1
View File
@@ -19,9 +19,9 @@ CREATE TABLE prompts (
-- Original product to which the prompt applied.
-- Prompts may or may not be compatible across products.
-- Possible values:
-- * 'artcraft_app'
-- * 'stable_diffusion' (legacy)
-- * 'comfy_ui' (legacy)
-- * 'artcraft_app'
prompt_type VARCHAR(16) NOT NULL,
-- What type of model was used to generate the result.
@@ -13,6 +13,7 @@ use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
use errors::AnyhowResult;
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::prompts::PromptToken;
use tokens::tokens::users::UserToken;
use crate::helpers::boolean_converters::i8_to_bool;
@@ -30,11 +31,35 @@ pub struct FalJobDetails {
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_anonymous_visitor_token: Option<AnonymousVisitorTrackingToken>,
pub creator_ip_address: String,
pub maybe_prompt_token: Option<PromptToken>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Default)]
struct RawJobRecord {
job_token: InferenceJobToken,
status: JobStatusPlus,
// NB: Nullable, but required to be filled for this query
external_third_party: Option<InferenceJobExternalThirdParty>,
// NB: Nullable, but required to be filled for this query
external_third_party_id: Option<String>,
maybe_creator_user_token: Option<UserToken>,
maybe_creator_anonymous_visitor_token: Option<AnonymousVisitorTrackingToken>,
creator_ip_address: String,
maybe_prompt_token: Option<PromptToken>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
/// Returns Ok(None) when the record cannot be found.
pub async fn get_inference_job_by_fal_id(fal_id: &str, mysql_pool: &MySqlPool)
-> AnyhowResult<Option<FalJobDetails>>
@@ -66,6 +91,8 @@ SELECT
jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,
jobs.creator_ip_address,
jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,
jobs.created_at,
jobs.updated_at
@@ -106,28 +133,8 @@ fn raw_record_to_public_result(record: RawJobRecord) -> AnyhowResult<FalJobDetai
maybe_creator_user_token: record.maybe_creator_user_token,
maybe_creator_anonymous_visitor_token: record.maybe_creator_anonymous_visitor_token,
creator_ip_address: record.creator_ip_address,
maybe_prompt_token: record.maybe_prompt_token,
created_at: record.created_at,
updated_at: record.updated_at,
})
}
#[derive(Debug, Default)]
struct RawJobRecord {
pub job_token: InferenceJobToken,
pub status: JobStatusPlus,
// NB: Nullable, but required to be filled for this query
pub external_third_party: Option<InferenceJobExternalThirdParty>,
// NB: Nullable, but required to be filled for this query
pub external_third_party_id: Option<String>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_anonymous_visitor_token: Option<AnonymousVisitorTrackingToken>,
pub creator_ip_address: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -1,6 +1,8 @@
use anyhow::anyhow;
use log::info;
use sqlx::MySqlPool;
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_input_source_token_type::InferenceInputSourceTokenType;
@@ -13,6 +15,7 @@ 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;
@@ -27,26 +30,35 @@ pub enum FalCategory {
ObjectGeneration,
}
pub struct InsertGenericInferenceForFalArgs<'a> {
pub uuid_idempotency_token: &'a str,
pub struct InsertGenericInferenceForFalArgs<'e, 'c, E>
where E: 'e + Executor<'c, Database = MySql>
{
pub uuid_idempotency_token: &'e str,
/// The external primary key identifier for the job.
pub maybe_external_third_party_id: &'a str,
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<&'a UserToken>,
pub maybe_avt_token: Option<&'a AnonymousVisitorTrackingToken>,
pub creator_ip_address: &'a str,
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_pool: &'a MySqlPool,
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>,
}
pub async fn insert_generic_inference_job_for_fal_queue(args: InsertGenericInferenceForFalArgs<'_>)
pub async fn insert_generic_inference_job_for_fal_queue<'e, 'c : 'e, E>(args: InsertGenericInferenceForFalArgs<'e, 'c, E>)
-> Result<InferenceJobToken, DatabaseQueryError>
where E: 'e + Executor<'c, Database = MySql>
{
let job_token = InferenceJobToken::generate();
@@ -105,6 +117,8 @@ SET
maybe_download_url = NULL,
maybe_cover_image_media_file_token = NULL,
maybe_prompt_token = ?,
maybe_raw_inference_text = NULL,
@@ -134,6 +148,8 @@ SET
product_category.to_str(),
inference_category.to_str(),
args.maybe_prompt_token.map(|t| t.to_string()),
serialized_args_payload,
@@ -145,7 +161,7 @@ SET
STATUS.to_str(),
);
let query_result = query.execute(args.mysql_pool)
let query_result = query.execute(args.mysql_executor)
.await;
let record_id = match query_result {
@@ -1,19 +1,18 @@
use crate::queries::media_files::create::generic_insert::insert_media_file_generic::{insert_media_file_generic, InsertArgs};
use anyhow::anyhow;
use crate::queries::media_files::create::insert_builder::media_file_insert_builder_error::MediaFileInsertBuilderError;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::generic_synthetic_ids::id_category::IdCategory;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::common::visibility::Visibility;
use errors::AnyhowResult;
use sqlx::MySqlPool;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use tokens::tokens::users::UserToken;
use crate::queries::media_files::create::insert_builder::media_file_insert_builder_error::MediaFileInsertBuilderError;
/// Builder for inserting a media files into the database.
/// Resembles an ORM, but can still use statically type checked queries (which is probably
@@ -53,7 +52,9 @@ pub struct MediaFileInsertBuilder {
checksum_sha2: Option<String>, // NB: Non-nullable field
// Media info for certain product areas
pub maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
maybe_prompt_token: Option<PromptToken>,
// // User text information
// maybe_title: Option<&'a str>,
@@ -100,6 +101,7 @@ impl MediaFileInsertBuilder {
checksum_sha2: None,
maybe_engine_category: None,
public_bucket_directory_hash: None,
maybe_prompt_token: None,
}
}
@@ -165,6 +167,11 @@ impl MediaFileInsertBuilder {
self
}
pub fn maybe_prompt_token(mut self, maybe_prompt_token: Option<&PromptToken>) -> Self {
self.maybe_prompt_token = maybe_prompt_token.map(|token| token.clone());
self
}
// TODO: Method for maybe_origin_model_type
// TODO: Method for maybe_origin_model_token
// TODO: Method for maybe_origin_filename
@@ -273,7 +280,7 @@ impl MediaFileInsertBuilder {
maybe_title: None, // TODO
maybe_text_transcript: None, // TODO
maybe_scene_source_media_file_token: None, // TODO
maybe_prompt_token: None, // TODO
maybe_prompt_token: self.maybe_prompt_token.as_ref(),
maybe_batch_token: None, // TODO
public_bucket_directory_hash: bucket_path.get_object_hash(),
maybe_public_bucket_prefix: bucket_path.get_optional_prefix(),
@@ -1,7 +1,7 @@
use chrono::{DateTime, Utc};
use sqlx::mysql::MySqlRow;
use sqlx::{Acquire, FromRow, MySql, MySqlPool, QueryBuilder, Row};
use sqlx::pool::PoolConnection;
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
@@ -84,6 +84,18 @@ pub async fn batch_get_media_files_by_tokens(
).await
}
pub async fn batch_get_media_files_by_tokens_with_connection(
mysql_connection: &mut PoolConnection<MySql>,
media_file_tokens: &[MediaFileToken],
can_see_deleted: bool
) -> AnyhowResult<Vec<MediaFilesByTokensRecord>> {
batch_get_media_files_by_tokens_with_transactor(
Transactor::Connection{ connection: mysql_connection },
media_file_tokens,
can_see_deleted
).await
}
pub async fn batch_get_media_files_by_tokens_with_transactor(
transactor: Transactor<'_, '_>,
media_file_tokens: &[MediaFileToken],
@@ -5,8 +5,6 @@
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use sqlx::MySqlPool;
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
@@ -16,6 +14,8 @@ use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::by_table::model_weights::weights_types::WeightsType;
use enums::common::visibility::Visibility;
use errors::AnyhowResult;
use sqlx::pool::PoolConnection;
use sqlx::{MySql, MySqlPool};
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::batch_generations::BatchGenerationToken;
use tokens::tokens::media_files::MediaFileToken;
@@ -215,6 +215,18 @@ pub async fn get_media_file(
).await
}
pub async fn get_media_file_with_connection(
media_file_token: &MediaFileToken,
can_see_deleted: bool,
mysql_connection: &mut PoolConnection<MySql>,
) -> AnyhowResult<Option<MediaFile>> {
get_media_file_with_transactor(
media_file_token,
can_see_deleted,
Transactor::for_connection(mysql_connection)
).await
}
pub async fn get_media_file_with_transactor(
media_file_token: &MediaFileToken,
can_see_deleted: bool,
@@ -6,6 +6,8 @@ use sqlx;
use sqlx::{Executor, MySql};
use enums::by_table::prompts::prompt_type::PromptType;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use errors::AnyhowResult;
use tokens::tokens::prompts::PromptToken;
use tokens::tokens::users::UserToken;
@@ -21,6 +23,10 @@ pub struct InsertPromptArgs<'e, 'c, E>
pub prompt_type: PromptType,
pub maybe_creator_user_token: Option<&'e UserToken>,
pub maybe_model_type: Option<ModelType>,
pub maybe_generation_provider: Option<GenerationProvider>,
pub maybe_positive_prompt: Option<&'e str>,
@@ -65,9 +71,13 @@ SET
prompt_type = ?,
maybe_creator_user_token = ?,
maybe_model_type = ?,
maybe_generation_provider = ?,
maybe_positive_prompt = ?,
maybe_negative_prompt = ?,
maybe_other_args = ?,
creator_ip_address = ?
@@ -75,6 +85,8 @@ SET
prompt_token.as_str(),
args.prompt_type.to_str(),
args.maybe_creator_user_token.map(|t| t.as_str()),
args.maybe_model_type.map(|m| m.to_str()),
args.maybe_generation_provider.map(|g| g.to_str()),
args.maybe_positive_prompt,
args.maybe_negative_prompt,
maybe_other_args,
@@ -13,6 +13,7 @@ pub mod media_files;
pub mod media_uploads;
pub mod model_categories;
pub mod model_weights;
pub mod prompt_context_items;
pub mod prompts;
pub mod tag_uses;
pub mod trending_model_analytics;
@@ -0,0 +1 @@
pub mod prompt_context_semantic_type;
@@ -0,0 +1,163 @@
use std::collections::BTreeSet;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
/// Used in the `prompt_context_items` table in a `VARCHAR(16)` field.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum PromptContextSemanticType {
/// Image-to-Video starting frame
VidStartFrame,
/// Image-to-Video ending frame
VidEndFrame,
/// Standard image reference without a semantic type (e.g. Sora/ChatGPT 4o/gpt-image-1)
Imgref,
ImgrefCharacter,
ImgrefStyle,
ImgrefBg,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(PromptContextSemanticType);
impl_mysql_enum_coders!(PromptContextSemanticType);
impl_mysql_from_row!(PromptContextSemanticType);
/// NB: Legacy API for older code.
impl PromptContextSemanticType {
pub fn to_str(&self) -> &'static str {
match self {
Self::VidStartFrame => "vid_start_frame",
Self::VidEndFrame => "vid_end_frame",
Self::Imgref => "imgref",
Self::ImgrefCharacter => "imgref_character",
Self::ImgrefStyle => "imgref_style",
Self::ImgrefBg => "imgref_bg",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"vid_start_frame" => Ok(Self::VidStartFrame),
"vid_end_frame" => Ok(Self::VidEndFrame),
"imgref" => Ok(Self::Imgref),
"imgref_character" => Ok(Self::ImgrefCharacter),
"imgref_style" => Ok(Self::ImgrefStyle),
"imgref_bg" => Ok(Self::ImgrefBg),
_ => Err(format!("invalid value: {:?}", value)),
}
}
pub fn all_variants() -> BTreeSet<Self> {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::VidStartFrame,
Self::VidEndFrame,
Self::Imgref,
Self::ImgrefCharacter,
Self::ImgrefStyle,
Self::ImgrefBg,
])
}
}
#[cfg(test)]
mod tests {
use crate::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
use crate::test_helpers::assert_serialization;
mod serde {
use super::*;
#[test]
fn test_serialization() {
assert_serialization(PromptContextSemanticType::VidStartFrame, "vid_start_frame");
assert_serialization(PromptContextSemanticType::VidEndFrame, "vid_end_frame");
assert_serialization(PromptContextSemanticType::Imgref, "imgref");
assert_serialization(PromptContextSemanticType::ImgrefCharacter, "imgref_character");
assert_serialization(PromptContextSemanticType::ImgrefStyle, "imgref_style");
assert_serialization(PromptContextSemanticType::ImgrefBg, "imgref_bg");
}
}
mod impl_methods {
use super::*;
#[test]
fn to_str() {
assert_eq!(PromptContextSemanticType::VidStartFrame.to_str(), "vid_start_frame");
assert_eq!(PromptContextSemanticType::VidEndFrame.to_str(), "vid_end_frame");
assert_eq!(PromptContextSemanticType::Imgref.to_str(), "imgref");
assert_eq!(PromptContextSemanticType::ImgrefCharacter.to_str(), "imgref_character");
assert_eq!(PromptContextSemanticType::ImgrefStyle.to_str(), "imgref_style");
assert_eq!(PromptContextSemanticType::ImgrefBg.to_str(), "imgref_bg");
}
#[test]
fn from_str() {
assert_eq!(PromptContextSemanticType::from_str("vid_start_frame").unwrap(), PromptContextSemanticType::VidStartFrame);
assert_eq!(PromptContextSemanticType::from_str("vid_end_frame").unwrap(), PromptContextSemanticType::VidEndFrame);
assert_eq!(PromptContextSemanticType::from_str("imgref").unwrap(), PromptContextSemanticType::Imgref);
assert_eq!(PromptContextSemanticType::from_str("imgref_character").unwrap(), PromptContextSemanticType::ImgrefCharacter);
assert_eq!(PromptContextSemanticType::from_str("imgref_style").unwrap(), PromptContextSemanticType::ImgrefStyle);
assert_eq!(PromptContextSemanticType::from_str("imgref_bg").unwrap(), PromptContextSemanticType::ImgrefBg);
assert!(PromptContextSemanticType::from_str("foo").is_err());
}
}
mod manual_variant_checks {
use super::*;
#[test]
fn all_variants() {
let mut variants = PromptContextSemanticType::all_variants();
assert_eq!(variants.len(), 6);
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::VidStartFrame));
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::VidEndFrame));
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::Imgref));
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::ImgrefCharacter));
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::ImgrefStyle));
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::ImgrefBg));
assert_eq!(variants.pop_first(), None);
}
}
mod mechanical_checks {
use super::*;
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(PromptContextSemanticType::all_variants().len(), PromptContextSemanticType::iter().len());
}
#[test]
fn round_trip() {
for variant in PromptContextSemanticType::all_variants() {
assert_eq!(variant, PromptContextSemanticType::from_str(variant.to_str()).unwrap());
assert_eq!(variant, PromptContextSemanticType::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, PromptContextSemanticType::from_str(&format!("{:?}", variant)).unwrap());
}
}
#[test]
fn serialized_length_ok_for_database() {
const MAX_LENGTH : usize = 16;
for variant in PromptContextSemanticType::all_variants() {
let serialized = variant.to_str();
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
}
}
}
}
@@ -13,10 +13,15 @@ use utoipa::ToSchema;
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum PromptType {
/// Artcraft (App)
ArtcraftApp,
/// Stable diffusion
#[deprecated]
StableDiffusion,
/// Comfy UI
#[deprecated]
ComfyUi,
}
@@ -29,6 +34,7 @@ impl_mysql_from_row!(PromptType);
impl PromptType {
pub fn to_str(&self) -> &'static str {
match self {
Self::ArtcraftApp => "artcraft_app",
Self::StableDiffusion => "stable_diffusion",
Self::ComfyUi => "comfy_ui",
}
@@ -36,6 +42,7 @@ impl PromptType {
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"artcraft_app" => Ok(Self::ArtcraftApp),
"stable_diffusion" => Ok(Self::StableDiffusion),
"comfy_ui" => Ok(Self::ComfyUi),
_ => Err(format!("invalid value: {:?}", value)),
@@ -46,6 +53,7 @@ impl PromptType {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::ArtcraftApp,
Self::StableDiffusion,
Self::ComfyUi,
])
@@ -62,6 +70,7 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(PromptType::ArtcraftApp, "artcraft_app");
assert_serialization(PromptType::StableDiffusion, "stable_diffusion");
assert_serialization(PromptType::ComfyUi, "comfy_ui");
}
@@ -72,12 +81,14 @@ mod tests {
#[test]
fn to_str() {
assert_eq!(PromptType::ArtcraftApp.to_str(), "artcraft_app");
assert_eq!(PromptType::StableDiffusion.to_str(), "stable_diffusion");
assert_eq!(PromptType::ComfyUi.to_str(), "comfy_ui");
}
#[test]
fn from_str() {
assert_eq!(PromptType::from_str("artcraft_app").unwrap(), PromptType::ArtcraftApp);
assert_eq!(PromptType::from_str("stable_diffusion").unwrap(), PromptType::StableDiffusion);
assert_eq!(PromptType::from_str("comfy_ui").unwrap(), PromptType::ComfyUi);
assert!(PromptType::from_str("foo").is_err());
@@ -90,7 +101,8 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = PromptType::all_variants();
assert_eq!(variants.len(), 2);
assert_eq!(variants.len(), 3);
assert_eq!(variants.pop_first(), Some(PromptType::ArtcraftApp));
assert_eq!(variants.pop_first(), Some(PromptType::StableDiffusion));
assert_eq!(variants.pop_first(), Some(PromptType::ComfyUi));
assert_eq!(variants.pop_first(), None);
@@ -12,9 +12,37 @@ use utoipa::ToSchema;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ModelType {
Artcraft,
Fal,
Sora,
// Image models
#[serde(rename = "flux_1_dev")]
Flux1Dev,
#[serde(rename = "flux_1_schnell")]
Flux1Schnell,
#[serde(rename = "flux_pro_1p1")]
FluxPro11,
#[serde(rename = "flux_pro_1p1_ultra")]
FluxPro11Ultra,
#[serde(rename = "gpt_image_1")]
GptImage1,
#[serde(rename = "recraft_3")]
Recraft3,
// Video models
#[serde(rename = "kling_1p6_pro")]
Kling16Pro,
#[serde(rename = "kling_2p1_pro")]
Kling21Pro,
#[serde(rename = "kling_2p1_master")]
Kling21Master,
#[serde(rename = "seedance_1p0_lite")]
Seedance10Lite,
#[serde(rename = "veo_2")]
Veo2,
// 3D Object generation models
#[serde(rename = "hunyuan_3d_2p0")]
Hunyuan3d2_0,
#[serde(rename = "hunyuan_3d_2p1")]
Hunyuan3d2_1,
}
impl_enum_display_and_debug_using_to_str!(ModelType);
@@ -26,17 +54,48 @@ impl_mysql_from_row!(ModelType);
impl ModelType {
pub fn to_str(&self) -> &'static str {
match self {
Self::Artcraft => "artcraft",
Self::Fal => "fal",
Self::Sora => "sora",
// Image models
Self::Flux1Dev => "flux_1_dev",
Self::Flux1Schnell => "flux_1_schnell",
Self::FluxPro11 => "flux_pro_1p1",
Self::FluxPro11Ultra => "flux_pro_1p1_ultra",
Self::GptImage1 => "gpt_image_1",
Self::Recraft3 => "recraft_3",
// Video models
Self::Kling16Pro => "kling_1p6_pro",
Self::Kling21Pro => "kling_2p1_pro",
Self::Kling21Master => "kling_2p1_master",
Self::Seedance10Lite => "seedance_1p0_lite",
Self::Veo2 => "veo_2",
// 3D Object generation models
Self::Hunyuan3d2_0 => "hunyuan_3d_2p0",
Self::Hunyuan3d2_1 => "hunyuan_3d_2p1",
}
}
pub fn from_str(job_status: &str) -> Result<Self, String> {
match job_status {
"artcraft" => Ok(Self::Artcraft),
"fal" => Ok(Self::Fal),
"sora" => Ok(Self::Sora),
// Image models
"flux_1_dev" => Ok(Self::Flux1Dev),
"flux_1_schnell" => Ok(Self::Flux1Schnell),
"flux_pro_1p1" => Ok(Self::FluxPro11),
"flux_pro_1p1_ultra" => Ok(Self::FluxPro11Ultra),
"gpt_image_1" => Ok(Self::GptImage1),
"recraft_3" => Ok(Self::Recraft3),
// Video models
"kling_1p6_pro" => Ok(Self::Kling16Pro),
"kling_2p1_pro" => Ok(Self::Kling21Pro),
"kling_2p1_master" => Ok(Self::Kling21Master),
"seedance_1p0_lite" => Ok(Self::Seedance10Lite),
"veo_2" => Ok(Self::Veo2),
// 3D Object generation models
"hunyuan_3d_2p0" => Ok(Self::Hunyuan3d2_0),
"hunyuan_3d_2p1" => Ok(Self::Hunyuan3d2_1),
_ => Err(format!("invalid task_state: {:?}", job_status)),
}
}
@@ -45,9 +104,24 @@ impl ModelType {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::Artcraft,
Self::Fal,
Self::Sora,
// Image models
Self::Flux1Dev,
Self::Flux1Schnell,
Self::FluxPro11,
Self::FluxPro11Ultra,
Self::GptImage1,
Self::Recraft3,
// Video models
Self::Kling16Pro,
Self::Kling21Pro,
Self::Kling21Master,
Self::Seedance10Lite,
Self::Veo2,
// 3D Object generation models
Self::Hunyuan3d2_0,
Self::Hunyuan3d2_1,
])
}
}
@@ -62,32 +136,87 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(ModelType::Artcraft, "artcraft");
assert_serialization(ModelType::Fal, "fal");
assert_serialization(ModelType::Sora, "sora");
// Image models
assert_serialization(ModelType::Flux1Dev, "flux_1_dev");
assert_serialization(ModelType::Flux1Schnell, "flux_1_schnell");
assert_serialization(ModelType::FluxPro11, "flux_pro_1p1");
assert_serialization(ModelType::FluxPro11Ultra, "flux_pro_1p1_ultra");
assert_serialization(ModelType::GptImage1, "gpt_image_1");
assert_serialization(ModelType::Recraft3, "recraft_3");
// Video models
assert_serialization(ModelType::Kling16Pro, "kling_1p6_pro");
assert_serialization(ModelType::Kling21Pro, "kling_2p1_pro");
assert_serialization(ModelType::Kling21Master, "kling_2p1_master");
assert_serialization(ModelType::Seedance10Lite, "seedance_1p0_lite");
assert_serialization(ModelType::Veo2, "veo_2");
// 3D Object generation models
assert_serialization(ModelType::Hunyuan3d2_0, "hunyuan_3d_2p0");
assert_serialization(ModelType::Hunyuan3d2_1, "hunyuan_3d_2p1");
}
#[test]
fn to_str() {
assert_eq!(ModelType::Artcraft.to_str(), "artcraft");
assert_eq!(ModelType::Fal.to_str(), "fal");
assert_eq!(ModelType::Sora.to_str(), "sora");
// Image models
assert_eq!(ModelType::Flux1Dev.to_str(), "flux_1_dev");
assert_eq!(ModelType::Flux1Schnell.to_str(), "flux_1_schnell");
assert_eq!(ModelType::FluxPro11.to_str(), "flux_pro_1p1");
assert_eq!(ModelType::FluxPro11Ultra.to_str(), "flux_pro_1p1_ultra");
assert_eq!(ModelType::GptImage1.to_str(), "gpt_image_1");
assert_eq!(ModelType::Recraft3.to_str(), "recraft_3");
// Video models
assert_eq!(ModelType::Kling16Pro.to_str(), "kling_1p6_pro");
assert_eq!(ModelType::Kling21Pro.to_str(), "kling_2p1_pro");
assert_eq!(ModelType::Kling21Master.to_str(), "kling_2p1_master");
assert_eq!(ModelType::Seedance10Lite.to_str(), "seedance_1p0_lite");
assert_eq!(ModelType::Veo2.to_str(), "veo_2");
// 3D Object generation models
assert_eq!(ModelType::Hunyuan3d2_0.to_str(), "hunyuan_3d_2p0");
assert_eq!(ModelType::Hunyuan3d2_1.to_str(), "hunyuan_3d_2p1");
}
#[test]
fn from_str() {
assert_eq!(ModelType::from_str("artcraft").unwrap(), ModelType::Artcraft);
assert_eq!(ModelType::from_str("fal").unwrap(), ModelType::Fal);
assert_eq!(ModelType::from_str("sora").unwrap(), ModelType::Sora);
// Image models
assert_eq!(ModelType::from_str("flux_1_dev").unwrap(), ModelType::Flux1Dev);
assert_eq!(ModelType::from_str("flux_1_schnell").unwrap(), ModelType::Flux1Schnell);
assert_eq!(ModelType::from_str("flux_pro_1p1").unwrap(), ModelType::FluxPro11);
assert_eq!(ModelType::from_str("flux_pro_1p1_ultra").unwrap(), ModelType::FluxPro11Ultra);
assert_eq!(ModelType::from_str("gpt_image_1").unwrap(), ModelType::GptImage1);
assert_eq!(ModelType::from_str("recraft_3").unwrap(), ModelType::Recraft3);
// Video models
assert_eq!(ModelType::from_str("kling_1p6_pro").unwrap(), ModelType::Kling16Pro);
assert_eq!(ModelType::from_str("kling_2p1_pro").unwrap(), ModelType::Kling21Pro);
assert_eq!(ModelType::from_str("kling_2p1_master").unwrap(), ModelType::Kling21Master);
assert_eq!(ModelType::from_str("seedance_1p0_lite").unwrap(), ModelType::Seedance10Lite);
assert_eq!(ModelType::from_str("veo_2").unwrap(), ModelType::Veo2);
// 3D Object generation models
assert_eq!(ModelType::from_str("hunyuan_3d_2p0").unwrap(), ModelType::Hunyuan3d2_0);
assert_eq!(ModelType::from_str("hunyuan_3d_2p1").unwrap(), ModelType::Hunyuan3d2_1);
}
#[test]
fn all_variants() {
let mut variants = ModelType::all_variants();
assert_eq!(variants.len(), 3);
assert_eq!(variants.pop_first(), Some(ModelType::Artcraft));
assert_eq!(variants.pop_first(), Some(ModelType::Fal));
assert_eq!(variants.pop_first(), Some(ModelType::Sora));
assert_eq!(variants.len(), 13);
// Image models
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev));
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell));
assert_eq!(variants.pop_first(), Some(ModelType::FluxPro11));
assert_eq!(variants.pop_first(), Some(ModelType::FluxPro11Ultra));
assert_eq!(variants.pop_first(), Some(ModelType::GptImage1));
assert_eq!(variants.pop_first(), Some(ModelType::Recraft3));
// Video models
assert_eq!(variants.pop_first(), Some(ModelType::Kling16Pro));
assert_eq!(variants.pop_first(), Some(ModelType::Kling21Pro));
assert_eq!(variants.pop_first(), Some(ModelType::Kling21Master));
assert_eq!(variants.pop_first(), Some(ModelType::Seedance10Lite));
assert_eq!(variants.pop_first(), Some(ModelType::Veo2));
// 3D Object generation models
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_0));
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_1));
assert_eq!(variants.pop_first(), None);
}
}
@@ -15,6 +15,8 @@ use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginM
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::prompts::prompt_type::PromptType;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use filesys::path_to_string::path_to_string;
use mysql_queries::payloads::media_file_extra_info::inner_payloads::stable_diffusion_extra_info::StableDiffusionExtraInfo;
use mysql_queries::payloads::media_file_extra_info::media_file_extra_info::MediaFileExtraInfo;
@@ -328,6 +330,8 @@ pub async fn process_job_inference(
maybe_apriori_prompt_token: Some(&prompt_token),
prompt_type: PromptType::StableDiffusion,
maybe_creator_user_token: job.maybe_creator_user_token_typed.as_ref(),
maybe_model_type: None,
maybe_generation_provider: None,
maybe_positive_prompt: Some(&positive_prompt),
maybe_negative_prompt: maybe_negative_prompt.as_deref(),
maybe_other_args: None, // TODO(bt,2024-02-22): Support other arguments
@@ -8,6 +8,8 @@ use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path:
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::prompts::prompt_type::PromptType;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use filesys::file_size::file_size;
use filesys::path_to_string::path_to_string;
use hashing::sha256::sha256_hash_file::sha256_hash_file;
@@ -290,6 +292,8 @@ pub async fn validate_and_save_results(args: SaveResultsArgs<'_>) -> Result<Medi
maybe_apriori_prompt_token: Some(&prompt_token),
prompt_type: PromptType::ComfyUi,
maybe_creator_user_token: args.job.maybe_creator_user_token_typed.as_ref(),
maybe_model_type: None,
maybe_generation_provider: None,
maybe_positive_prompt: args.comfy_args.positive_prompt.as_deref(),
maybe_negative_prompt: args.comfy_args.negative_prompt.as_deref(),
maybe_other_args: maybe_other_args.as_ref(),
@@ -9,6 +9,9 @@ use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest, GptImage1EditImageResponse};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
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 fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::enqueue_gpt_image_1_edit_image_webhook;
@@ -20,7 +23,9 @@ use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::batch_get_media_files_by_tokens;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use utoipa::ToSchema;
/// Gpt Image 1 Image Editing
@@ -40,9 +45,17 @@ pub async fn gpt_image_1_edit_image_handler(
request: Json<GptImage1EditImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GptImage1EditImageResponse>, CommonWebError> {
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await
.map_err(|err| {
error!("MySql pool error: {:?}", err);
CommonWebError::ServerError
})?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
@@ -77,8 +90,8 @@ pub async fn gpt_image_1_edit_image_handler(
}
};
let result = batch_get_media_files_by_tokens(
&server_state.mysql_pool,
let result = batch_get_media_files_by_tokens_with_connection(
&mut mysql_connection,
tokens,
CAN_SEE_DELETED,
).await;
@@ -116,7 +129,7 @@ pub async fn gpt_image_1_edit_image_handler(
})
.collect::<Vec<_>>();
insert_idempotency_token(&request.uuid_idempotency_token, &server_state.mysql_pool)
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
.await
.map_err(|err| {
error!("Error inserting idempotency token: {:?}", err);
@@ -180,16 +193,51 @@ pub async fn gpt_image_1_edit_image_handler(
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
})?;
// NB: Don't fail the job if the query fails.
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_session
.as_ref()
.map(|s| &s.user_token),
maybe_model_type: Some(ModelType::GptImage1),
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 // Don't fail the job if the prompt insertion fails.
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ImageGeneration,
maybe_inference_args: None,
maybe_prompt_token: prompt_token.as_ref(),
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &mut *transaction,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -199,6 +247,14 @@ pub async fn gpt_image_1_edit_image_handler(
return Err(CommonWebError::ServerError);
}
};
let _r = transaction
.commit()
.await
.map_err(|err| {
error!("Error committing MySQL transaction: {:?}", err);
CommonWebError::ServerError
})?;
Ok(Json(GptImage1EditImageResponse {
success: true,
@@ -120,11 +120,13 @@ pub async fn generate_flux_1_dev_text_to_image_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ImageGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -119,12 +119,14 @@ pub async fn generate_flux_1_schnell_text_to_image_handler(
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ImageGeneration,
maybe_prompt_token: None,
maybe_inference_args: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -121,11 +121,13 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ImageGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -121,11 +121,13 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ImageGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -145,11 +145,13 @@ pub async fn remove_image_background_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::BackgroundRemoval,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -146,11 +146,13 @@ pub async fn generate_hunyuan_2_0_image_to_3d_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ObjectGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -146,11 +146,13 @@ pub async fn generate_hunyuan_2_1_image_to_3d_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::ObjectGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -191,11 +191,13 @@ pub async fn generate_kling_1_6_pro_video_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -192,11 +192,13 @@ pub async fn generate_kling_2_1_master_video_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -192,11 +192,13 @@ pub async fn generate_kling_2_1_pro_video_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -171,11 +171,13 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_prompt_token: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &server_state.mysql_pool,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -2,16 +2,21 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use anyhow::anyhow;
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::GenerateVeo2AspectRatio;
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::GenerateVeo2Duration;
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::GenerateVeo2ImageToVideoRequest;
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::GenerateVeo2ImageToVideoResponse;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
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 fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::enqueue_veo_2_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2Args;
@@ -23,7 +28,10 @@ use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::get_media_file::get_media_file;
use mysql_queries::queries::media_files::get::get_media_file::get_media_file_with_connection;
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::prompts::PromptToken;
use utoipa::ToSchema;
/// Veo 2 Image to Video
@@ -43,9 +51,17 @@ pub async fn generate_veo_2_image_to_video_handler(
request: Json<GenerateVeo2ImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateVeo2ImageToVideoResponse>, CommonWebError> {
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await
.map_err(|err| {
error!("MySql pool error: {:?}", err);
CommonWebError::ServerError
})?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
@@ -78,7 +94,7 @@ pub async fn generate_veo_2_image_to_video_handler(
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
}
insert_idempotency_token(&request.uuid_idempotency_token, &server_state.mysql_pool)
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
.await
.map_err(|err| {
error!("Error inserting idempotency token: {:?}", err);
@@ -86,10 +102,10 @@ pub async fn generate_veo_2_image_to_video_handler(
})?;
const IS_MOD : bool = false;
let media_file_lookup_result = get_media_file(
let media_file_lookup_result = get_media_file_with_connection(
media_file_token,
IS_MOD,
&server_state.mysql_pool,
&mut mysql_connection,
).await;
let media_file = match media_file_lookup_result {
@@ -169,16 +185,53 @@ pub async fn generate_veo_2_image_to_video_handler(
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
})?;
// NB: Don't fail the job if the query fails.
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_session
.as_ref()
.map(|s| &s.user_token),
maybe_model_type: Some(ModelType::Veo2),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: Some(prompt),
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 // Don't fail the job if the prompt insertion fails.
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_prompt_token: prompt_token.as_ref(),
maybe_creator_user_token: maybe_user_session
.as_ref()
.map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_pool: &server_state.mysql_pool,
mysql_executor: &mut *transaction,
phantom: Default::default(),
}).await;
let job_token = match db_result {
@@ -189,6 +242,14 @@ pub async fn generate_veo_2_image_to_video_handler(
}
};
let _r = transaction
.commit()
.await
.map_err(|err| {
error!("Error committing MySQL transaction: {:?}", err);
CommonWebError::ServerError
})?;
Ok(Json(GenerateVeo2ImageToVideoResponse {
success: true,
inference_job_token: job_token,
@@ -86,6 +86,7 @@ pub async fn handle_image_payload(
.media_file_class(MediaFileClass::Image)
.media_file_type(media_file_type)
.media_file_origin_category(MediaFileOriginCategory::Inference)
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
//.media_file_origin_product_category(MediaFileOriginProductCategory::Unknown)
.mime_type(mime_type)
.file_size_bytes(file_size_bytes as u64)
@@ -109,6 +109,7 @@ pub async fn upload_image(
.file_size_bytes(file_size_bytes as u64)
.frame_width(image_info.width())
.frame_height(image_info.height())
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
.checksum_sha2(&file_hash)
.insert_pool(&server_state.mysql_pool)
.await?;
@@ -89,6 +89,7 @@ pub async fn handle_model_mesh_payload(
//.media_file_origin_product_category(MediaFileOriginProductCategory::Unknown)
.mime_type(mime_type)
.file_size_bytes(file_size_bytes as u64)
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
.checksum_sha2(&file_hash)
.insert_pool(&server_state.mysql_pool)
.await?;
@@ -119,6 +119,7 @@ pub async fn handle_video_payload(
.maybe_frame_height(maybe_frame_height)
.maybe_duration_millis(maybe_duration_millis)
.checksum_sha2(&file_hash)
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
.insert_pool(&server_state.mysql_pool)
.await?;