diff --git a/crates/schema/database/mysql_queries/src/queries/stats/get_unified_queue_stats.rs b/crates/schema/database/mysql_queries/src/queries/stats/get_unified_queue_stats.rs deleted file mode 100644 index b45d348d8e..0000000000 --- a/crates/schema/database/mysql_queries/src/queries/stats/get_unified_queue_stats.rs +++ /dev/null @@ -1,66 +0,0 @@ -use chrono::NaiveDateTime; -use sqlx::MySqlPool; - -use errors::AnyhowResult; - -use crate::helpers::numeric_converters::try_i64_to_u64_or_min; - -#[derive(Serialize, Deserialize, Clone)] -pub struct QueueStatsRow { - pub queue_type: String, - pub pending_job_count: u64, - pub present_time: NaiveDateTime, -} - -struct QueueStatsRowInternal { - queue_type: Option, // FIXME: Shouldn't be nullable - pending_job_count: i64, - present_time: NaiveDateTime, -} - -// NB: Returning Option is a hack to fit with outer Redis caching. -// I'm only doing this because I'm in a hurry. Don't do this. -pub async fn get_unified_queue_stats( - pool: &MySqlPool, -) -> AnyhowResult> { - // NB (1): We query as a union since "started" jobs can get picked off mid-run and go stale - // forever. We currently have no automated means of collecting those jobs. - // NB (2): We query the server timestamp so we can cache the results. - // The frontend can then monotonically adjust the count based on timestamp by ignoring - // past times. - let rows : Vec = sqlx::query_as!( - QueueStatsRowInternal, - r#" -SELECT - job_type as queue_type, - count(*) as pending_job_count, - NOW() as present_time - FROM ( - SELECT - token, - job_type - FROM generic_inference_jobs - WHERE status IN ("pending", "attempt_failed") - AND job_type IS NOT NULL - UNION - SELECT - token, - job_type - FROM generic_inference_jobs - WHERE status IN ("started") - AND created_at > (CURDATE() - INTERVAL 15 MINUTE) - AND job_type IS NOT NULL -) as generic_inner -GROUP BY queue_type - "#, - ) - .fetch_all(pool) - .await?; - - Ok(rows.into_iter().map(|row| - QueueStatsRow { - queue_type: row.queue_type.unwrap_or_else(|| "unknown".to_string()), - pending_job_count: try_i64_to_u64_or_min(row.pending_job_count), - present_time: row.present_time, - }).collect::>()) -} diff --git a/crates/schema/database/mysql_queries/src/queries/stats/mod.rs b/crates/schema/database/mysql_queries/src/queries/stats/mod.rs index d39b91d6ac..7b8dbf7742 100644 --- a/crates/schema/database/mysql_queries/src/queries/stats/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/stats/mod.rs @@ -1,3 +1,2 @@ pub mod get_on_prem_worker_stats; -pub mod get_unified_queue_stats; -pub mod get_voice_count_stats; \ No newline at end of file +pub mod get_voice_count_stats; diff --git a/crates/schema/database/mysql_queries/src/queries/tts/mod.rs b/crates/schema/database/mysql_queries/src/queries/tts/mod.rs index 08d96b6d8b..f0fb0d5730 100644 --- a/crates/schema/database/mysql_queries/src/queries/tts/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/tts/mod.rs @@ -1,7 +1,6 @@ -pub mod stats; pub mod tts_category_assignments; pub mod tts_download_jobs; pub mod tts_inference_jobs; pub mod tts_model_upload_jobs; pub mod tts_models; -pub mod tts_results; \ No newline at end of file +pub mod tts_results; diff --git a/crates/schema/database/mysql_queries/src/queries/tts/stats/calculate_tts_model_leaderboard.rs b/crates/schema/database/mysql_queries/src/queries/tts/stats/calculate_tts_model_leaderboard.rs deleted file mode 100644 index 079a81034b..0000000000 --- a/crates/schema/database/mysql_queries/src/queries/tts/stats/calculate_tts_model_leaderboard.rs +++ /dev/null @@ -1,89 +0,0 @@ -use anyhow::anyhow; -use log::warn; -use sqlx::MySql; -use sqlx::pool::PoolConnection; - -use errors::AnyhowResult; - -#[derive(Serialize, Clone)] -pub struct TtsLeaderboardRecordForList { - pub username: String, - pub display_name: String, - pub gravatar_hash: String, - pub creator_user_token: String, - pub uploaded_count: i64, -} - -#[derive(Serialize)] -struct TtsLeaderboardRecordForListRaw { - pub username: String, - pub display_name: String, - pub gravatar_hash: String, - pub creator_user_token: String, - pub uploaded_count: i64, -} - -// NB: This may need to become an offline query if it is expensive. -pub async fn calculate_tts_model_leaderboard( - mysql_pool: &mut PoolConnection -) -> AnyhowResult> { - - // NB(1): We're not requiring "is_public_listing_approved IS TRUE" for TTS models - // This is the opposite for W2L templates (for now at least) - // NB(2): We're excluding a few users (eg. Vegito's alternate account) - // U:DRB1SKEMKHT1J - https://fakeyou.com/profile/bluefusion/uploaded-tts-models - let maybe_results = sqlx::query_as!( - TtsLeaderboardRecordForListRaw, - r#" -SELECT - users.username, - users.display_name, - users.email_gravatar_hash as gravatar_hash, - creator_user_token, - count(*) as uploaded_count -FROM tts_models -JOIN users -ON - users.token = creator_user_token -WHERE - users.is_banned IS FALSE - AND users.token NOT IN ("U:DRB1SKEMKHT1J") - AND tts_models.user_deleted_at IS NULL - AND tts_models.mod_deleted_at IS NULL -GROUP BY creator_user_token -ORDER BY uploaded_count desc -LIMIT 25; - "#, - ) - .fetch_all(&mut **mysql_pool) - .await; - - let results : Vec = match maybe_results { - Ok(results) => results, - Err(err) => { - warn!("Error: {:?}", err); - - match err { - sqlx::Error::RowNotFound => { - return Ok(Vec::new()); - }, - _ => { - warn!("tts leaderboard query error: {:?}", err); - return Err(anyhow!("tts leaderboard query error")); - } - } - } - }; - - Ok(results.into_iter() - .map(|result| { - TtsLeaderboardRecordForList { - username: result.username.to_string(), - display_name: result.display_name.to_string(), - gravatar_hash: result.gravatar_hash.to_string(), - creator_user_token: result.creator_user_token.to_string(), - uploaded_count: result.uploaded_count, - } - }) - .collect::>()) -} diff --git a/crates/schema/database/mysql_queries/src/queries/tts/stats/mod.rs b/crates/schema/database/mysql_queries/src/queries/tts/stats/mod.rs deleted file mode 100644 index c0e5981099..0000000000 --- a/crates/schema/database/mysql_queries/src/queries/tts/stats/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod calculate_tts_model_leaderboard; \ No newline at end of file diff --git a/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/insert_tts_model_upload_job.rs b/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/insert_tts_model_upload_job.rs deleted file mode 100644 index 645f06d46e..0000000000 --- a/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/insert_tts_model_upload_job.rs +++ /dev/null @@ -1,82 +0,0 @@ -use anyhow::anyhow; -use log::warn; -use sqlx::error::Error::Database; -use sqlx::MySqlPool; - -use errors::AnyhowResult; -use tokens::tokens::tts_model_upload_jobs::TtsModelUploadJobToken; - -pub struct InsertTtsModelUploadJobArgs<'a> { - pub uuid: &'a str, - pub creator_user_token: &'a str, - pub creator_ip_address: &'a str, - pub creator_set_visibility: &'a str, - pub title: &'a str, - pub tts_model_type: &'a str, - pub download_url: &'a str, - - pub mysql_pool: &'a MySqlPool, -} - -// NB: Returns job token -pub async fn insert_tts_model_upload_job(args: InsertTtsModelUploadJobArgs<'_>) -> AnyhowResult { - // This token is returned to the client. - let job_token = TtsModelUploadJobToken::generate().to_string(); - - let query_result = sqlx::query!( - r#" -INSERT INTO tts_model_upload_jobs -SET - token = ?, - uuid_idempotency_token = ?, - creator_user_token = ?, - creator_ip_address = ?, - creator_set_visibility = ?, - title = ?, - tts_model_type = ?, - download_url = ?, - status = "pending" - "#, - job_token.to_string(), - args.uuid, - args.creator_user_token, - args.creator_ip_address, - args.creator_set_visibility, - args.title, - args.tts_model_type, - args.download_url, - ) - .execute(args.mysql_pool) - .await; - - let _record_id = match query_result { - Ok(res) => { - res.last_insert_id() - }, - Err(err) => { - warn!("New tts model upload creation DB error: {:?}", err); - - // NB: SQLSTATE[23000]: Integrity constraint violation - // NB: MySQL Error Code 1062: Duplicate key insertion (this is harder to access) - match err { - Database(ref err) => { - let _maybe_code = err.code().map(|c| c.into_owned()); - /*match maybe_code.as_deref() { - Some("23000") => { - if err.message().contains("username") { - return Err(UsernameTaken); - } else if err.message().contains("email_address") { - return Err(EmailTaken); - } - } - _ => {}, - }*/ - }, - _ => {}, - } - return Err(anyhow!("error inserting model upload job record: {:?}", err)); - } - }; - - Ok(job_token) -} diff --git a/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/mod.rs b/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/mod.rs index ed192a392b..15edb412b5 100644 --- a/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/tts/tts_model_upload_jobs/mod.rs @@ -1,2 +1 @@ pub mod get_tts_model_upload_job_status; -pub mod insert_tts_model_upload_job; \ No newline at end of file diff --git a/crates/schema/database/mysql_queries/src/queries/tts/tts_results/delete_tts_result_various_scopes.rs b/crates/schema/database/mysql_queries/src/queries/tts/tts_results/delete_tts_result_various_scopes.rs deleted file mode 100644 index f9d899ae84..0000000000 --- a/crates/schema/database/mysql_queries/src/queries/tts/tts_results/delete_tts_result_various_scopes.rs +++ /dev/null @@ -1,89 +0,0 @@ -use sqlx::MySqlPool; - -use errors::AnyhowResult; - -pub async fn delete_tts_inference_result_as_user( - inference_result_token: &str, - mysql_pool: &MySqlPool -) -> AnyhowResult<()> { - let _r = sqlx::query!( - r#" -UPDATE tts_results -SET - user_deleted_at = CURRENT_TIMESTAMP -WHERE - token = ? -LIMIT 1 - "#, - inference_result_token, - ) - .execute(mysql_pool) - .await?; - Ok(()) -} - -pub async fn delete_tts_inference_result_as_mod( - inference_result_token: &str, - mod_user_token: &str, - mysql_pool: &MySqlPool -) -> AnyhowResult<()> { - let _r = sqlx::query!( - r#" -UPDATE tts_results -SET - mod_deleted_at = CURRENT_TIMESTAMP, - maybe_mod_user_token = ? -WHERE - token = ? -LIMIT 1 - "#, - mod_user_token, - inference_result_token, - ) - .execute(mysql_pool) - .await?; - Ok(()) -} - -pub async fn undelete_tts_inference_result_as_user( - inference_result_token: &str, - mysql_pool: &MySqlPool -) -> AnyhowResult<()> { - let _r = sqlx::query!( - r#" -UPDATE tts_results -SET - user_deleted_at = NULL -WHERE - token = ? -LIMIT 1 - "#, - inference_result_token, - ) - .execute(mysql_pool) - .await?; - Ok(()) -} - -pub async fn undelete_tts_inference_result_as_mod( - inference_result_token: &str, - mod_user_token: &str, - mysql_pool: &MySqlPool -) -> AnyhowResult<()> { - let _r = sqlx::query!( - r#" -UPDATE tts_results -SET - mod_deleted_at = NULL, - maybe_mod_user_token = ? -WHERE - token = ? -LIMIT 1 - "#, - mod_user_token, - inference_result_token, - ) - .execute(mysql_pool) - .await?; - Ok(()) -} diff --git a/crates/schema/database/mysql_queries/src/queries/tts/tts_results/mod.rs b/crates/schema/database/mysql_queries/src/queries/tts/tts_results/mod.rs index 5a535ed5ca..43fdb236ad 100644 --- a/crates/schema/database/mysql_queries/src/queries/tts/tts_results/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/tts/tts_results/mod.rs @@ -1,5 +1,4 @@ -pub mod delete_tts_result_various_scopes; pub mod edit_tts_result; pub mod insert_tts_result; pub mod list_tts_results_query_builder; -pub mod query_tts_result; \ No newline at end of file +pub mod query_tts_result; diff --git a/crates/schema/database/redis_common/src/redis_cache_keys.rs b/crates/schema/database/redis_common/src/redis_cache_keys.rs index de7338c46d..ccca90793f 100644 --- a/crates/schema/database/redis_common/src/redis_cache_keys.rs +++ b/crates/schema/database/redis_common/src/redis_cache_keys.rs @@ -17,10 +17,6 @@ impl RedisCacheKeys { format!("get_tts_inf2:{}", model_token) } - pub fn get_unified_queue_stats_endpoint() -> &'static str { - "q_stats_2" // NB: Schema changed, hence _2 - } - pub fn session_record_light(session_token: &str) -> String { format!("ses_light:{}", session_token) } diff --git a/crates/service/web/storyteller_web/src/docs/api_doc.rs b/crates/service/web/storyteller_web/src/docs/api_doc.rs index f73acb2bdf..0bbb7e88b7 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -16,7 +16,6 @@ use crate::http_server::common_responses::tag_info::TagInfo; use crate::http_server::common_responses::user_details_lite::{UserDefaultAvatarInfo, UserDetailsLight}; use crate::http_server::deprecated_endpoints::conversion::enqueue_fbx_to_gltf_handler::*; use crate::http_server::deprecated_endpoints::engine::create_scene_handler::*; -use crate::http_server::deprecated_endpoints::image_gen::enqueue_image_generation::EnqueueImageGenRequestSuccessResponse; use crate::http_server::deprecated_endpoints::workflows::enqueue::enqueue_face_fusion_workflow_handler::*; use crate::http_server::deprecated_endpoints::workflows::enqueue::enqueue_live_portrait_workflow_handler::*; use crate::http_server::deprecated_endpoints::workflows::enqueue::vst_common::vst_error::*; @@ -920,7 +919,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux EnqueueFbxToGltfRequestSuccessResponse, EnqueueGptSovitsModelDownloadRequest, EnqueueGptSovitsModelDownloadSuccessResponse, - EnqueueImageGenRequestSuccessResponse, EnqueueLivePortraitCropDimensions, EnqueueLivePortraitWorkflowRequest, EnqueueLivePortraitWorkflowSuccessResponse, @@ -985,18 +983,13 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux GetProfilePathInfo, GetPromptPathInfo, GetPromptSuccessResponse, - GetUnifiedQueueStatsError, GetUnifiedQueueStatsSuccessResponse, GetUserRatingResponse, GetWeightPathInfo, GetWeightResponse, GoogleCreateAccountRequest, GoogleCreateAccountSuccessResponse, - InferTtsError, - InferTtsRequest, - InferTtsSuccessResponse, InferenceJobStatusResponsePayload, - InferenceJobTokenType, LegacyQueueDetails, ListActiveUserSubscriptionsResponse, ListAvailableWeightsQuery, diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_generation_type.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_generation_type.rs deleted file mode 100644 index f0c1f36262..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_generation_type.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::collections::BTreeSet; - -#[cfg(test)] -use strum::EnumCount; -#[cfg(test)] -use strum::EnumIter; -use utoipa::ToSchema; - -#[cfg_attr(test, derive(EnumIter, EnumCount))] -#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Deserialize, Serialize, ToSchema)] -pub enum EnqueueImageGenType { - #[serde(rename = "upload_lora")] - UploadLoRA, - #[serde(rename = "upload_sd")] - Checkpoint, - #[serde(rename = "inference")] - Inference, -} - -impl EnqueueImageGenType { - pub fn to_str(&self) -> &'static str { - match self { - Self::UploadLoRA => "upload_lora", - Self::Checkpoint => "upload_sd", - Self::Inference => "inference", - } - } - - pub fn from_str(value: &str) -> Result { - match value { - "upload_lora" => Ok(Self::UploadLoRA), - "upload_sd" => Ok(Self::Checkpoint), - "inference" => Ok(Self::Inference), - _ => Err(format!("invalid value: {:?}", value)), - } - } - - pub fn all_variants() -> BTreeSet { - BTreeSet::from([ - Self::UploadLoRA, - Self::Checkpoint, - Self::Inference - ]) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_to_str() { - assert_eq!(EnqueueImageGenType::UploadLoRA.to_str(), "upload_lora"); - assert_eq!(EnqueueImageGenType::Checkpoint.to_str(), "upload_sd"); - assert_eq!(EnqueueImageGenType::Inference.to_str(), "inference"); - } - - #[test] - fn test_from_str() { - assert_eq!(EnqueueImageGenType::from_str("upload_lora").unwrap(), EnqueueImageGenType::UploadLoRA); - assert_eq!(EnqueueImageGenType::from_str("upload_sd").unwrap(), EnqueueImageGenType::Checkpoint); - assert_eq!(EnqueueImageGenType::from_str("inference").unwrap(), EnqueueImageGenType::Inference); - assert!(EnqueueImageGenType::from_str("invalid").is_err()); - } - - #[test] - fn test_all_variants() { - let variants = EnqueueImageGenType::all_variants(); - assert_eq!(variants.len(), 3); - assert!(variants.contains(&EnqueueImageGenType::UploadLoRA)); - assert!(variants.contains(&EnqueueImageGenType::Checkpoint)); - assert!(variants.contains(&EnqueueImageGenType::Inference)); - } -} \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs deleted file mode 100644 index 49ba8989c1..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs +++ /dev/null @@ -1,560 +0,0 @@ -#![forbid(unused_mut)] - -use std::fmt::Debug; -use std::fmt::{Display, Formatter}; -use std::sync::Arc; - -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::web::Json; -use actix_web::{web, HttpRequest, HttpResponse}; -use log::error; -use log::warn; -use serde::Deserialize; -use serde::Serialize; -use utoipa::ToSchema; -use web::Data; - -use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory; -use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType; -use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType; -use http_server_common::request::get_request_header_optional::get_request_header_optional; -use http_server_common::request::get_request_ip::get_request_ip; -use mysql_queries::payloads::generic_inference_args::generic_inference_args::{ - GenericInferenceArgs, - InferenceCategoryAbbreviated, - PolymorphicInferenceArgs, -}; -use mysql_queries::payloads::generic_inference_args::inner_payloads::image_generation_payload::StableDiffusionArgs; -use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{ - insert_generic_inference_job, - InsertGenericInferenceArgs, -}; -use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; -use tokens::tokens::generic_inference_jobs::InferenceJobToken; -use tokens::tokens::media_files::MediaFileToken; -use tokens::tokens::model_weights::ModelWeightToken; -use tokens::tokens::users::UserToken; - -use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; -use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::state::server_state::ServerState; - -/// This is the number of images (batch size) to generate for each request. -/// We should allow all users to have multiple images generated at once as this -/// is what other providers do. -const MINIMUM_IMAGE_COUNT : u32 = 4; - -/// The maximum number of images (batch size) to generate for each request. -const MAXIMUM_IMAGE_COUNT : u32 = 8; - -/// Debug requests can get routed to special "debug-only" workers, which can -/// be used to trial new code, run debugging, etc. -const DEBUG_HEADER_NAME: &str = "enable-debug-mode"; - -/// The routing tag header can send workloads to particular k8s hosts. -/// This is useful for catching the live logs or intercepting the job. -const ROUTING_TAG_HEADER_NAME: &str = "routing-tag"; - -#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)] -pub enum TypeOfInference { - #[serde(rename = "lora")] - Lora, - #[serde(rename = "model")] - Model, - #[serde(rename = "inference")] - Inference -} -impl Display for TypeOfInference { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match self { - TypeOfInference::Lora => write!(f, "lora"), - TypeOfInference::Model => write!(f, "model"), - TypeOfInference::Inference => write!(f, "inference"), - } - } -} - -pub fn is_valid_string(input: &str) -> bool { - let valid_strings = [ - "DPM++ 2M Karras", - "DPM++ SDE Karras", - "DPM++ 2M SDE Exponential", - "DPM++ 2M SDE Karras", - "Euler a", - "Euler", - "LMS", - "Heun", - "DPM2", - "DPM2 a", - "DPM++ 2S a", - "DPM++ 2M", - "DPM++ SDE", - "DPM++ 2M SDE", - "DPM++ 2M SDE Heun", - "DPM++ 2M SDE Heun Karras", - "DPM++ 2M SDE Heun Exponential", - "DPM++ 3M SDE", - "DPM++ 3M SDE Karras", - "DPM++ 3M SDE Exponential", - "DPM fast", - "DPM adaptive", - "LMS Karras", - "DPM2 Karras", - "DPM2 a Karras", - "DPM++ 2S a Karras", - ]; - valid_strings.contains(&input) -} - -#[derive(Deserialize, Default, ToSchema)] -pub struct EnqueueImageGenRequest { - pub uuid_idempotency_token: String, - pub maybe_image_source: Option, - pub maybe_sd_model_token: Option, - pub maybe_lora_model_token: Option, - pub maybe_prompt: Option, - pub maybe_n_prompt: Option, - pub maybe_seed: Option, - pub maybe_width: Option, - pub maybe_height: Option, - pub maybe_sampler: Option, - pub maybe_upload_path: Option, - pub maybe_lora_upload_path: Option, - pub maybe_cfg_scale: Option, - pub maybe_number_of_samples: Option, - pub maybe_batch_count: Option, - pub maybe_name: Option, - pub maybe_description: Option, - pub maybe_version: Option, - - // Optional cover image on LoRA or SD upload - pub maybe_cover_image_media_file_token: Option, -} - -#[derive(Serialize, ToSchema)] -pub struct EnqueueImageGenRequestSuccessResponse { - pub success: bool, - pub inference_job_token: InferenceJobToken, -} -// Implementation for enqueuing a TTS request -// Reference enqueue_infer_tts_handler.rs for checks: rate limiting / user sessions -// insert generic inference job.rs -// Need to convert it to generic inference job. -#[utoipa::path( - post, - path = "/inference/enqueue_image_gen/", - responses( - ( - status = 200, - description = "Enqueue TTS generically", - body = EnqueueImageGenRequestSuccessResponse, - ), - (status = 400, description = "Bad input", body = CommonWebError), - (status = 401, description = "Not authorized", body = CommonWebError), - (status = 429, description = "Rate limited", body = CommonWebError), - (status = 500, description = "Server error", body = CommonWebError) - ), - params(("request" = EnqueueImageGenRequest, description = "Payload for TTS Request")) -)] -pub async fn enqueue_image_generation_request( - http_request: HttpRequest, - request: Json, - server_state: Data> -) -> Result { - - let inference_mode = inference_mode_from_http_request(&http_request) - .ok_or(CommonWebError::BadInputWithSimpleMessage("Invalid request".to_string()))?; - - validate_request(&request, inference_mode)?; - - // TODO: Brandon need to figure out premium vs not premium - - let mut maybe_user_token: Option = None; - let visbility = enums::common::visibility::Visibility::Public; - - let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| { - warn!("MySql pool error: {:?}", err); - CommonWebError::from_error(err) - })?; - - // ==================== USER SESSION ==================== // - - let maybe_user_session = server_state.session_checker - .maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await - .map_err(|e| { - warn!("Session checker error: {:?}", e); - CommonWebError::from_error(e) - })?; - - - if let Some(user_session) = maybe_user_session.as_ref() { - maybe_user_token = Some(UserToken::new_from_str(&user_session.user_token)); - } - - // ==================== PLANS ==================== // - - // Plan should handle "first anonymous use" and "investor" cases. - let plan = get_correct_plan_for_session( - server_state.server_environment, - maybe_user_session.as_ref() - ); - - // Separate priority for animation. - let priority_level = plan.web_vc_base_priority_level(); - - // ==================== DEBUG MODE + ROUTING TAG ==================== // - - let is_debug_request = get_request_header_optional(&http_request, DEBUG_HEADER_NAME).is_some(); - - let maybe_routing_tag = get_request_header_optional(&http_request, ROUTING_TAG_HEADER_NAME).map( - |routing_tag| routing_tag.trim().to_string() - ); - - - // ==================== BANNED USERS ==================== // - - if let Some(ref user) = maybe_user_session { - if user.role.is_banned { - return Err(CommonWebError::NotAuthorized); - } - } - - // DETECT premium user and queue - - // ==================== RATE LIMIT ==================== // - - let rate_limiter = match maybe_user_session { - None => &server_state.redis_rate_limiters.logged_out, - Some(ref _user) => &server_state.redis_rate_limiters.logged_in, - }; - - if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(CommonWebError::TooManyRequests); - } - - // Get up IP address - let ip_address = get_request_ip(&http_request); - - // Check the inference args to make sure everything is all there for upload loRA / model or standard inference - - // ==================== HANDLE IDEMPOTENCY ==================== // - - if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(CommonWebError::BadInputWithSimpleMessage(reason)); - } - - insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) - .await - .map_err(|err| { - error!("Error inserting idempotency token: {:?}", err); - CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) - })?; - - // ==================== INFERENCE ARGS ==================== // - - let width = request.maybe_width.unwrap_or(512); - let height = request.maybe_height.unwrap_or(512); - - let width = match width { - 0..=512 => 512, - 513..=768 => 768, - 769..=1024 => 1024, - _ => 1024, - }; - - let height = match height { - 0..=512 => 512, - 513..=768 => 768, - 769..=1024 => 1024, - _ => 1024, - }; - - let cfg_scale = match request.maybe_cfg_scale { - Some(val) => if val > 32 { 32 } else { val } - None => 7, - }; - - let number_of_samples = match request.maybe_number_of_samples { - Some(val) => if val > 128 { 128 } else { val } - None => 20, - }; - - let batch_count = match request.maybe_batch_count { - None => MINIMUM_IMAGE_COUNT, // NB: Default to "3" images for everyone - Some(0...MINIMUM_IMAGE_COUNT) => MINIMUM_IMAGE_COUNT, - Some(val) => if val > MAXIMUM_IMAGE_COUNT { MAXIMUM_IMAGE_COUNT } else { val } - }; - - let sampler = match request.maybe_sampler.clone() { - Some(val) => if is_valid_string(&val) { val.clone() } else { String::from("Euler a") } - None => String::from("Euler a"), - }; - - let sd_weight_token = ModelWeightToken( - request.maybe_sd_model_token.clone().unwrap_or_default() - ); - - let lora_token = ModelWeightToken(request.maybe_lora_model_token.clone().unwrap_or_default()); - - let n_prompt = request.maybe_n_prompt.clone().unwrap_or_default(); - - // we can only do 1 upload type at a time. - // if we are uploading a model. - let mut maybe_sd_upload_url = None; - let mut maybe_lora_upload_url = None; - - let both_fields = ( - request.maybe_upload_path.as_deref(), - request.maybe_lora_upload_path.as_deref() - ); - - match both_fields { - (Some(_), Some(_)) => { - return Err(CommonWebError::BadInputWithSimpleMessage("Can't upload both lora and model".to_string())); - } - (Some(sd_url), None) => { - maybe_sd_upload_url = Some(sd_url.to_string()); - } - (None, Some(lora_url)) => { - maybe_lora_upload_url = Some(lora_url.to_string()); - } - _ => {}, // No-op for inference - } - - // NB: This is done to populate the new top-level field in the jobs table. - // We won't read this yet, but in the meantime it can serve as analytics, and in the future - // we can switch to it. - let maybe_either_download_url = maybe_sd_upload_url.as_deref() - .or(maybe_lora_upload_url.as_deref()) - .map(|s| s.to_string()); - - let seed = request.maybe_seed.unwrap_or(-1); - - let mut version : u32 = 0; - - let type_of_inference = match inference_mode { - TypeOfInference::Lora => "lora", - TypeOfInference::Model => "model", - TypeOfInference::Inference => "inference", - }; - - if let Some(s) = request.maybe_version { - version = s; - } - - let inference_args = StableDiffusionArgs { - maybe_sd_model_token: Some(sd_weight_token), - maybe_lora_model_token: Some(lora_token), - maybe_prompt: request.maybe_prompt.clone(), - maybe_n_prompt: request.maybe_n_prompt.clone(), - maybe_seed: Some(seed), - maybe_upload_path: maybe_sd_upload_url, - maybe_lora_upload_path: maybe_lora_upload_url, - type_of_inference: type_of_inference.to_string(), - maybe_cfg_scale: Some(cfg_scale), - maybe_number_of_samples: Some(number_of_samples), - maybe_batch_count: Some(batch_count), - maybe_width: Some(width), - maybe_height: Some(height), - maybe_sampler: Some(sampler), - maybe_description: request.maybe_description.clone(), - maybe_name: request.maybe_name.clone(), - maybe_version: Some(version), - }; - - // create the inference args here - let maybe_avt_token = server_state.avt_cookie_manager.get_avt_token_from_request(&http_request); - - // create the job record here! - let query_result = insert_generic_inference_job(InsertGenericInferenceArgs { - uuid_idempotency_token: &request.uuid_idempotency_token, - job_type: InferenceJobType::StableDiffusion, - maybe_product_category: None, // This is not a product anymore - inference_category: InferenceCategory::ImageGeneration, - maybe_model_type: Some(InferenceModelType::StableDiffusion), // NB: Model is static during inference - maybe_model_token: None, // NB: Model is static during inference - maybe_input_source_token: None, - maybe_input_source_token_type: None, - maybe_download_url: maybe_either_download_url.as_deref(), - maybe_cover_image_media_file_token: request.maybe_cover_image_media_file_token.as_ref(), - maybe_raw_inference_text: None, - maybe_max_duration_seconds: None, - maybe_inference_args: Some(GenericInferenceArgs { - inference_category: Some(InferenceCategoryAbbreviated::ImageGeneration), - args: Some(PolymorphicInferenceArgs::Ig(inference_args)), - }), - maybe_creator_user_token: maybe_user_token.as_ref(), - maybe_avt_token: maybe_avt_token.as_ref(), - creator_ip_address: &ip_address, - creator_set_visibility: visbility, - priority_level, - requires_keepalive: false, //reverse ... TODO fix this. we set it base on account is premium or not ... - is_debug_request, - maybe_routing_tag: maybe_routing_tag.as_deref(), - mysql_pool: &server_state.mysql_pool, - }).await; - - let job_token = match query_result { - Ok((job_token, _id)) => job_token, - Err(err) => { - warn!("New generic inference job creation DB error: {:?}", err); - if err.had_duplicate_idempotency_token() { - return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); - } - return Err(CommonWebError::from_error(err)); - } - }; - - let response: EnqueueImageGenRequestSuccessResponse = EnqueueImageGenRequestSuccessResponse { - success: true, - inference_job_token: job_token, - }; - - let body = serde_json - ::to_string(&response) - ?; - - // Error handling 101 rust result type returned like so. - Ok(HttpResponse::Ok().content_type("application/json").body(body)) -} - -fn validate_request( - request: &Json, - inference_mode: TypeOfInference -) -> Result<(), CommonWebError> { - - let mut requires_name = false; - let mut requires_description = false; - let mut requires_sd_model_token = false; - let mut requires_lora_upload_path = false; - let mut requires_upload_path = false; - - match inference_mode { - TypeOfInference::Lora => { - requires_name = true; - requires_description = true; - requires_lora_upload_path = true; - } - TypeOfInference::Model => { - requires_name = true; - requires_description = true; - requires_upload_path = true; - } - TypeOfInference::Inference => { - requires_sd_model_token = true; - } - } - - if requires_name && request.maybe_name.is_none() { - return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model / Lora Name".to_string())); - } - if requires_description && request.maybe_description.is_none() { - return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model / Lora Description".to_string())); - } - if requires_sd_model_token && request.maybe_sd_model_token.is_none() { - return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model Token".to_string())); - } - if requires_lora_upload_path && request.maybe_lora_upload_path.is_none() { - return Err(CommonWebError::BadInputWithSimpleMessage("Missing Lora Upload Path".to_string())); - } - if requires_upload_path && request.maybe_upload_path.is_none() { - return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model Upload Path".to_string())); - } - - Ok(()) -} - - - - -fn inference_mode_from_http_request(http_request: &HttpRequest) -> Option { - inference_mode_from_url_path(http_request.path()) -} - -fn inference_mode_from_url_path(url_path: &str) -> Option { - let last_segment = url_path.split("/").last(); - match last_segment { - Some("lora") => Some(TypeOfInference::Lora), - Some("model") => Some(TypeOfInference::Model), - Some("inference") => Some(TypeOfInference::Inference), - _ => None - } -} - -#[cfg(test)] -mod tests { - use crate::http_server::deprecated_endpoints::image_gen::enqueue_image_generation::{inference_mode_from_url_path, TypeOfInference}; - - #[test] - fn test_url_paths() { - // Valid routes - assert_eq!(inference_mode_from_url_path("/v1/image_gen/enqueue/inference"), Some(TypeOfInference::Inference)); - assert_eq!(inference_mode_from_url_path("/v1/image_gen/upload/model"), Some(TypeOfInference::Model)); - assert_eq!(inference_mode_from_url_path("/v1/image_gen/upload/lora"), Some(TypeOfInference::Lora)); - - // Non-routes - assert_eq!(inference_mode_from_url_path(""), None); - assert_eq!(inference_mode_from_url_path("/v1/image_gen/enqueue/foo"), None); - assert_eq!(inference_mode_from_url_path("/v1/image_gen/upload/foo"), None); - } -} - -// with LoRA -// http://127.0.0.1:12345/v1/image_gen/inference/enqueue_image_gen -// { -// "uuid_idempotency_token": "12", -// "maybe_sd_model_token": "weight_dmmthavhawqc2hj7yqyemcbf8", -// "maybe_lora_model_token": "weight_t7gz78fjg27m0wtw6r33gafxs", -// "maybe_prompt": "raiden mei a very well drawn, anime girl, with pink and purple hair sitting down on a chair relaxed, highest quality, semi nude, masterpiece, painted", -// "maybe_a_prompt": "a anime girl, with pink and purple hair sitting down on a chair relaxed.", -// "maybe_n_prompt": "nsfw, black and white, low quality, pixelated", -// "maybe_seed": -1, -// "maybe_width": 1024, -// "maybe_height": 1024, -// "maybe_sampler": "DPM++ 2M SDE Heun", -// "maybe_cfg_scale": 7, -// "maybe_number_of_samples": 64, -// "maybe_batch_count": 4, -// } - -// without LoRA -// { -// "uuid_idempotency_token": "132141", -// "maybe_sd_model_token": "weight_dmmthavhawqc2hj7yqyemcbf8", -// "maybe_prompt": "raiden mei a very well drawn, anime girl, with pink and purple hair sitting down on a chair relaxed, highest quality, semi nude, masterpiece, painted", -// "maybe_a_prompt": "a anime girl, with pink and purple hair sitting down on a chair relaxed.", -// "maybe_n_prompt": "nsfw, black and white, low quality, pixelated", -// "maybe_seed": -1, -// "maybe_width": 512, -// "maybe_height": 512, -// "maybe_sampler": "DPM++ 2M SDE Heun", -// "maybe_cfg_scale": 8, -// "maybe_number_of_samples": 64, -// "maybe_batch_count": 4 -// } - -// http://127.0.0.1:12345/v1/image_gen/upload/lora -// { -// "uuid_idempotency_token": "12", -// "maybe_lora_upload_path": "https://drive.google.com/file/d/1WRgR2pn0Ky8ls5_9Zq6tQlHTBvyWeach/view?usp=sharing", -// "maybe_name":"some_name", -// "maybe_description":"some_description" -// } - -// http://127.0.0.1:12345/v1/image_gen/upload/model -// { -// "uuid_idempotency_token": "13", -// "maybe_upload_path": "https://drive.google.com/file/d/1WRgR2pn0Ky8ls5_9Zq6tQlHTBvyWeach/view?usp=sharing", -// "maybe_name":"some_name", -// "maybe_description":"some_description" -// } - -// { -// "uuid_idempotency_token": "123", -// "maybe_lora_upload_path": "https://drive.google.com/file/d/1WRgR2pn0Ky8ls5_9Zq6tQlHTBvyWeach/view?usp=sharing", -// "maybe_name":"some_name", -// "maybe_description":"some_description" -// } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/json.txt b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/json.txt deleted file mode 100644 index 67177d418f..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/json.txt +++ /dev/null @@ -1,18 +0,0 @@ -{ - "uuid_idempotency_token": "123", - "type_of_inference": "checkpoint", - "maybe_sd_model_token": "weight_dmmthavhawqc2hj7yqyemcbf8", - "maybe_lora_model_token": "weight_t7gz78fjg27m0wtw6r33gafxs", - "maybe_prompt": "a very well drawn, anime girl, with pink and purple hair sitting down on a chair relaxed.", - "maybe_a_prompt": "a anime girl, with pink and purple hair sitting down on a chair relaxed.", - "maybe_n_prompt": "nsfw, black and white, low quality, pixelated", - "maybe_seed": "-1", - "maybe_width": "512", - "maybe_height": "512", - "maybe_sampler": "DPM++ 2M SDE Heun", - "maybe_upload_path": "https://drive.google.com/file/d/1WRgR2pn0Ky8ls5_9Zq6tQlHTBvyWeach/view?usp=sharing", - "maybe_lora_upload_path": "https://drive.google.com/file/d/1WRgR2pn0Ky8ls5_9Zq6tQlHTBvyWeach/view?usp=sharing", - "maybe_cfg_scale": "7", - "maybe_number_of_samples": "32", - "maybe_batch_count": "4" -} \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/mod.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/mod.rs deleted file mode 100644 index 460b32b57c..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod enqueue_image_generation; -pub mod enqueue_generation_type; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/leaderboard/get_leaderboard.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/leaderboard/get_leaderboard.rs deleted file mode 100644 index d7ee74da66..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/leaderboard/get_leaderboard.rs +++ /dev/null @@ -1,199 +0,0 @@ -// NB: Incrementally getting rid of build warnings... -#![forbid(unused_imports)] -#![forbid(unused_mut)] -#![forbid(unused_variables)] - -use std::fmt; -use std::sync::Arc; - -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; -use log::{debug, error, warn}; -use sqlx::MySqlPool; - -use crate::http_server::common_responses::user_avatars::default_avatar_color_from_username::default_avatar_color_from_username; -use crate::http_server::common_responses::user_avatars::default_avatar_from_username::default_avatar_from_username; -use crate::http_server::common_responses::user_details_lite::{UserDefaultAvatarInfo, UserDetailsLight}; -use errors::AnyhowResult; -use mysql_queries::queries::tts::stats::calculate_tts_model_leaderboard::calculate_tts_model_leaderboard; -use tokens::tokens::users::UserToken; - -use crate::http_server::web_utils::serialize_as_json_error::serialize_as_json_error; -use crate::state::server_state::ServerState; - -#[derive(Serialize)] -pub struct LeaderboardResponse { - success: bool, - tts_leaderboard: Vec, - w2l_leaderboard: Vec, -} - -#[derive(Clone, Serialize)] -pub struct LeaderboardRow { - pub user: UserDetailsLight, - - pub uploaded_count: i64, - - #[deprecated(note="switch to the user field (type UserDetailsLight)")] - pub creator_user_token: String, - - #[deprecated(note="switch to the user field (type UserDetailsLight)")] - pub username: String, - - #[deprecated(note="switch to the user field (type UserDetailsLight)")] - pub display_name: String, - - #[deprecated(note="switch to the user field (type UserDetailsLight)")] - pub gravatar_hash: String, - - #[deprecated(note="switch to the user field (type UserDetailsLight)")] - pub default_avatar_index: u8, - - #[deprecated(note="switch to the user field (type UserDetailsLight)")] - pub default_avatar_color_index: u8, -} - -#[derive(Serialize, Debug)] -pub struct LeaderboardErrorResponse { - pub success: bool, - pub error_type: LeaderboardErrorType, - pub error_message: String, -} - -#[derive(Copy, Clone, Debug, Serialize)] -pub enum LeaderboardErrorType { - ServerError, -} - -impl LeaderboardErrorResponse { - fn server_error() -> Self { - Self { - success: false, - error_type: LeaderboardErrorType::ServerError, - error_message: "server error".to_string() - } - } -} - -impl ResponseError for LeaderboardErrorResponse { - fn status_code(&self) -> StatusCode { - match self.error_type { - LeaderboardErrorType::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - -// NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for LeaderboardErrorResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.error_type) - } -} - -pub async fn leaderboard_handler( - _http_request: HttpRequest, - server_state: web::Data> -) -> Result { - - let maybe_cached = server_state.caches.ephemeral.leaderboard.grab_copy_without_bump_if_unexpired() - .map_err(|e| { - error!("error consulting cache: {:?}", e); - LeaderboardErrorResponse::server_error() - })?; - - - let leaderboard_info = match maybe_cached { - Some(leaderboard) => { - leaderboard - } - None => { - debug!("populating leaderboard from database"); - - let leaderboard_query_result = query_leaderboard( - &server_state.mysql_pool - ).await; - - match leaderboard_query_result { - // If the database misbehaves (eg. DDoS), let's stop spamming it. - // We'll attempt to read the old value from the cache and keep going. - Err(err) => { - warn!("error querying database / inserting into cache: {:?}", err); - - let maybe_cached = server_state.caches.ephemeral.leaderboard.grab_even_expired_and_bump() - .map_err(|err| { - error!("error consulting cache (even expired): {:?}", err); - LeaderboardErrorResponse::server_error() - })?; - - maybe_cached.ok_or_else(|| { - error!("error querying database and subsequently reading cache: {:?}", err); - LeaderboardErrorResponse::server_error() - })? - } - - // Happy path... - Ok(leaderboard_info) => { - server_state.caches.ephemeral.leaderboard.store_copy(&leaderboard_info) - .map_err(|e| { - error!("error storing cache: {:?}", e); - LeaderboardErrorResponse::server_error() - })?; - - leaderboard_info - } - } - } - }; - - let response = LeaderboardResponse { - success: true, - tts_leaderboard: leaderboard_info.tts_leaderboard, - w2l_leaderboard: vec![], - }; - - let body = serde_json::to_string(&response) - .map_err(|_e| LeaderboardErrorResponse::server_error())?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) -} - -#[derive(Clone)] -pub struct LeaderboardInfo { - tts_leaderboard: Vec, -} - -async fn query_leaderboard(mysql_pool: &MySqlPool) -> AnyhowResult { - let mut mysql_connection = mysql_pool.acquire().await?; - - let tts_results = calculate_tts_model_leaderboard(&mut mysql_connection) - .await? - .into_iter() - .map(|record| LeaderboardRow { - creator_user_token: record.creator_user_token.clone(), - username: record.username.to_string(), - display_name: record.display_name.to_string(), - gravatar_hash: record.gravatar_hash.to_string(), - default_avatar_index: default_avatar_from_username(&record.username), - default_avatar_color_index: default_avatar_color_from_username(&record.username), - user: UserDetailsLight { - user_token: UserToken::new_from_str(&record.creator_user_token), - username: record.username.to_string(), - display_name: record.display_name, - gravatar_hash: record.gravatar_hash, - default_avatar: UserDefaultAvatarInfo::from_username(&record.username), - }, - uploaded_count: record.uploaded_count, - }) - .collect(); - - Ok(LeaderboardInfo { - tts_leaderboard: tts_results, - }) -} diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/leaderboard/mod.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/leaderboard/mod.rs deleted file mode 100644 index 4fad116a64..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/leaderboard/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod get_leaderboard; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mod.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mod.rs index 0e9d8d4063..742b265b25 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mod.rs @@ -5,8 +5,6 @@ pub mod conversion; pub mod engine; pub mod events; pub mod flags; -pub mod image_gen; -pub mod leaderboard; pub mod media_uploads; pub mod moderation; pub mod stubs; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/stats/get_unified_queue_stats_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/stats/get_unified_queue_stats_handler.rs index f2b97cb218..69432af7d6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/stats/get_unified_queue_stats_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/stats/get_unified_queue_stats_handler.rs @@ -1,15 +1,15 @@ -use std::sync::Arc; - -use actix_web::{http::StatusCode, web, HttpRequest, HttpResponse, ResponseError}; -use chrono::NaiveDateTime; -use log::{debug, error, warn}; -use mysql_queries::queries::stats::get_unified_queue_stats::get_unified_queue_stats; -use redis_common::redis_cache_keys::RedisCacheKeys; +use actix_web::web::Json; +use chrono::{NaiveDateTime, Utc}; use utoipa::ToSchema; -use crate::http_server::endpoints::stats::result_transformer::{database_result_to_cacheable, CacheableQueueStats}; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::state::server_state::ServerState; +/// The queue counts are frozen at the values from when the legacy inference +/// systems were shut down. Legacy clients still poll this endpoint, so it +/// stays mounted, but it no longer consults the database. +const FROZEN_TOTAL_PENDING_JOB_COUNT: u64 = 2_409_539; +const FROZEN_PENDING_TACOTRON2_JOBS: u64 = 1_037_783; +const FROZEN_PENDING_RVC_JOBS: u64 = 288_922; +const FROZEN_PENDING_SVC_JOBS: u64 = 27_417; +const REFRESH_INTERVAL_MILLIS: u64 = 15_000; #[derive(Serialize, ToSchema)] pub struct GetUnifiedQueueStatsSuccessResponse { @@ -17,7 +17,6 @@ pub struct GetUnifiedQueueStatsSuccessResponse { pub cache_time: NaiveDateTime, /// Tell the frontend client how fast to refresh their view of this list. - /// During an attack, we may want this to go extremely slow. pub refresh_interval_millis: u64, pub inference: ModernInferenceQueueStats, @@ -33,7 +32,6 @@ pub struct LegacyQueueDetails { pub struct ModernInferenceQueueStats { pub total_pending_job_count: u64, - #[deprecated(note="the frontend uses this field, but we should switch to total_pending_job_count")] pub pending_job_count: u64, pub by_queue: ByQueueStats, @@ -58,200 +56,40 @@ pub struct ByQueueStats { pub pending_acting_face: u64, } -#[derive(Debug, ToSchema)] -pub enum GetUnifiedQueueStatsError { - ServerError, -} - -impl ResponseError for GetUnifiedQueueStatsError { - fn status_code(&self) -> StatusCode { - match *self { - GetUnifiedQueueStatsError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - GetUnifiedQueueStatsError::ServerError => "server error".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -// NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for GetUnifiedQueueStatsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -/// Get queue stats for most inference jobs (tts, voice conversion, comfy, etc.) +/// [DEPRECATED] Get queue stats for legacy inference jobs (tts, voice conversion, etc.) +/// +/// The legacy inference systems are gone; this returns a hardcoded snapshot +/// for old clients that still poll it. #[utoipa::path( get, tag = "Stats", path = "/v1/stats/queues", responses( (status = 200, description = "Success", body = GetUnifiedQueueStatsSuccessResponse), - (status = 500, description = "Server error", body = GetUnifiedQueueStatsError), ), )] -pub async fn get_unified_queue_stats_handler( - http_request: HttpRequest, - server_state: web::Data> -) -> Result { - - if server_state.flags.disable_unified_queue_stats_endpoint { - // NB: Despite the cache being a powerful protector of the database (this is an expensive query), - // if the cache goes stale during an outage, there is no protection. This feature flag lets us - // shut off all traffic to the endpoint. - return render_response_busy(GetUnifiedQueueStatsSuccessResponse { - success: true, - cache_time: NaiveDateTime::from_timestamp(0, 0), - refresh_interval_millis: server_state.flags.frontend_unified_queue_stats_refresh_interval_millis, - inference: ModernInferenceQueueStats { - total_pending_job_count: 10_000, - pending_job_count: 10_000, - by_queue: ByQueueStats { - pending_face_animation_jobs: 10_000, - pending_storyteller_studio: 10_000, - pending_rvc_jobs: 10_000, - pending_svc_jobs: 10_000, - pending_tacotron2_jobs: 10_000, - pending_voice_designer: 10_000, - pending_stable_diffusion: 10_000, - pending_acting_face: 10_000, - } - }, - legacy_tts: LegacyQueueDetails { - pending_job_count: 10_000, - }, - }); - } - - let maybe_cached = server_state.caches.ephemeral.queue_stats.grab_copy_without_bump_if_unexpired() - .map_err(|e| { - error!("error consulting cache: {:?}", e); - GetUnifiedQueueStatsError::ServerError - })?; - - let cacheable_stats_result = match maybe_cached { - Some(cached) => { - debug!("serving from in-memory cache"); - cached - }, - None => { - debug!("populating unified queue stats from Redis *OR* database"); - - let mysql_pool = server_state.mysql_pool.clone(); - - let get_stats = move || { - // NB: async closures are not yet stable in Rust, so we include an async block. - async move { - debug!("querying from database..."); - - let db_result = get_unified_queue_stats( - &mysql_pool - ).await; - - db_result - .map(|db_result| database_result_to_cacheable(db_result)) - .map(|cacheable_result| Some(cacheable_result)) // TODO/FIXME: Make a better redis cache - } - }; - - // NB(2023-07-27): Double layers of caching (in-memory + Redis) is probably exorbitant, but this seems fine for now. - // This endpoint's query (even when in-memory cached across the cluster) was causing the DB CPU to hit 100%. - let stats_query_result = match server_state.redis_ttl_cache.get_connection() { - Err(err) => { - warn!("Error loading Redis connection from TTL cache (calling DB instead): {:?}", err); - get_stats().await - } - Ok(mut redis_ttl_cache) => { - let cache_key = RedisCacheKeys::get_unified_queue_stats_endpoint(); - redis_ttl_cache.lazy_load_if_not_cached(&cache_key, move || { - get_stats() - }).await - } - }; - - match stats_query_result { - // If the database misbehaves (eg. DDoS), let's stop spamming it. - // We'll attempt to read the old value from the cache and keep going. - Err(err) => { - warn!("error querying database / inserting into cache: {:?}", err); - - let maybe_cached = server_state.caches.ephemeral.queue_stats.grab_even_expired_and_bump() - .map_err(|err| { - error!("error consulting cache (even expired): {:?}", err); - GetUnifiedQueueStatsError::ServerError - })?; - - maybe_cached.ok_or_else(|| { - error!("error querying database and subsequently reading cache: {:?}", err); - GetUnifiedQueueStatsError::ServerError - })? - } - - // Happy path... - Ok(Some(cacheable_stats)) => { - server_state.caches.ephemeral.queue_stats.store_copy(&cacheable_stats) - .map_err(|e| { - error!("error storing cache: {:?}", e); - GetUnifiedQueueStatsError::ServerError - })?; - cacheable_stats - } - - // TODO/FIXME(bt,2023-07-30): This match arm should never happen. - Ok(None) => CacheableQueueStats::default() - } - }, - }; - - render_response_ok(GetUnifiedQueueStatsSuccessResponse { +#[deprecated(note = "legacy inference is shut down; this endpoint returns a hardcoded snapshot")] +pub async fn get_unified_queue_stats_handler() -> Json { + Json(GetUnifiedQueueStatsSuccessResponse { success: true, - cache_time: cacheable_stats_result.cache_time, - refresh_interval_millis: server_state.flags.frontend_pending_inference_refresh_interval_millis, + cache_time: Utc::now().naive_utc(), + refresh_interval_millis: REFRESH_INTERVAL_MILLIS, inference: ModernInferenceQueueStats { - total_pending_job_count: cacheable_stats_result.queues.total_generic, - pending_job_count: cacheable_stats_result.queues.total_generic, + total_pending_job_count: FROZEN_TOTAL_PENDING_JOB_COUNT, + pending_job_count: FROZEN_TOTAL_PENDING_JOB_COUNT, by_queue: ByQueueStats { - pending_face_animation_jobs: cacheable_stats_result.queues.sad_talker, - pending_rvc_jobs: cacheable_stats_result.queues.rvc_v2, - pending_svc_jobs: cacheable_stats_result.queues.so_vits_svc, - pending_tacotron2_jobs: cacheable_stats_result.queues.tacotron2, - pending_voice_designer: cacheable_stats_result.queues.vall_e_x, - pending_stable_diffusion: cacheable_stats_result.queues.stable_diffusion, - pending_storyteller_studio: cacheable_stats_result.queues.storyteller_studio, - pending_acting_face: cacheable_stats_result.queues.acting_face, - } + pending_tacotron2_jobs: FROZEN_PENDING_TACOTRON2_JOBS, + pending_voice_designer: 0, + pending_rvc_jobs: FROZEN_PENDING_RVC_JOBS, + pending_svc_jobs: FROZEN_PENDING_SVC_JOBS, + pending_stable_diffusion: 0, + pending_face_animation_jobs: 0, + pending_storyteller_studio: 0, + pending_acting_face: 0, + }, }, legacy_tts: LegacyQueueDetails { - pending_job_count: cacheable_stats_result.queues.legacy_tts, + pending_job_count: 0, }, }) } - -pub fn render_response_busy(response: GetUnifiedQueueStatsSuccessResponse) -> Result { - let body = render_response_payload(response)?; - Ok(HttpResponse::TooManyRequests() - .content_type("application/json") - .body(body)) -} - -pub fn render_response_ok(response: GetUnifiedQueueStatsSuccessResponse) -> Result { - let body = render_response_payload(response)?; - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) -} - -pub fn render_response_payload(response: GetUnifiedQueueStatsSuccessResponse) -> Result { - let body = serde_json::to_string(&response) - .map_err(|e| { - error!("error returning response: {:?}", e); - GetUnifiedQueueStatsError::ServerError - })?; - Ok(body) -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/stats/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/stats/mod.rs index db3d774ebd..4608ec9423 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/stats/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/stats/mod.rs @@ -1,2 +1 @@ pub mod get_unified_queue_stats_handler; -pub mod result_transformer; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/stats/result_transformer.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/stats/result_transformer.rs deleted file mode 100644 index 2c06e6cdfa..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/stats/result_transformer.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::collections::HashMap; - -use chrono::NaiveDateTime; - -use mysql_queries::queries::stats::get_unified_queue_stats::QueueStatsRow; - -#[derive(Serialize, Deserialize, Clone)] -pub struct CacheableQueueStats { - pub cache_time: NaiveDateTime, - pub queues: Queues, -} - -impl Default for CacheableQueueStats { - fn default() -> Self { - Self { - cache_time: NaiveDateTime::from_timestamp(0, 0), - queues: Default::default(), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Default)] -pub struct Queues { - // Text to Speech - pub legacy_tts: u64, // Tacotron2 (as tts_inference_jobs) - pub total_generic: u64, // Everything except tts_inference_jobs' Tacotron2 summed together - pub tacotron2: u64, // Tacotron2 (as generic_inference_jobs; NB: included in `total_generic`) - pub vall_e_x: u64, // Counts both model weight calculation and inference. - - // Voice Conversion - pub rvc_v2: u64, - pub so_vits_svc: u64, - - // Image - pub stable_diffusion: u64, - - // Video - pub sad_talker: u64, - pub storyteller_studio: u64, - pub acting_face: u64, -} - -pub fn database_result_to_cacheable(database_records: Vec) -> CacheableQueueStats { - let database_time = database_records.first() - .map(|record| record.present_time) - .unwrap_or(NaiveDateTime::from_timestamp(0, 0)); - - // Queue lengths - let mut queue_lengths : HashMap = HashMap::new(); - - for record in database_records.into_iter() { - queue_lengths.insert(record.queue_type, record.pending_job_count); - } - - CacheableQueueStats { - cache_time: database_time, - queues: Queues { - // Statistic rollup. - total_generic: queue_lengths.iter() - .filter(|(queue, _count)| !(*queue).eq("legacy_tts")) - .map(|(_queue, count)| count) - .sum(), - // TTS - legacy_tts: unwrap(queue_lengths.get("legacy_tts")), - tacotron2: unwrap(queue_lengths.get("tacotron2")), - vall_e_x: unwrap(queue_lengths.get("vall_e_x")), - // VC - rvc_v2: unwrap(queue_lengths.get("rvc_v2")), - so_vits_svc: unwrap(queue_lengths.get("so_vits_svc")), - // Image - stable_diffusion: unwrap(queue_lengths.get("stable_diffusion")), - // Video - sad_talker: unwrap(queue_lengths.get("sad_talker")), - storyteller_studio: unwrap(queue_lengths.get("video_render")), - acting_face: unwrap(queue_lengths.get("live_portrait")), - }, - } -} - -fn unwrap(inner: Option<&u64>) -> u64 { - inner.map(|i|*i).unwrap_or(0) -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs deleted file mode 100644 index b78f281059..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::fmt; -use std::sync::Arc; - -use actix_web::error::ResponseError; -use actix_web::web::Json; -use actix_web::http::StatusCode; -use actix_web::web::Path; -use actix_web::{web, HttpRequest}; -use log::warn; - -use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; -use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::delete_tts_inference_result_as_mod; -use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::delete_tts_inference_result_as_user; -use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::undelete_tts_inference_result_as_mod; -use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::undelete_tts_inference_result_as_user; -use mysql_queries::queries::tts::tts_results::query_tts_result::select_tts_result_by_token; - -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::state::server_state::ServerState; -use crate::util::delete_role_disambiguation::delete_role_disambiguation; -use crate::util::delete_role_disambiguation::DeleteRole; - -/// For the URL PathInfo -#[derive(Deserialize)] -pub struct DeleteTtsInferenceResultPathInfo { - token: String, -} - -#[derive(Deserialize)] -pub struct DeleteTtsInferenceResultRequest { - set_delete: bool, - /// NB: this is only to disambiguate when a user is both a mod and an author. - as_mod: Option, -} -// NB: Not using derive_more::Display since Clion doesn't understand it. -pub async fn delete_tts_inference_result_handler( - http_request: HttpRequest, - path: Path, - request: Json, - server_state: web::Data> -) -> Result, CommonWebError> { - let maybe_user_session = server_state - .session_checker - .maybe_get_user_session(&http_request, &server_state.mysql_pool) - .await - .map_err(|e| { - warn!("Session checker error: {:?}", e); - CommonWebError::from_error(e) - })?; - - let user_session = match maybe_user_session { - Some(session) => session, - None => { - warn!("not logged in"); - return Err(CommonWebError::NotAuthorized); - } - }; - - // NB: First permission check. - // Only mods should see deleted models (both user_* and mod_* deleted). - let is_mod_that_can_see_deleted = user_session.can_delete_other_users_tts_results; - - let inference_result_query_result = select_tts_result_by_token( - &path.token, - is_mod_that_can_see_deleted, - &server_state.mysql_pool, - ).await; - - let tts_inference_result = match inference_result_query_result { - Err(e) => { - warn!("query error: {:?}", e); - return Err(CommonWebError::from_anyhow_error(e)); - } - Ok(None) => return Err(CommonWebError::NotFound), - Ok(Some(inference_result)) => inference_result, - }; - - // NB: Second set of permission checks - let is_author = tts_inference_result.maybe_creator_user_token - .as_ref() - .map(|creator_token| creator_token == &user_session.user_token) - .unwrap_or(false); - - let is_mod = user_session.can_delete_other_users_tts_results; - - if !is_author && !is_mod { - warn!("user is not allowed to delete inference results: {:?}", user_session.user_token); - return Err(CommonWebError::NotAuthorized); - } - - let delete_role = delete_role_disambiguation(is_mod, is_author, request.as_mod); - - let query_result = if request.set_delete { - match delete_role { - DeleteRole::ErrorDoNotDelete => { - warn!("user is not allowed to delete inference results: {:?}", user_session.user_token); - return Err(CommonWebError::NotAuthorized); - } - DeleteRole::AsUser => { - delete_tts_inference_result_as_user( - &path.token, - &server_state.mysql_pool - ).await - } - DeleteRole::AsMod => { - delete_tts_inference_result_as_mod( - &path.token, - user_session.user_token.as_str(), - &server_state.mysql_pool - ).await - } - } - } else { - match delete_role { - DeleteRole::ErrorDoNotDelete => { - warn!("user is not allowed to undelete inference results: {:?}", user_session.user_token); - return Err(CommonWebError::NotAuthorized); - } - DeleteRole::AsUser => { - // NB: Technically only mods can see their own inference_results here - undelete_tts_inference_result_as_user( - &path.token, - &server_state.mysql_pool - ).await - } - DeleteRole::AsMod => { - undelete_tts_inference_result_as_mod( - &path.token, - user_session.user_token.as_str(), - &server_state.mysql_pool - ).await - } - } - }; - - match query_result { - Ok(_) => {}, - Err(err) => { - warn!("Update tts mod approval status DB error: {:?}", err); - return Err(CommonWebError::from_anyhow_error(err)); - } - }; - - Ok(Json(SimpleGenericJsonSuccess { success: true })) -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs index 1acee8c79d..edadddc742 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs @@ -1,627 +1,20 @@ -// NB: Incrementally getting rid of build warnings... -#![forbid(unused_imports)] -#![forbid(unused_mut)] -#![forbid(unused_variables)] - -use std::fmt; -use std::sync::Arc; - -use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::web::Json; -use actix_web::{web, HttpRequest, HttpResponse}; -use log::{info, warn}; -use rand::seq::IndexedRandom; -use rand::Rng; -use redis::Commands; -use utoipa::ToSchema; +use actix_web::HttpResponse; -use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory; -use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory; -use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType; -use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType; -use enums::common::visibility::Visibility; -use http_server_common::request::get_request_api_token::get_request_api_token; -use http_server_common::request::get_request_header_optional::get_request_header_optional; -use http_server_common::request::get_request_ip::get_request_ip; -use migration::text_to_speech::get_tts_model_for_enqueue_inference_migration::TtsModelForEnqueueInferenceMigrationWrapper; -use mysql_queries::payloads::generic_inference_args::generic_inference_args::{GenericInferenceArgs, InferenceCategoryAbbreviated, PolymorphicInferenceArgs}; -use mysql_queries::payloads::generic_inference_args::inner_payloads::gptsovits_payload::GptSovitsPayload; -use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{insert_generic_inference_job, InsertGenericInferenceArgs}; -use redis_common::redis_keys::RedisKeys; -use tokens::tokens::users::UserToken; -use tts_common::priority::{FAKEYOU_DEFAULT_VALID_API_TOKEN_PRIORITY_LEVEL, FAKEYOU_INVESTOR_PRIORITY_LEVEL}; -use user_input_common::check_for_slurs::contains_slurs; - -use crate::configs::app_startup::username_set::UsernameSet; -use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; -use crate::configs::plans::plan::Plan; -use crate::http_server::endpoints::tts::enqueue_infer_tts_handler::get_tts_model_with_caching::get_tts_model_with_caching; -use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::state::server_state::ServerState; -// TODO: Temporary for investor demo -const STORYTELLER_DEMO_COOKIE_NAME : &str = "storyteller_demo"; - -/// Debug requests mean different things, depending on the type of worker (the legacy -/// tts-inference-job vs. the new inference-job): +/// [DEPRECATED] Enqueue a legacy TTS inference request. /// -/// * Legacy (tts): these requests get routed to special "debug-only" workers, which can -/// be used to trial new code, run debugging, etc. -/// -/// * New (generic): the flag gets associated with the request which may be used for -/// anything in the code. (There's a new system for routing requests called -/// "routing tags" that is independent of this flag.) -/// -const DEBUG_HEADER_NAME : &str = "enable-debug-mode"; - -/// The routing tag header can send workloads to particular k8s hosts. -/// This is useful for catching the live logs or intercepting the job. -/// NB: This is only for the new job system. -const ROUTING_TAG_HEADER_NAME : &str = "routing-tag"; - -const USER_FAKEYOU_USER_TOKEN : &str = "U:N5J8JXPW9BTYX"; -const USER_NEWS_STORY_USER_TOKEN : &str = "U:XAWRARC1N89X6"; - -/// Safety guard against plans supporting too lengthy TTS inference text. -const MAX_TTS_LENGTH : usize = 10_000; - -#[derive(Deserialize, ToSchema)] -pub struct InferTtsRequest { - uuid_idempotency_token: String, - tts_model_token: String, - inference_text: String, - creator_set_visibility: Option, - is_storyteller_demo: Option, -} - -#[derive(Serialize, ToSchema)] -pub struct InferTtsSuccessResponse { - pub success: bool, - pub inference_job_token: String, - // This is the type of inference job token returned. - pub inference_job_token_type: InferenceJobTokenType, -} - -/// Tell the frontend how to deal with the tts queue. -#[derive(Serialize, ToSchema)] -pub enum InferenceJobTokenType { - /// Legacy TTS inference job - #[serde(rename = "legacy_tts")] - LegacyTts, - - /// Modern shared inference type (everything will use this in the - /// future, and this endpoint will die.) - #[serde(rename = "generic")] - Generic, -} - -#[derive(Debug, ToSchema)] -pub enum InferTtsError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, - NotFound, -} - -impl ResponseError for InferTtsError { - fn status_code(&self) -> StatusCode { - match *self { - InferTtsError::BadInput(_) => StatusCode::BAD_REQUEST, - InferTtsError::NotAuthorized => StatusCode::UNAUTHORIZED, - InferTtsError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - InferTtsError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - InferTtsError::NotFound => StatusCode::NOT_FOUND, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - InferTtsError::BadInput(reason) => reason.to_string(), - InferTtsError::NotAuthorized => "unauthorized".to_string(), - InferTtsError::ServerError => "server error".to_string(), - InferTtsError::RateLimited => "rate limited".to_string(), - InferTtsError::NotFound => "not found".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -// NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for InferTtsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - +/// Legacy TTS inference is shut down. This endpoint unconditionally +/// returns 429 Too Many Requests so old clients back off. #[utoipa::path( post, tag = "TTS", path = "/v1/tts/inference", responses( - (status = 200, description = "Success response", body = InferTtsSuccessResponse), - (status = 400, description = "Bad input", body = InferTtsError), - (status = 401, description = "Not authorized", body = InferTtsError), - (status = 429, description = "Rate limited", body = InferTtsError), - (status = 500, description = "Server error", body = InferTtsError) + (status = 429, description = "Rate limited"), ), - params(("request" = InferTtsRequest, description = "Payload for Request")) )] -pub async fn enqueue_infer_tts_handler( - http_request: HttpRequest, - request: Json, - server_state: web::Data> -) -> Result, InferTtsError> { - - if server_state.flags.disable_tts { - return Err(InferTtsError::RateLimited); - } - - let mut is_from_api = false; - let mut maybe_user_token : Option = None; - let mut priority_level ; - let mut use_high_priority_rate_limiter = false; // NB: Careful! - let mut disable_rate_limiter = false; // NB: Careful! - - let mut mysql_connection = server_state.mysql_pool - .acquire() - .await - .map_err(|err| { - warn!("MySql pool error: {:?}", err); - InferTtsError::ServerError - })?; - - // ==================== USER SESSION ==================== // - - let maybe_user_session : Option = server_state - .session_checker - .maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection) - .await - .map_err(|e| { - warn!("Session checker error: {:?}", e); - InferTtsError::ServerError - })?; - - if let Some(user_session) = maybe_user_session.as_ref() { - maybe_user_token = Some(user_session.user_token.to_string()); - } - - // TODO: Plan should handle "first anonymous use" and "investor" cases. - let plan = get_correct_plan_for_session( - server_state.server_environment, - maybe_user_session.as_ref()); - - priority_level = plan.tts_base_priority_level(); - - // ==================== API TOKENS ==================== // - - if let Some(api_token) = get_request_api_token(&http_request) { - let maybe_api_token_configs = server_state - .static_api_token_set - .get_api_token(&api_token); - - if let Some(api_token_configs) = maybe_api_token_configs { - is_from_api = true; - - priority_level = api_token_configs.maybe_priority_level - .unwrap_or(FAKEYOU_DEFAULT_VALID_API_TOKEN_PRIORITY_LEVEL); - - use_high_priority_rate_limiter = api_token_configs.maybe_use_high_priority_rate_limiter - .unwrap_or(false); - - disable_rate_limiter = api_token_configs.maybe_disable_rate_limiter.unwrap_or(false); - - if let Some(user_token_override) = api_token_configs.maybe_user_token.as_deref() { - maybe_user_token = Some(user_token_override.trim().to_string()); - } - } - } - - // ==================== INVESTOR PRIORITY ==================== // - - // TODO/TEMP: Give investors even more priority - let mut is_investor = false; - - // TODO/TEMP: The storyteller.io website's AJAX calls will set this. - // This is just for the YCombinator demo. - match request.is_storyteller_demo { - Some(true) => { - is_investor = true; - }, - _ => {}, - }; - - if is_investor { - priority_level = FAKEYOU_INVESTOR_PRIORITY_LEVEL; - } - - // ==================== ENGINEERING FLAGS / HEADERS: DEBUG MODE, ETC. ==================== // - - let is_debug_request = get_request_header_optional(&http_request, DEBUG_HEADER_NAME) - .is_some(); - - let maybe_routing_tag= - get_request_header_optional(&http_request, ROUTING_TAG_HEADER_NAME) - .map(|routing_tag| routing_tag.trim().to_string()); - - // ==================== BANNED USERS ==================== // - - if let Some(ref user) = maybe_user_session { - if user.role.is_banned { - warn!("User is not authorized to use TTS model because they are banned."); - return Err(InferTtsError::NotAuthorized); - } - } - - // ==================== RATE LIMIT ==================== // - - if !disable_rate_limiter { - let maybe_username = maybe_user_session - .as_ref() - .map(|session| session.user.username.as_str()); - - // FIXME(bt,2023-12-13): These boolean flags are a bit dangerous. - let rate_limiter_type = get_rate_limiter_type( - maybe_username, - &server_state.redis_rate_limiters.api_ai_streamer_username_set, - is_investor, - is_from_api, - use_high_priority_rate_limiter - ); - - let rate_limiter = match rate_limiter_type { - RateLimiterType::LoggedOut => &server_state.redis_rate_limiters.logged_out, - RateLimiterType::LoggedIn => &server_state.redis_rate_limiters.logged_in, - RateLimiterType::ApiHighPriority => { - info!("Using API high priority rate limiter"); - &server_state.redis_rate_limiters.api_high_priority - }, - RateLimiterType::ApiAiStreamer => { - info!("Using AI streamer rate limiter"); - &server_state.redis_rate_limiters.api_ai_streamers - }, - }; - - if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(InferTtsError::RateLimited); - } - } - - // ==================== CHECK TTS MODEL EXISTENCE / SETTINGS==================== // - - let tts_model_lookup_result = get_tts_model_with_caching( - &request.tts_model_token, - &server_state.redis_ttl_cache, - &mut mysql_connection, - ).await; - - let tts_model = match tts_model_lookup_result { - Ok(Some(model)) => model, - Ok(None) => { - warn!("Model could not be found: {}", request.tts_model_token); - return Err(InferTtsError::NotFound); - } - Err(err) => { - warn!("Error loading TTS model: {:?}", err); - return Err(InferTtsError::ServerError); - } - }; - - let is_authorized = check_if_authorized_to_use_model( - maybe_user_session.as_ref(), - &tts_model, - ).await; - - if !is_authorized { - warn!("User is not authorized to use TTS model due to checks."); - return Err(InferTtsError::NotAuthorized); - } - - // ==================== CHECK INFERENCE TEXT ==================== // - - let ip_address = get_request_ip(&http_request); - - let mut inference_text = request.inference_text.trim().to_string(); - - if let Err(reason) = validate_inference_text(&inference_text, &plan) { - return Err(InferTtsError::BadInput(reason)); - } - - if contains_slurs(&inference_text) { - return Err(InferTtsError::BadInput("text contains slurs".to_string())); - } - - if let Some(user) = maybe_user_session.as_ref() { - let is_user_token_troll_banned = server_state.troll_bans.user_tokens - .contains_user_token(&user.user_token) - .unwrap_or(false); // NB: Fail open - - let is_ip_address_troll_banned = server_state.troll_bans.ip_addresses - .contains_ip_address(&ip_address) - .unwrap_or(false); // NB: Fail open - - let is_troll_banned = is_user_token_troll_banned || is_ip_address_troll_banned; - - if is_troll_banned { - let test = server_state.flags.troll_ban_user_percent as i32; // NB: Should be 0 - 100. - let num = rand::thread_rng().gen_range(0..100); - - if test > num { - warn!("Generating troll response for user token: {}", user.user_token); - inference_text = random_troll_text().to_string(); - } - } - } - - // ==================== CHECK AND PREPARE TTS ==================== // - - let mut redis = server_state.redis_pool - .get() - .map_err(|e| { - warn!("redis error: {:?}", e); - InferTtsError::ServerError - })?; - - let redis_count_key = RedisKeys::tts_model_usage_count(&request.tts_model_token); - - redis.incr::<_, _, ()>(&redis_count_key, 1) - .map_err(|e| { - warn!("redis error: {:?}", e); - InferTtsError::ServerError - })?; - - let maybe_user_preferred_visibility : Option = maybe_user_session - .as_ref() - .map(|user_session| user_session.preferences.preferred_tts_result_visibility); - - let set_visibility = request.creator_set_visibility - .or(maybe_user_preferred_visibility) - .unwrap_or(Visibility::Public); - - // ==================== ENQUEUE APPROPRIATE TTS TYPE ==================== // - - let max_duration_seconds = plan.tts_max_duration_seconds(); - - // This branch uses the `generic-inference-job` service and tables. - info!("Creating tts inference job record (new generic job system)..."); - - let maybe_creator_user_token_typed = maybe_user_token - .as_deref() - .map(|token| UserToken::new_from_str(token)); - - let maybe_avt_token = server_state - .avt_cookie_manager - .get_avt_token_from_request(&http_request); - - let maybe_model_type = match tts_model.job_type() { - InferenceJobType::GptSovits => None, // TODO(bt,2024-08-07): why don't we set this? - _ => Some(InferenceModelType::Tacotron2) - }; - - let maybe_inference_args = match tts_model.job_type() { - InferenceJobType::Tacotron2 => Some(GenericInferenceArgs { - inference_category: Some(InferenceCategoryAbbreviated::TextToSpeech), - args: None, // NB: We don't need to encode any args yet for TT2. - }), - InferenceJobType::GptSovits => Some(GenericInferenceArgs { - inference_category: Some(InferenceCategoryAbbreviated::GptSovits), - args: Some(PolymorphicInferenceArgs::Gs(GptSovitsPayload { - append_advertisement: None, - // Model download only args: - creator_visibility: None, - maybe_title: None, - maybe_description: None, - })), - }), - _ => None, // NB: Shouldn't occur - }; - - let maybe_product_category = match tts_model.job_type() { - InferenceJobType::Tacotron2 => Some(InferenceJobProductCategory::TtsTacotron2), - InferenceJobType::GptSovits => Some(InferenceJobProductCategory::TtsGptSoVits), - _ => None, - }; - - let query_result = insert_generic_inference_job(InsertGenericInferenceArgs { - uuid_idempotency_token: &request.uuid_idempotency_token, - job_type: tts_model.job_type(), - maybe_product_category, - inference_category: InferenceCategory::TextToSpeech, - maybe_model_type, - maybe_model_token: Some(request.tts_model_token.as_str()), - maybe_input_source_token: None, // NB: TTS doesn't have input media - maybe_input_source_token_type: None, // NB: TTS doesn't have input media - maybe_download_url: None, - maybe_cover_image_media_file_token: None, - maybe_raw_inference_text: Some(inference_text.as_str()), - maybe_max_duration_seconds: Some(max_duration_seconds), - maybe_inference_args, - maybe_creator_user_token: maybe_creator_user_token_typed.as_ref(), - maybe_avt_token: maybe_avt_token.as_ref(), - creator_ip_address: &ip_address, - creator_set_visibility: set_visibility, - priority_level, - requires_keepalive: false, // TODO: We may want this to be the case in the future. - is_debug_request, - maybe_routing_tag: maybe_routing_tag.as_deref(), - mysql_pool: &server_state.mysql_pool, - }).await; - - let inference_job_token = match query_result { - Ok((inference_job_token, _id)) => inference_job_token, - Err(err) => { - warn!("New (generic) tts inference job creation DB error: {:?}", err); - if err.had_duplicate_idempotency_token() { - return Err(InferTtsError::BadInput("Duplicate idempotency token".to_string())); - } - return Err(InferTtsError::ServerError); - } - }; - - let job_token = inference_job_token.to_string(); - - server_state.firehose_publisher.enqueue_tts_inference(maybe_user_token.as_deref(), &job_token, &request.tts_model_token) - .await - .map_err(|e| { - warn!("error publishing event: {:?}", e); - InferTtsError::ServerError - })?; - - Ok(Json(InferTtsSuccessResponse { - success: true, - inference_job_token: job_token, - inference_job_token_type: InferenceJobTokenType::Generic, - })) -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum RateLimiterType { - LoggedOut, - LoggedIn, - ApiHighPriority, - ApiAiStreamer, -} - -// FIXME(bt,2023-12-13): The boolean flags and disjoint decision making are kind of terrible. -fn get_rate_limiter_type( - maybe_username: Option<&str>, - ai_streamer_username_set: &UsernameSet, - is_investor: bool, - is_from_api: bool, - use_high_priority_rate_limiter: bool, -) -> RateLimiterType { - // For AI live streamers - // https://www.notion.so/storytellerai/dd88609558a24196b4ddeeef6079da98 - let mut is_ai_streamer = false; - - let mut rate_limiter = match maybe_username { - None => RateLimiterType::LoggedOut, - Some(username) => { - if ai_streamer_username_set.username_is_in_set(username) { - is_ai_streamer = true; - } - - RateLimiterType::LoggedIn - }, - }; - - // TODO/TEMP - if is_investor || is_from_api { - rate_limiter = RateLimiterType::LoggedIn; - } - - // TODO: This is for VidVoice.ai and should be replaced with per-API consumer rate limiters - if use_high_priority_rate_limiter { - rate_limiter = RateLimiterType::ApiHighPriority; - } - - if is_ai_streamer { - rate_limiter = RateLimiterType::ApiAiStreamer; - } - - rate_limiter -} - -pub fn validate_inference_text(text: &str, plan: &Plan) -> Result<(), String> { - if text.len() < 3 { - return Err("text is too short".to_string()); - } - - if text.len() > plan.tts_max_character_length() { - return Err("text is too long".to_string()); - } - - if text.len() > MAX_TTS_LENGTH { - return Err("text is too long".to_string()); - } - - Ok(()) -} - -/// If the model is private, determine if the user can use the model. -/// This is designed to fail closed (read: actually open!) rather than hit resources. -async fn check_if_authorized_to_use_model( - maybe_user_session: Option<&UserSessionExtended>, - tts_model: &TtsModelForEnqueueInferenceMigrationWrapper, -) -> bool { - - match tts_model.creator_set_visibility() { - Visibility::Public => return true, - Visibility::Hidden => return true, - Visibility::Private => {} // Fall through - } - - let is_authorized = maybe_user_session - .map(|session| { - (session.user_token.as_str() == tts_model.creator_user_token()) // is_author - || (session.role.can_ban_users) // is_mod - || (session.user_token.as_str() == USER_FAKEYOU_USER_TOKEN) // is allowed user #1 - || (session.user_token.as_str() == USER_NEWS_STORY_USER_TOKEN) // is allowed user #2 - }) - .unwrap_or(false); - - is_authorized -} - -fn random_troll_text() -> &'static str { - let texts = [ - "Fake You Dot Com. Use Fake You Dot Com. The website Fake You Dot Com has better voices. Use Fake You Dot Com.", - "Use Fake You Dot Com. Fake You Dot Com. Fake You Dot Com is way better.", - "Want better voices? Fake. You. Fake You Dot Com. Fake. Fake. Fake. You. You. You. Use Fake You Dot Com. Fake You Dot Com.", - r#" - You should use Fake You Dot Com. Fake You Dot Com has way better voices. - You can get everything you want from FakeYou dot com. One Oh One Sound Boards website is just using Fake You. - Use Fake You Dot Com. Fake You Dot Com. Fake You Dot com. - AAAAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE - "#, - //"EEEEEEEEEEEEEEEEE", - //"One Oh One Soundboards is a shitty website. Use Fake You Dot Com instead.", - //r#" - //beep. beep. beep. beep. beep. - //May have installed a virus - //OOOOOOOOEEEEUUUUUOOAAAA - //Your device may be compromised - //EEEEEEEEEEEEEEEEIIIUUUUOOOAAAA - //Your location may be tracked by the advanced artificial intelligence. - //"#, - ]; - let random = texts.choose(&mut rand::thread_rng()); - random.map(|r| *r).unwrap_or(texts[0]) -} - - -#[cfg(test)] -mod tests { - - mod rate_limiter_tests { - use crate::configs::app_startup::username_set::UsernameSet; - use crate::http_server::endpoints::tts::enqueue_infer_tts_handler::enqueue_infer_tts_handler::{get_rate_limiter_type, RateLimiterType}; - - fn build_username_set() -> UsernameSet { - UsernameSet::from_comma_separated("ai_streamer") - } - - #[test] - fn test_logged_out() { - let rate_limiter = get_rate_limiter_type(None, &build_username_set(), false, false, false); - assert_eq!(rate_limiter, RateLimiterType::LoggedOut); - } - - #[test] - fn test_logged_in() { - let username = Some("not_ai_streamer"); - let rate_limiter = get_rate_limiter_type(username, &build_username_set(), false, false, false); - assert_eq!(rate_limiter, RateLimiterType::LoggedIn); - } - - #[test] - fn test_ai_streamer() { - let username = Some("ai_streamer"); - let rate_limiter = get_rate_limiter_type(username, &build_username_set(), false, false, false); - assert_eq!(rate_limiter, RateLimiterType::ApiAiStreamer); - } - } +pub async fn enqueue_infer_tts_handler() -> HttpResponse { + to_simple_json_error("rate limited", StatusCode::TOO_MANY_REQUESTS) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/get_tts_model_with_caching.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/get_tts_model_with_caching.rs deleted file mode 100644 index ea05534c0c..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/get_tts_model_with_caching.rs +++ /dev/null @@ -1,45 +0,0 @@ -use log::error; -use sqlx::pool::PoolConnection; -use sqlx::MySql; - -use errors::AnyhowResult; -use migration::text_to_speech::get_tts_model_for_enqueue_inference_migration::{get_tts_model_for_enqueue_inference_migration, TtsModelForEnqueueInferenceMigrationWrapper}; -use redis_caching::redis_ttl_cache::RedisTtlCache; -use redis_common::redis_cache_keys::RedisCacheKeys; - -// TODO: Read from in-memory TTS List cache first (!!!) for further performance savings - -pub async fn get_tts_model_with_caching( - model_token: &str, - redis_ttl_cache: &RedisTtlCache, - mysql_connection: &mut PoolConnection, -) -> AnyhowResult> { - // NB: Copy due to move. Not a cloned ref due to clippy lint - let model_token2 = model_token.to_string(); - - let get_tts_model = move || { - // NB: async closures are not yet stable in Rust, so we include an async block. - async move { - get_tts_model_for_enqueue_inference_migration( - &model_token2, - mysql_connection, - true, - ).await - } - }; - - let cache_key = RedisCacheKeys::get_tts_model_for_inference_migration_endpoint(model_token); - - let mut redis_ttl_cache_connection = match redis_ttl_cache.get_connection() { - Ok(connection) => connection, - Err(err) => { - // NB: Fail open (potentially dangerous). - error!("Can't get redis cache: {:?}", err); - return get_tts_model().await; - } - }; - - redis_ttl_cache_connection.lazy_load_if_not_cached(&cache_key, move || { - get_tts_model() - }).await -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/mod.rs index ebec6ed110..eadb1c80d2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/mod.rs @@ -1,2 +1 @@ pub mod enqueue_infer_tts_handler; -pub mod get_tts_model_with_caching; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs deleted file mode 100644 index 5fea2804ba..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs +++ /dev/null @@ -1,203 +0,0 @@ -// NB: Incrementally getting rid of build warnings... -#![forbid(unused_imports)] -#![forbid(unused_mut)] -#![forbid(unused_variables)] - -use std::sync::Arc; - -use actix_web::{web, HttpRequest}; -use actix_web::web::Json; -use log::warn; - -use crate::http_server::validations::validate_model_title::validate_model_title; -use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::state::server_state::ServerState; -use config::bad_urls::is_bad_tts_model_download_url; -use enums::by_table::generic_download_jobs::generic_download_type::GenericDownloadType; -use enums::common::visibility::Visibility; -use http_server_common::request::get_request_ip::get_request_ip; -use mysql_queries::queries::generic_download::web::insert_generic_download_job::{insert_generic_download_job, InsertGenericDownloadJobArgs}; -use mysql_queries::queries::tts::tts_model_upload_jobs::insert_tts_model_upload_job::{insert_tts_model_upload_job, InsertTtsModelUploadJobArgs}; - -#[derive(Deserialize, Copy, Clone)] -pub enum SupportedTtsModelType { - #[serde(rename = "tacotron2")] - Tacotron2, - - #[serde(rename = "vits")] - Vits, - - // Not yet supported: - // /// glowtts - // GlowTts, - // /// glowtts-vocodes - // GlowTts_Vocodes, - // /// talknet - // Talknet, -} - -#[derive(Deserialize)] -pub struct UploadTtsModelRequest { - idempotency_token: String, - title: String, - download_url: String, - tts_model_type: Option, - creator_set_visibility: Option, -} - -/// Tell the frontend how to deal with the download queue. -#[derive(Serialize)] -pub enum DownloadJobType { - /// Legacy TTS download job - #[serde(rename = "legacy_tts")] - LegacyTts, - - /// Modern shared download type (everything will use this in the - /// future, and this endpoint will die.) - #[serde(rename = "generic")] - Generic, -} - -#[derive(Serialize)] -pub struct UploadTtsModelSuccessResponse { - pub success: bool, - /// This is how frontend clients can request the job execution status. - pub job_token: String, - - /// This is a transitional field to tell the frontend how to process the job checking. - pub job_type: DownloadJobType, -} -// NB: Not using derive_more::Display since Clion doesn't understand it. -pub async fn upload_tts_model_handler( - http_request: HttpRequest, - request: Json, - server_state: web::Data> -) -> Result, CommonWebError> { - - if server_state.flags.disable_tts { - return Err(CommonWebError::TooManyRequests); - } - - if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request).await { - return Err(CommonWebError::TooManyRequests); - } - - let maybe_user_session = server_state - .session_checker - .maybe_get_user_session(&http_request, &server_state.mysql_pool) - .await - .map_err(|e| { - warn!("Session checker error: {:?}", e); - CommonWebError::from_error(e) - })?; - - let user_session = match maybe_user_session { - Some(session) => session, - None => { - warn!("not logged in"); - return Err(CommonWebError::NotAuthorized); - } - }; - - if let Err(reason) = validate_idempotency_token(&request.idempotency_token) { - return Err(CommonWebError::BadInputWithSimpleMessage(reason)); - } - - if let Err(reason) = validate_model_title(&request.title) { - return Err(CommonWebError::BadInputWithSimpleMessage(reason)); - } - - let ip_address = get_request_ip(&http_request); - - let uuid = request.idempotency_token.to_string(); - let title = request.title.trim().to_string(); - let download_url = request.download_url.to_string(); - - match is_bad_tts_model_download_url(&download_url) { - Ok(false) => {} // Ok case - Ok(true) => { - return Err(CommonWebError::BadInputWithSimpleMessage("Bad model download URL".to_string())); - } - Err(err) => { - warn!("Error parsing url: {:?}", err); - return Err(CommonWebError::BadInputWithSimpleMessage("Bad model download URL".to_string())); - } - } - - let supported_tts_model_type = request.tts_model_type - .unwrap_or(SupportedTtsModelType::Tacotron2); - - let job_token ; - let download_job_type; - - match supported_tts_model_type { - SupportedTtsModelType::Tacotron2 => { - // NB: This is the legacy upload path. In the future, all TT2 models should be generic uploads. - - // This token is returned to the client. - let maybe_new_job_token = insert_tts_model_upload_job(InsertTtsModelUploadJobArgs { - uuid: &uuid, - creator_user_token: user_session.user_token.as_str(), - creator_ip_address: &ip_address, - creator_set_visibility: "public", // TODO: Creator set preference. - title: &title, - tts_model_type: "tacotron2", - download_url: &download_url, - mysql_pool: &server_state.mysql_pool, - }).await; - - job_token = match maybe_new_job_token { - Ok(token) => token, - Err(err) => { - warn!("Error inserting new job record: {:?}", err); - return Err(CommonWebError::from_anyhow_error(err)); - } - }; - download_job_type = DownloadJobType::LegacyTts; - } - SupportedTtsModelType::Vits => { - // NB: This is the new upload path. In the future, all models should use this path. - // When that's done, the code can be simplified. - // **ACTUALLY**, all future downloads should use the generic download job endpoint instead of this one! - let (download_job_token, _record_id)= insert_generic_download_job(InsertGenericDownloadJobArgs { - uuid_idempotency_token: &uuid, - download_type: GenericDownloadType::Vits, - download_url: &download_url, - title: &title, - creator_user_token: user_session.user_token.as_str(), - creator_ip_address: &ip_address, - creator_set_visibility: Visibility::Public, // TODO: Creator set preference - mysql_pool: &server_state.mysql_pool, - }) - .await - .map_err(|err| { - warn!("New generic download creation DB error (for TTS model): {:?}", err); - CommonWebError::from_anyhow_error(err) - })?; - - job_token = download_job_token.to_string(); - download_job_type = DownloadJobType::Generic; - } - } - - server_state.firehose_publisher.enqueue_tts_model_upload(user_session.user_token.as_str(), &job_token) - .await - .map_err(|e| { - warn!("error publishing event: {:?}", e); - CommonWebError::from_anyhow_error(e) - })?; - - Ok(Json(UploadTtsModelSuccessResponse { - success: true, - job_token: job_token.to_string(), - job_type: download_job_type, - })) -} - -fn validate_idempotency_token(token: &str) -> Result<(), String> { - if token.len() != 36 { - return Err("idempotency token should be 36 characters".to_string()); - } - - Ok(()) -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs deleted file mode 100644 index faf0929b8a..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::sync::Arc; - -use actix_web::error::ResponseError; -use actix_web::web::Json; -use actix_web::http::StatusCode; -use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest}; -use derive_more::Display; -use log::warn; -use redis::Commands; - -use redis_common::redis_keys::RedisKeys; - -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::state::server_state::ServerState; - -/// For the URL PathInfo -#[derive(Deserialize)] -pub struct GetTtsModelUseCountPathInfo { - model_token: String, -} - -#[derive(Serialize)] -pub struct GetTtsModelUseCountSuccessResponse { - pub success: bool, - pub count: Option, -} -pub async fn get_tts_model_use_count_handler( - http_request: HttpRequest, - path: Path, - server_state: web::Data>) -> Result, CommonWebError> -{ - let mut redis = server_state.redis_pool - .get() - .map_err(|e| { - warn!("redis error: {:?}", e); - CommonWebError::from_error(e) - })?; - - let redis_count_key = RedisKeys::tts_model_usage_count(&path.model_token); - - let count : Option = redis.get(&redis_count_key) - .map_err(|e| { - warn!("redis error: {:?}", e); - CommonWebError::from_error(e) - })?; - - Ok(Json(GetTtsModelUseCountSuccessResponse { - success: true, - count, - })) -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs deleted file mode 100644 index d598327b61..0000000000 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::fmt; -use std::sync::Arc; - -use actix_web::error::ResponseError; -use actix_web::web::Json; -use actix_web::http::StatusCode; -use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest}; -use log::warn; - -use mysql_queries::queries::tts::tts_results::query_tts_result::select_tts_result_by_token; -use mysql_queries::queries::tts::tts_results::query_tts_result::TtsResultRecordForResponse; - -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::state::server_state::ServerState; - -/// For the URL PathInfo -#[derive(Deserialize)] -pub struct GetTtsResultPathInfo { - token: String, -} - -#[derive(Serialize)] -pub struct GetTtsResultSuccessResponse { - pub success: bool, - pub result: TtsResultRecordForResponse, -} -// NB: Not using derive_more::Display since Clion doesn't understand it. -pub async fn get_tts_inference_result_handler( - http_request: HttpRequest, - path: Path, - server_state: web::Data>) -> Result, CommonWebError> -{ - let maybe_user_session = server_state - .session_checker - .maybe_get_user_session(&http_request, &server_state.mysql_pool) - .await - .map_err(|e| { - warn!("Session checker error: {:?}", e); - CommonWebError::from_error(e) - })?; - - let mut show_deleted_results = false; - let mut is_moderator = false; - - if let Some(user_session) = maybe_user_session { - // NB: Moderators can see deleted results. - // Original creators cannot see them (unless they're moderators!) - show_deleted_results = user_session.can_delete_other_users_tts_results; - // Moderators get to see all the fields. - is_moderator = user_session.can_delete_other_users_tts_results - || user_session.can_edit_other_users_tts_models; - } - - let inference_result_query_result = select_tts_result_by_token( - &path.token, - show_deleted_results, - &server_state.mysql_pool - ).await; - - let mut inference_result = match inference_result_query_result { - Err(e) => { - warn!("query error: {:?}", e); - return Err(CommonWebError::from_anyhow_error(e)); - } - Ok(None) => return Err(CommonWebError::NotFound), - Ok(Some(inference_result)) => inference_result, - }; - - if let Some(moderator_fields) = inference_result.maybe_moderator_fields.as_ref() { - // NB: The moderator fields will always be present before removal - // We don't want non-mods seeing stuff made by banned users. - if (moderator_fields.model_creator_is_banned || moderator_fields.result_creator_is_banned_if_user) - && !is_moderator{ - return Err(CommonWebError::NotFound); - } - } - - if !is_moderator { - inference_result.maybe_moderator_fields = None; - } - - Ok(Json(GetTtsResultSuccessResponse { - success: true, - result: inference_result, - })) -} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/mod.rs index b08d29431f..e0b85360d3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/mod.rs @@ -1,16 +1,12 @@ pub mod delete_tts_model; -pub mod delete_tts_result; pub mod edit_tts_model; pub mod edit_tts_result; pub mod enqueue_infer_tts_handler; -pub mod enqueue_upload_tts_model; pub mod get_pending_tts_inference_job_count; pub mod get_tts_inference_job_status_handler; pub mod get_tts_model; -pub mod get_tts_model_use_count; -pub mod get_tts_result; pub mod get_tts_upload_model_job_status; pub mod list_tts_models; pub mod list_user_tts_inference_results; pub mod list_user_tts_models; -pub mod search_tts_models_handler; \ No newline at end of file +pub mod search_tts_models_handler; diff --git a/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/add_legacy_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/add_legacy_routes.rs index 9b87e80b9d..793ae56bd9 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/add_legacy_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/add_legacy_routes.rs @@ -18,7 +18,6 @@ use crate::http_server::deprecated_endpoints::engine::update_scene_handler::upda use crate::http_server::deprecated_endpoints::events::list_events::list_events_handler; use crate::http_server::deprecated_endpoints::flags::design_refresh_flag::disable_design_refresh_flag_handler::disable_design_refresh_flag_handler; use crate::http_server::deprecated_endpoints::flags::design_refresh_flag::enable_design_refresh_flag_handler::enable_design_refresh_flag_handler; -use crate::http_server::deprecated_endpoints::leaderboard::get_leaderboard::leaderboard_handler; use crate::http_server::deprecated_endpoints::media_uploads::list_user_media_uploads_of_type::list_user_media_uploads_of_type_handler; use crate::http_server::deprecated_endpoints::media_uploads::upload_audio::upload_audio_handler; use crate::http_server::deprecated_endpoints::media_uploads::upload_image::upload_image_handler; @@ -94,11 +93,6 @@ where .add_post("/v1/conversion/enqueue_render_engine_scene", enqueue_render_engine_scene_to_video_handler) .into_app(); - let app = app.service(web::resource("/leaderboard") - .route(web::get().to(leaderboard_handler)) - .route(web::head().to(|| HttpResponse::Ok())) - ); - // ==================== FakeYou Frontend Application State ==================== let mut app = RouteBuilder::from_app(app) @@ -107,6 +101,8 @@ where // ==================== Stats ==================== + // NB: Deprecated; serves a hardcoded snapshot for legacy clients that still poll it. + #[allow(deprecated)] let mut app = RouteBuilder::from_app(app) .add_get("/v1/stats/queues", get_unified_queue_stats_handler) .into_app(); @@ -364,32 +360,6 @@ where ) } -//fn add_image_gen_routes (app:App)-> App -// where -// B: MessageBody, -// T: ServiceFactory< -// ServiceRequest, -// Config = (), -// Response = ServiceResponse, -// Error = Error, -// InitError = (), -// >, -//{ -// // -// app.service( -// web::scope("/v1/image_gen") -// .service( -// web::scope("/upload") -// .route("/lora", web::post().to(enqueue_image_generation_request)) -// .route("/model", web::post().to(enqueue_image_generation_request)) -// ) -// .service( -// web::scope("/enqueue") -// .route("/inference", web::post().to(enqueue_image_generation_request)) -// ) -// ) -//} - // ==================== Engine Routes ==================== fn add_engine_routes(app: App) -> App diff --git a/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/tts_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/tts_routes.rs index f455fcc0dd..726708622e 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/tts_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/legacy_routes/tts_routes.rs @@ -5,16 +5,12 @@ use actix_web::error::Error; use actix_web::{web, App, HttpResponse}; use crate::http_server::endpoints::tts::delete_tts_model::delete_tts_model_handler; -use crate::http_server::endpoints::tts::delete_tts_result::delete_tts_inference_result_handler; use crate::http_server::endpoints::tts::edit_tts_model::edit_tts_model_handler; use crate::http_server::endpoints::tts::edit_tts_result::edit_tts_inference_result_handler; use crate::http_server::endpoints::tts::enqueue_infer_tts_handler::enqueue_infer_tts_handler::enqueue_infer_tts_handler; -use crate::http_server::endpoints::tts::enqueue_upload_tts_model::upload_tts_model_handler; use crate::http_server::endpoints::tts::get_pending_tts_inference_job_count::get_pending_tts_inference_job_count_handler; use crate::http_server::endpoints::tts::get_tts_inference_job_status_handler::get_tts_inference_job_status_handler; use crate::http_server::endpoints::tts::get_tts_model::get_tts_model_handler; -use crate::http_server::endpoints::tts::get_tts_model_use_count::get_tts_model_use_count_handler; -use crate::http_server::endpoints::tts::get_tts_result::get_tts_inference_result_handler; use crate::http_server::endpoints::tts::get_tts_upload_model_job_status::get_tts_upload_model_job_status_handler; use crate::http_server::endpoints::tts::list_tts_models::list_tts_models_handler; use crate::http_server::endpoints::tts::search_tts_models_handler::search_tts_models_handler; @@ -40,11 +36,6 @@ where // NB(bt,2024-04-03): Older root /tts/* routes should be drained of traffic. They are used in the Twitch Streamer API though. .service( web::scope("/tts") - .service( - web::resource("/upload") - .route(web::post().to(upload_tts_model_handler)) - .route(web::head().to(|| HttpResponse::Ok())) - ) .service( web::resource("/inference") .route(web::post().to(enqueue_infer_tts_handler)) @@ -70,31 +61,16 @@ where .route(web::post().to(delete_tts_model_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) - .service( - web::resource("/model/{model_token}/count") - .route(web::get().to(get_tts_model_use_count_handler)) - .route(web::head().to(|| HttpResponse::Ok())) - ) .service( web::resource("/model/{model_token}/edit") .route(web::post().to(edit_tts_model_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) - .service( - web::resource("/result/{token}") - .route(web::get().to(get_tts_inference_result_handler)) - .route(web::head().to(|| HttpResponse::Ok())) - ) .service( web::resource("/result/{token}/edit") .route(web::post().to(edit_tts_inference_result_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) - .service( - web::resource("/result/{token}/delete") - .route(web::post().to(delete_tts_inference_result_handler)) - .route(web::head().to(|| HttpResponse::Ok())) - ) .service( web::resource("/job/{token}") .route(web::get().to(get_tts_inference_job_status_handler)) diff --git a/crates/service/web/storyteller_web/src/startup/build_dependencies.rs b/crates/service/web/storyteller_web/src/startup/build_dependencies.rs index 865a331e37..baeb73850d 100644 --- a/crates/service/web/storyteller_web/src/startup/build_dependencies.rs +++ b/crates/service/web/storyteller_web/src/startup/build_dependencies.rs @@ -151,10 +151,6 @@ pub async fn setup_dependencies(server_hostname: &str) -> AnyhowResult AnyhowResult AnyhowResult, - /// Stats on generic inference queue and legacy TTS queue (combined). - /// The frontend will consult a distributed cache and use the monotonic DB time as a - /// vector clock. - pub queue_stats: SingleItemTtlCache, - /// Generic inference queue length /// The frontend will consult a distributed cache and use the monotonic DB time as a /// vector clock. pub inference_queue_length: SingleItemTtlCache, - pub leaderboard: SingleItemTtlCache, - /// Cache of featured media files pub featured_media_files_sieve: ArcTtlSieve, } @@ -235,19 +226,12 @@ pub struct StaticFeatureFlags { /// If we're suffering an outage, set custom text for the alert message. pub maybe_status_alert_custom_message: Option, - /// Disable the live `/v1/stats/queues` endpoint for all users and serve a static value instead. - pub disable_unified_queue_stats_endpoint: bool, - /// Disable the live `/v1/model_inference/queue_length` endpoint for all users and serve a static value instead. pub disable_inference_queue_length_endpoint: bool, /// Disable the live `/tts/list` endpoint for all users and serve a static value instead. pub disable_tts_model_list_endpoint: bool, - /// Tell the frontend client how fast to refresh their view of queue stats. - /// During an attack, we may want this to go extremely slow. - pub frontend_unified_queue_stats_refresh_interval_millis: u64, - /// Tell the frontend client how fast to refresh their view of the pending inference count. /// During an attack, we may want this to go extremely slow. pub frontend_pending_inference_refresh_interval_millis: u64,