mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-30 15:25:57 -04:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbfcb29dde | ||
|
|
d3382f61fe | ||
|
|
0f74242b2e | ||
|
|
528677cd47 | ||
|
|
88769baa4f | ||
|
|
f4d85363cb | ||
|
|
c3dadcfd1d | ||
|
|
517abc0462 | ||
|
|
bbbd921eab | ||
|
|
4ae2500207 | ||
|
|
e54c29cd43 | ||
|
|
59f5da1be3 |
@@ -186,8 +186,8 @@ CREATE TABLE users (
|
||||
-- ========== TRACKING ==========
|
||||
|
||||
-- Where the user signed up from (NOT NECESSARILY AN ENUM!)
|
||||
-- For now this will be "fakeyou" and "storyteller", but we may extend
|
||||
-- or overload this to handle other cases or metadata.
|
||||
-- For now this will be "artcraft", "fakeyou", or "storyteller",
|
||||
-- though we may extend or overload this to handle other cases or metadata.
|
||||
maybe_source VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- How users created their account
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
|
||||
const DEFAULT_DATA_DIR : &str = "Artcraft";
|
||||
|
||||
/// Note: Tauri appends ".log" to the end of the filename.
|
||||
const LOG_FILE_NAME : &str = "application_debug";
|
||||
const LOG_FILE_NAME : &str = "artcraft_debug";
|
||||
|
||||
/// The path to the application data directory, which includes "asset" and "weights" data.
|
||||
#[derive(Clone)]
|
||||
|
||||
+4
-3
@@ -3,11 +3,12 @@
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
use sqlx::MySqlPool;
|
||||
use enums::by_table::users::user_signup_method::UserSignupMethod;
|
||||
use crate::queries::users::user::create::create_account_error::CreateAccountError;
|
||||
use crate::queries::users::user::create::create_account_generic::{create_account_generic, GenericCreateAccountArgs};
|
||||
use crate::utils::transactor::Transactor;
|
||||
use enums::by_table::users::user_signup_method::UserSignupMethod;
|
||||
use enums::by_table::users::user_signup_source::UserSignupSource;
|
||||
use sqlx::MySqlPool;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct CreateAccountFromEmailPasswordArgs<'a> {
|
||||
@@ -17,7 +18,7 @@ pub struct CreateAccountFromEmailPasswordArgs<'a> {
|
||||
pub email_gravatar_hash: &'a str,
|
||||
pub password_hash: &'a str,
|
||||
pub ip_address: &'a str,
|
||||
pub maybe_source: Option<&'a str>,
|
||||
pub maybe_source: Option<UserSignupSource>,
|
||||
|
||||
/// In production code, send this as `None`.
|
||||
/// Only provide an external user token for db integration tests and db seeding tools.
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ use crate::queries::users::user::create::create_account_error::CreateAccountErro
|
||||
use crate::queries::users::user::create::create_account_generic::{create_account_generic, GenericCreateAccountArgs};
|
||||
use crate::utils::transactor::Transactor;
|
||||
use enums::by_table::users::user_signup_method::UserSignupMethod;
|
||||
use enums::by_table::users::user_signup_source::UserSignupSource;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// SSO accounts do not have passwords at account creation
|
||||
@@ -22,7 +23,7 @@ pub struct CreateAccountFromGoogleSsoArgs<'a> {
|
||||
pub email_confirmed_by_google: bool,
|
||||
|
||||
pub ip_address: &'a str,
|
||||
pub maybe_source: Option<&'a str>,
|
||||
pub maybe_source: Option<UserSignupSource>,
|
||||
|
||||
/// Comma separated string of feature flags.
|
||||
pub maybe_feature_flags: Option<&'a str>,
|
||||
|
||||
+4
-3
@@ -5,9 +5,10 @@
|
||||
|
||||
use crate::queries::users::user::create::create_account_error::CreateAccountError;
|
||||
use crate::utils::transactor::Transactor;
|
||||
use enums::by_table::users::user_signup_method::UserSignupMethod;
|
||||
use enums::by_table::users::user_signup_source::UserSignupSource;
|
||||
use log::warn;
|
||||
use sqlx::error::Error::Database;
|
||||
use enums::by_table::users::user_signup_method::UserSignupMethod;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct GenericCreateAccountArgs<'a> {
|
||||
@@ -28,7 +29,7 @@ pub struct GenericCreateAccountArgs<'a> {
|
||||
pub is_without_password: bool,
|
||||
|
||||
pub ip_address: &'a str,
|
||||
pub maybe_source: Option<&'a str>,
|
||||
pub maybe_source: Option<UserSignupSource>,
|
||||
pub maybe_signup_method: Option<UserSignupMethod>,
|
||||
|
||||
/// Comma separated string of feature flags.
|
||||
@@ -119,7 +120,7 @@ SET
|
||||
args.ip_address,
|
||||
args.ip_address,
|
||||
|
||||
args.maybe_source,
|
||||
args.maybe_source.map(|s| s.to_str()),
|
||||
args.maybe_signup_method.map(|m| m.to_str()),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod user_feature_flag;
|
||||
pub mod user_signup_method;
|
||||
pub mod user_signup_method;
|
||||
pub mod user_signup_source;
|
||||
@@ -0,0 +1,138 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Used in the `users` table in a `VARCHAR(255)` (which should be a `VARCHAR(16)`) field, `maybe_source`.
|
||||
///
|
||||
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UserSignupSource {
|
||||
#[serde(rename = "artcraft")]
|
||||
ArtCraft,
|
||||
|
||||
#[serde(rename = "fakeyou")]
|
||||
FakeYou,
|
||||
|
||||
#[serde(rename = "storyteller")]
|
||||
Storyteller,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
impl_enum_display_and_debug_using_to_str!(UserSignupSource);
|
||||
impl_mysql_enum_coders!(UserSignupSource);
|
||||
impl_mysql_from_row!(UserSignupSource);
|
||||
|
||||
/// NB: Legacy API for older code.
|
||||
impl UserSignupSource {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::ArtCraft => "artcraft",
|
||||
Self::FakeYou => "fakeyou",
|
||||
Self::Storyteller => "storyteller",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"artcraft" => Ok(Self::ArtCraft),
|
||||
"fakeyou" => Ok(Self::FakeYou),
|
||||
"storyteller" => Ok(Self::Storyteller),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants() -> BTreeSet<Self> {
|
||||
// NB: BTreeSet is sorted
|
||||
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
|
||||
BTreeSet::from([
|
||||
Self::ArtCraft,
|
||||
Self::FakeYou,
|
||||
Self::Storyteller,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::by_table::users::user_signup_source::UserSignupSource;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
mod serde {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(UserSignupSource::ArtCraft, "artcraft");
|
||||
assert_serialization(UserSignupSource::FakeYou, "fakeyou");
|
||||
assert_serialization(UserSignupSource::Storyteller, "storyteller");
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_methods {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(UserSignupSource::ArtCraft.to_str(), "artcraft");
|
||||
assert_eq!(UserSignupSource::FakeYou.to_str(), "fakeyou");
|
||||
assert_eq!(UserSignupSource::Storyteller.to_str(), "storyteller");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(UserSignupSource::from_str("artcraft").unwrap(), UserSignupSource::ArtCraft);
|
||||
assert_eq!(UserSignupSource::from_str("fakeyou").unwrap(), UserSignupSource::FakeYou);
|
||||
assert_eq!(UserSignupSource::from_str("storyteller").unwrap(), UserSignupSource::Storyteller);
|
||||
assert!(UserSignupSource::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod manual_variant_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = UserSignupSource::all_variants();
|
||||
assert_eq!(variants.len(), 3);
|
||||
assert_eq!(variants.pop_first(), Some(UserSignupSource::ArtCraft));
|
||||
assert_eq!(variants.pop_first(), Some(UserSignupSource::FakeYou));
|
||||
assert_eq!(variants.pop_first(), Some(UserSignupSource::Storyteller));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
mod mechanical_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn variant_length() {
|
||||
use strum::IntoEnumIterator;
|
||||
assert_eq!(UserSignupSource::all_variants().len(), UserSignupSource::iter().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
for variant in UserSignupSource::all_variants() {
|
||||
assert_eq!(variant, UserSignupSource::from_str(variant.to_str()).unwrap());
|
||||
assert_eq!(variant, UserSignupSource::from_str(&format!("{}", variant)).unwrap());
|
||||
assert_eq!(variant, UserSignupSource::from_str(&format!("{:?}", variant)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_length_ok_for_database() {
|
||||
const MAX_LENGTH : usize = 16;
|
||||
for variant in UserSignupSource::all_variants() {
|
||||
let serialized = variant.to_str();
|
||||
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
|
||||
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -7,7 +7,7 @@ use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::http_server::requests::get_request_signup_source::get_request_signup_source;
|
||||
use crate::http_server::requests::get_request_signup_source::get_request_signup_source_enum;
|
||||
use crate::http_server::session::http::http_user_session_manager::HttpUserSessionManager;
|
||||
use crate::http_server::validations::is_reserved_username::is_reserved_username;
|
||||
use crate::http_server::validations::validate_passwords::validate_passwords;
|
||||
@@ -17,6 +17,7 @@ use crate::util::enroll_in_studio::enroll_in_studio;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use enums::by_table::users::user_signup_source::UserSignupSource;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use log::{info, warn};
|
||||
@@ -36,6 +37,10 @@ pub struct CreateAccountRequest {
|
||||
pub password: String,
|
||||
pub password_confirmation: String,
|
||||
pub email_address: String,
|
||||
|
||||
/// Optional: Source of the signup, e.g. "artcraft", "fakeyou", "storyteller", etc.
|
||||
/// If not provided, we try to infer it from the Origin header instead.
|
||||
pub signup_source: Option<UserSignupSource>,
|
||||
}
|
||||
|
||||
#[derive(ToSchema, Serialize)]
|
||||
@@ -184,7 +189,11 @@ pub async fn create_account_handler(
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_source = get_request_signup_source(&http_request);
|
||||
let mut maybe_source = request.signup_source;
|
||||
|
||||
if maybe_source.is_none() {
|
||||
maybe_source = get_request_signup_source_enum(&http_request);
|
||||
}
|
||||
|
||||
let create_account_result = create_account_from_email_and_password(
|
||||
&mysql_pool,
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountErrorResponse;
|
||||
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::NewSsoAccountInfo;
|
||||
use crate::http_server::requests::get_request_signup_source::get_request_signup_source;
|
||||
use crate::http_server::requests::get_request_signup_source::{get_request_signup_source, get_request_signup_source_enum};
|
||||
use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
use crate::util::email_to_gravatar::email_to_gravatar;
|
||||
use crate::util::generate_random_username::generate_random_username;
|
||||
@@ -42,7 +42,7 @@ pub async fn handle_new_sso_account_for_new_user(
|
||||
let ip_address = get_request_ip(&args.http_request);
|
||||
let user_email_gravatar_hash = email_to_gravatar(&args.user_email_address);
|
||||
|
||||
let mut maybe_source = get_request_signup_source(&args.http_request);
|
||||
let mut maybe_source = get_request_signup_source_enum(&args.http_request);
|
||||
|
||||
let mut maybe_user_token = None;
|
||||
let mut maybe_user_display_name = None;
|
||||
|
||||
+98
-3
@@ -1,7 +1,9 @@
|
||||
use actix_helpers::extractors::get_request_origin_uri::get_request_origin_uri;
|
||||
use actix_web::HttpRequest;
|
||||
use enums::by_table::users::user_signup_source::UserSignupSource;
|
||||
use log::warn;
|
||||
|
||||
const ARTCRAFT : &str = "artcraft";
|
||||
const FAKEYOU : &str = "fakeyou";
|
||||
const STORYTELLER : &str = "storyteller";
|
||||
|
||||
@@ -25,7 +27,9 @@ pub fn get_request_signup_source(http_request: &HttpRequest) -> Option<&'static
|
||||
match maybe_origin {
|
||||
Ok(Some(uri)) => {
|
||||
if let Some(host) = uri.host() {
|
||||
if host.contains(STORYTELLER) {
|
||||
if host.contains(ARTCRAFT) {
|
||||
return Some(ARTCRAFT);
|
||||
} else if host.contains(STORYTELLER) {
|
||||
return Some(STORYTELLER);
|
||||
} else if host.contains(FAKEYOU) {
|
||||
return Some(FAKEYOU);
|
||||
@@ -46,13 +50,42 @@ pub fn get_request_signup_source(http_request: &HttpRequest) -> Option<&'static
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_request_signup_source_enum(http_request: &HttpRequest) -> Option<UserSignupSource> {
|
||||
get_request_signup_source(http_request)
|
||||
.and_then(|source_str| UserSignupSource::from_str(source_str).ok())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::http_server::requests::get_request_signup_source::get_request_signup_source;
|
||||
use actix_web::test::TestRequest;
|
||||
|
||||
mod origin_header {
|
||||
mod origin_header_str {
|
||||
use super::*;
|
||||
use crate::http_server::requests::get_request_signup_source::get_request_signup_source;
|
||||
|
||||
#[test]
|
||||
fn artcraft_dot_ai() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://artcraft.ai"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source(&request), Some("artcraft"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_artcraft_dot_com() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://getartcraft.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source(&request), Some("artcraft"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_dot_get_artcraft_dot_com() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://api.getartcraft.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source(&request), Some("artcraft"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fakeyou_dot_com() {
|
||||
@@ -86,4 +119,66 @@ mod tests {
|
||||
assert_eq!(get_request_signup_source(&request), Some("storyteller"));
|
||||
}
|
||||
}
|
||||
|
||||
mod origin_header_enum {
|
||||
use super::*;
|
||||
use crate::http_server::requests::get_request_signup_source::get_request_signup_source_enum;
|
||||
use enums::by_table::users::user_signup_source::UserSignupSource;
|
||||
|
||||
#[test]
|
||||
fn artcraft_dot_ai() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://artcraft.ai"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_artcraft_dot_com() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://getartcraft.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_dot_get_artcraft_dot_com() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://api.getartcraft.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fakeyou_dot_com() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://fakeyou.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::FakeYou));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_dot_fakeyou_dot_com() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://api.fakeyou.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::FakeYou));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storyteller_dot_ai() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://storyteller.ai"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::Storyteller));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_dot_storyteller_dot_ai() {
|
||||
let request = TestRequest::get()
|
||||
.insert_header(("origin", "https://api.storyteller.ai"))
|
||||
.to_http_request();
|
||||
assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::Storyteller));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+73
@@ -0,0 +1,73 @@
|
||||
use crate::http_server::routes::application_routes::comments_routes::add_comments_routes;
|
||||
use crate::http_server::routes::application_routes::credits_routes::add_credits_routes;
|
||||
use crate::http_server::routes::application_routes::featured_item_routes::add_featured_item_routes;
|
||||
use crate::http_server::routes::application_routes::generate_routes::add_generate_routes;
|
||||
use crate::http_server::routes::application_routes::job_routes::add_job_routes;
|
||||
use crate::http_server::routes::application_routes::media_files_routes::add_media_file_routes;
|
||||
use crate::http_server::routes::application_routes::moderation_routes::add_moderator_routes;
|
||||
use crate::http_server::routes::application_routes::stripe_artcraft_routes::add_stripe_artcraft_routes;
|
||||
use crate::http_server::routes::application_routes::subscription_routes::add_subscription_routes;
|
||||
use crate::http_server::routes::application_routes::tag_routes::add_tag_routes;
|
||||
use crate::http_server::routes::application_routes::tts_routes::add_tts_routes;
|
||||
use crate::http_server::routes::application_routes::user_bookmarks_routes::add_user_bookmarks_routes;
|
||||
use crate::http_server::routes::application_routes::user_rating_routes::add_user_rating_routes;
|
||||
use crate::http_server::routes::application_routes::user_routes::add_user_routes;
|
||||
use crate::http_server::routes::application_routes::voice_conversion_routes::add_voice_conversion_routes;
|
||||
use crate::http_server::routes::application_routes::webhook_routes::add_webhook_routes;
|
||||
use crate::http_server::routes::application_routes::weights_routes::add_weights_routes;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::{App, Error};
|
||||
use billing_component::default_routes::add_suggested_stripe_billing_routes;
|
||||
use crate::http_server::routes::application_routes::prompts_routes::add_prompts_routes;
|
||||
|
||||
/// Add the core application routes.
|
||||
pub fn add_application_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
|
||||
let mut app = add_moderator_routes(app); // /moderation
|
||||
|
||||
// Artcraft surface area
|
||||
app = add_generate_routes(app); // /v1/generate/...
|
||||
app = add_webhook_routes(app); // /v1/webhooks/... (fal)
|
||||
|
||||
// Remaining FakeYou surface area
|
||||
app = add_tag_routes(app); // /v1/tags
|
||||
app = add_tts_routes(app); // /tts
|
||||
app = add_weights_routes(app); // v1/weights
|
||||
app = add_voice_conversion_routes(app); // /v1/voice_conversion
|
||||
|
||||
// Media files routes
|
||||
app = add_media_file_routes(app); // /v1/media_files/...
|
||||
app = add_featured_item_routes(app); // /v1/featured_item/...
|
||||
app = add_prompts_routes(app); // /v1/prompts/...
|
||||
|
||||
// Job system
|
||||
app = add_job_routes(app);
|
||||
|
||||
// User and user-adjacent routes
|
||||
app = add_comments_routes(app); // /v1/comments/...
|
||||
app = add_user_bookmarks_routes(app); // /v1/user_bookmarks/...
|
||||
app = add_user_rating_routes(app); // /v1/user_rating/...
|
||||
app = add_user_routes(app); // /create_account, /session, /login, /logout, etc.
|
||||
|
||||
// Artcraft Billing pieces
|
||||
app = add_credits_routes(app); // /v1/credits/...
|
||||
app = add_stripe_artcraft_routes(app); // /v1/stripe_artcraft/...
|
||||
app = add_subscription_routes(app); // /v1/subscriptions/...
|
||||
|
||||
// FakeYou Billing component - still necessary for FakeYou monetization
|
||||
app = add_suggested_stripe_billing_routes(app); // /stripe, billing, webhooks, etc.
|
||||
|
||||
app
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
use crate::http_server::endpoints::comments::create_comment_handler::create_comment_handler;
|
||||
use crate::http_server::endpoints::comments::delete_comment_handler::delete_comment_handler;
|
||||
use crate::http_server::endpoints::comments::list_comments_handler::list_comments_handler;
|
||||
use actix_helpers::route_builder::RouteBuilder;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::App;
|
||||
|
||||
pub fn add_comments_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
RouteBuilder::from_app(app)
|
||||
.add_get("/v1/comments/list/{entity_type}/{entity_token}", list_comments_handler)
|
||||
.add_post("/v1/comments/new", create_comment_handler)
|
||||
.add_post("/v1/comments/delete/{comment_token}", delete_comment_handler)
|
||||
.into_app()
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
|
||||
use crate::http_server::endpoints::featured_items::create_featured_item_handler::create_featured_item_handler;
|
||||
use crate::http_server::endpoints::featured_items::delete_featured_item_handler::delete_featured_item_handler;
|
||||
use crate::http_server::endpoints::featured_items::get_is_featured_item_handler::get_is_featured_item_handler;
|
||||
|
||||
// ==================== FEATURED ITEM ROUTES ====================
|
||||
|
||||
pub fn add_featured_item_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/featured_item")
|
||||
.service(web::resource("/create")
|
||||
.route(web::post().to(create_featured_item_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/delete")
|
||||
.route(web::delete().to(delete_featured_item_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/is_featured/{entity_type}/{entity_token}")
|
||||
.route(web::get().to(get_is_featured_item_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
mod comments_routes;
|
||||
mod credits_routes;
|
||||
mod featured_item_routes;
|
||||
mod generate_routes;
|
||||
mod job_routes;
|
||||
mod media_files_routes;
|
||||
mod moderation_routes;
|
||||
mod stripe_artcraft_routes;
|
||||
mod subscription_routes;
|
||||
mod tag_routes;
|
||||
mod tts_routes;
|
||||
mod user_bookmarks_routes;
|
||||
mod user_rating_routes;
|
||||
mod user_routes;
|
||||
mod voice_conversion_routes;
|
||||
mod webhook_routes;
|
||||
mod weights_routes;
|
||||
pub (super) mod add_application_routes;
|
||||
mod prompts_routes;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
use crate::http_server::endpoints::prompts::create_prompt_handler::create_prompt_handler;
|
||||
use crate::http_server::endpoints::prompts::get_prompt_handler::get_prompt_handler;
|
||||
use actix_helpers::route_builder::RouteBuilder;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::App;
|
||||
|
||||
pub fn add_prompts_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
RouteBuilder::from_app(app)
|
||||
// NB: This poor RouteBuilder utility requires that POST comes first, otherwise the GET glob
|
||||
// will capture it and force 504 Method Not Allowed for all POSTs.
|
||||
.add_post("/v1/prompts/create", create_prompt_handler)
|
||||
.add_get("/v1/prompts/{token}", get_prompt_handler)
|
||||
.into_app()
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
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_f5_tts_handler::enqueue_infer_f5_tts_handler::enqueue_infer_f5_tts_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::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;
|
||||
|
||||
// ==================== TTS ROUTES ====================
|
||||
|
||||
pub fn add_tts_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
// NB(bt,2024-04-03): Newer /v1/tts/* routes don't have public use, but we'll start using it
|
||||
app.service(web::resource("/v1/tts/inference")
|
||||
.route(web::post().to(enqueue_infer_tts_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/v1/tts/f5_inference")
|
||||
.route(web::post().to(enqueue_infer_f5_tts_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
// 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))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/list")
|
||||
.route(web::get().to(list_tts_models_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/search")
|
||||
.route(web::post().to(search_tts_models_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/model/{token}")
|
||||
.route(web::get().to(get_tts_model_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/model/{token}/delete")
|
||||
.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))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/upload_model_job/{token}")
|
||||
.route(web::get().to(get_tts_upload_model_job_status_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/queue_length")
|
||||
.route(web::get().to(get_pending_tts_inference_job_count_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
use crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::batch_get_user_bookmarks_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::create_user_bookmark_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::delete_user_bookmark_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_entity_handler::list_user_bookmarks_for_entity_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_user_handler::list_user_bookmarks_for_user_handler;
|
||||
use actix_helpers::route_builder::RouteBuilder;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::App;
|
||||
|
||||
pub fn add_user_bookmarks_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
RouteBuilder::from_app(app)
|
||||
.add_get("/v1/user_bookmarks/batch", batch_get_user_bookmarks_handler)
|
||||
.add_post("/v1/user_bookmarks/create", create_user_bookmark_handler)
|
||||
.add_post("/v1/user_bookmarks/delete/{user_bookmark_token}", delete_user_bookmark_handler)
|
||||
//.add_get("/v1/user_bookmarks/list/session", list_user_bookmarks_for_session_handler)
|
||||
.add_get("/v1/user_bookmarks/list/user/{username}", list_user_bookmarks_for_user_handler)
|
||||
.add_get("/v1/user_bookmarks/list/entity/{entity_type}/{entity_token}", list_user_bookmarks_for_entity_handler)
|
||||
.into_app()
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
|
||||
use crate::http_server::endpoints::user_ratings::batch_get_user_rating_handler::batch_get_user_rating_handler;
|
||||
use crate::http_server::endpoints::user_ratings::get_user_rating_handler::get_user_rating_handler;
|
||||
use crate::http_server::endpoints::user_ratings::set_user_rating_handler::set_user_rating_handler;
|
||||
|
||||
|
||||
pub fn add_user_rating_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/user_rating")
|
||||
.service(web::resource("/batch")
|
||||
.route(web::get().to(batch_get_user_rating_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/rate")
|
||||
.route(web::post().to(set_user_rating_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/view/{entity_type}/{entity_token}")
|
||||
.route(web::get().to(get_user_rating_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
|
||||
use crate::http_server::endpoints::voice_conversion::enqueue_seed_vc_inference_handler::enqueue_infer_seed_vc_handler;
|
||||
use crate::http_server::endpoints::voice_conversion::enqueue_voice_conversion_inference_handler::enqueue_voice_conversion_inference_handler;
|
||||
use crate::http_server::endpoints::voice_conversion::list_voice_conversion_models_handler::list_voice_conversion_models_handler;
|
||||
|
||||
pub fn add_voice_conversion_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(
|
||||
web::scope("/v1/voice_conversion")
|
||||
.service(
|
||||
web::resource("/inference")
|
||||
.route(web::post().to(enqueue_voice_conversion_inference_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/seed_vc_inference")
|
||||
.route(web::post().to(enqueue_infer_seed_vc_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/model_list")
|
||||
.route(web::get().to(list_voice_conversion_models_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+648
@@ -0,0 +1,648 @@
|
||||
use crate::http_server::deprecated_endpoints::animation::enqueue_face_animation::enqueue_face_animation_handler;
|
||||
use crate::http_server::deprecated_endpoints::animation::enqueue_rerender_animation::enqueue_rerender_animation_handler;
|
||||
use crate::http_server::deprecated_endpoints::api_tokens::create_api_token::create_api_token_handler;
|
||||
use crate::http_server::deprecated_endpoints::api_tokens::delete_api_token::delete_api_token_handler;
|
||||
use crate::http_server::deprecated_endpoints::api_tokens::edit_api_token::edit_api_token_handler;
|
||||
use crate::http_server::deprecated_endpoints::api_tokens::list_api_tokens::list_api_tokens_handler;
|
||||
use crate::http_server::deprecated_endpoints::categories::create_category::create_category_handler;
|
||||
use crate::http_server::deprecated_endpoints::categories::get_category::get_category_handler;
|
||||
use crate::http_server::deprecated_endpoints::categories::tts::assign_tts_category::assign_tts_category_handler;
|
||||
use crate::http_server::deprecated_endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories_handler;
|
||||
use crate::http_server::deprecated_endpoints::categories::tts::list_tts_categories::list_tts_categories_handler;
|
||||
use crate::http_server::deprecated_endpoints::categories::tts::list_tts_model_assigned_categories::list_tts_model_assigned_categories_handler;
|
||||
use crate::http_server::deprecated_endpoints::conversion::enqueue_fbx_to_gltf_handler::enqueue_fbx_to_gltf_handler;
|
||||
use crate::http_server::deprecated_endpoints::conversion::enqueue_render_engine_scene_to_video_handler::enqueue_render_engine_scene_to_video_handler;
|
||||
use crate::http_server::deprecated_endpoints::engine::create_scene_handler::create_scene_handler;
|
||||
use crate::http_server::deprecated_endpoints::engine::get_scene_handler::get_scene_handler;
|
||||
use crate::http_server::deprecated_endpoints::engine::update_scene_handler::update_scene_handler;
|
||||
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::investor_demo::disable_demo_mode_handler::disable_demo_mode_handler;
|
||||
use crate::http_server::deprecated_endpoints::investor_demo::enable_demo_mode_handler::enable_demo_mode_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;
|
||||
use crate::http_server::deprecated_endpoints::media_uploads::upload_media::upload_media_handler;
|
||||
use crate::http_server::deprecated_endpoints::mocap::enqueue_mocapnet::enqueue_mocapnet_handler;
|
||||
use crate::http_server::deprecated_endpoints::vocoders::get_vocoder::get_vocoder_handler;
|
||||
use crate::http_server::deprecated_endpoints::vocoders::list_vocoders::list_vocoders_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::delete_w2l_result::delete_w2l_inference_result_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::delete_w2l_template::delete_w2l_template_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::edit_w2l_result::edit_w2l_inference_result_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::edit_w2l_template::edit_w2l_template_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::enqueue_infer_w2l_with_uploads::enqueue_infer_w2l_with_uploads;
|
||||
use crate::http_server::deprecated_endpoints::w2l::enqueue_upload_w2l_template::upload_w2l_template_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::get_w2l_inference_job_status::get_w2l_inference_job_status_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::get_w2l_result::get_w2l_inference_result_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::get_w2l_template::get_w2l_template_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::get_w2l_template_use_count::get_w2l_template_use_count_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::get_w2l_upload_template_job_status::get_w2l_upload_template_job_status_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::list_w2l_templates::list_w2l_templates_handler;
|
||||
use crate::http_server::deprecated_endpoints::w2l::set_w2l_template_mod_approval::set_w2l_template_mod_approval_handler;
|
||||
use crate::http_server::endpoints::download_job::enqueue_generic_download::enqueue_generic_download_handler;
|
||||
use crate::http_server::endpoints::download_job::get_generic_upload_job_status::get_generic_download_job_status_handler;
|
||||
use crate::http_server::endpoints::misc::enable_alpha_easy_handler::enable_alpha_easy_handler;
|
||||
use crate::http_server::endpoints::misc::enable_alpha_handler::enable_alpha_handler;
|
||||
use crate::http_server::endpoints::trending::list_trending_tts_models::list_trending_tts_models_handler;
|
||||
use crate::http_server::endpoints::voice_clone_requests::check_if_voice_clone_request_submitted::check_if_voice_clone_request_submitted_handler;
|
||||
use crate::http_server::endpoints::voice_clone_requests::create_voice_clone_request::create_voice_clone_request_handler;
|
||||
use crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::enqueue_tts_request;
|
||||
use crate::http_server::endpoints::voice_designer::inference::enqueue_vc_request::enqueue_vc_request;
|
||||
use crate::http_server::endpoints::voice_designer::voice_dataset_samples::delete_sample::delete_sample_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_dataset_samples::list_samples_by_dataset::list_samples_by_dataset_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_dataset_samples::upload_zs_sample::upload_zs_sample_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_datasets::create_dataset::create_dataset_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_datasets::delete_dataset::delete_dataset_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_datasets::get_dataset::get_dataset_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_datasets::list_datasets_by_session::list_datasets_by_session_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_datasets::list_datasets_by_user::list_datasets_by_user_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voice_datasets::update_dataset::update_dataset_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::create_voice::create_voice_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::delete_voice::delete_voice_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::get_voice::get_voice_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::list_available_voices::list_available_voices_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::list_voices_by_session::list_voices_by_session_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::list_voices_by_user::list_voices_by_user_handler;
|
||||
use crate::http_server::endpoints::voice_designer::voices::search_voices::search_voices;
|
||||
use crate::http_server::endpoints::voice_designer::voices::update_voice::update_voice_handler;
|
||||
use crate::http_server::routes::legacy_routes::beta_key_routes::add_beta_key_routes;
|
||||
use crate::http_server::routes::legacy_routes::control_plane_sora_routes::add_control_plane_sora_routes;
|
||||
use crate::http_server::routes::legacy_routes::desktop_vc_app_routes::add_desktop_vc_app_routes;
|
||||
use crate::http_server::routes::legacy_routes::image_studio_routes::add_image_studio_routes;
|
||||
use crate::http_server::routes::legacy_routes::model_download_routes::add_model_download_routes;
|
||||
use crate::http_server::routes::legacy_routes::studio_gen2_routes::add_studio_gen2_routes;
|
||||
use crate::http_server::routes::legacy_routes::workflow_routes::add_workflow_routes;
|
||||
use actix_helpers::route_builder::RouteBuilder;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
use crate::http_server::endpoints::app_state::get_app_state_handler::get_app_state_handler;
|
||||
use crate::http_server::endpoints::service::status_alert_handler::status_alert_handler;
|
||||
use crate::http_server::endpoints::stats::get_unified_queue_stats_handler::get_unified_queue_stats_handler;
|
||||
|
||||
pub fn add_legacy_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
let mut app = add_w2l_routes(app);
|
||||
|
||||
app = add_vocoder_routes(app); // /vocoder
|
||||
app = add_remote_download_routes(app); // /v1/remote_downloads (prev. /retrieval, aka. "generic_download_jobs")
|
||||
app = add_category_routes(app); // /category
|
||||
app = add_api_token_routes(app); // /api_tokens
|
||||
app = add_voice_clone_request_routes(app); // /voice_clone_requests
|
||||
app = add_investor_demo_routes(app); // /demo_mode // TODO: DEFINITELY TEMPORARY
|
||||
app = add_flag_routes(app); // /flag
|
||||
app = add_voice_designer_routes(app); // /v1/voice_designer
|
||||
app = add_beta_key_routes(app); // /v1/beta_keys
|
||||
app = add_model_download_routes(app);
|
||||
app = add_studio_gen2_routes(app);
|
||||
app = add_image_studio_routes(app);
|
||||
app = add_control_plane_sora_routes(app); // /v1/control_plane/...
|
||||
app = add_workflow_routes(app);
|
||||
app = add_engine_routes(app); // /v1/engine/...
|
||||
app = add_desktop_vc_app_routes(app); // /v1/vc/...
|
||||
app = add_media_upload_routes(app); // /v1/media_upload/...
|
||||
app = add_trending_routes(app); // /v1/trending/...
|
||||
|
||||
// ==================== Animations ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_post("/v1/animation/face_animation/create", enqueue_face_animation_handler)
|
||||
.add_post("/v1/animation/rerender/create", enqueue_rerender_animation_handler)
|
||||
.into_app();
|
||||
|
||||
// ==================== Mocap ========================
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_post("/v1/mocap/mocapnet/create", enqueue_mocapnet_handler)
|
||||
.into_app();
|
||||
|
||||
// ==================== Format Conversion ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_post("/v1/conversion/enqueue_fbx_to_gltf", enqueue_fbx_to_gltf_handler)
|
||||
.into_app();
|
||||
|
||||
// =================== BVH from Workflow ====================
|
||||
|
||||
// TODO(bt,2024-03-15): Migrate from "bvh_to_workflow" to "render_engine_scene"
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_post("/v1/conversion/enqueue_bvh_to_workflow", enqueue_render_engine_scene_to_video_handler)
|
||||
.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)
|
||||
.add_get("/v1/app_state", get_app_state_handler)
|
||||
.into_app();
|
||||
|
||||
// ==================== Stats ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_get("/v1/stats/queues", get_unified_queue_stats_handler)
|
||||
.into_app();
|
||||
|
||||
let app = app.service(
|
||||
web::resource("/events")
|
||||
.route(web::get().to(list_events_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
);
|
||||
|
||||
let app = app
|
||||
.service(enable_alpha_handler)
|
||||
.service(enable_alpha_easy_handler);
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================== VOCODER ROUTES ====================
|
||||
|
||||
fn add_vocoder_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(
|
||||
web::scope("/vocoder")
|
||||
.service(
|
||||
web::resource("/list")
|
||||
.route(web::get().to(list_vocoders_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/model/{token}")
|
||||
.route(web::get().to(get_vocoder_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== WAV2LIP ROUTES ====================
|
||||
|
||||
|
||||
fn add_w2l_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(
|
||||
web::scope("/w2l")
|
||||
.service(
|
||||
web::resource("/upload")
|
||||
.route(web::post().to(upload_w2l_template_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/inference")
|
||||
.route(web::post().to(enqueue_infer_w2l_with_uploads))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/list")
|
||||
.route(web::get().to(list_w2l_templates_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/template/{token}")
|
||||
.route(web::get().to(get_w2l_template_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/template/{template_token}/count")
|
||||
.route(web::get().to(get_w2l_template_use_count_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/template/{template_token}/edit")
|
||||
.route(web::post().to(edit_w2l_template_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/template/{token}/moderate")
|
||||
.route(web::post().to(set_w2l_template_mod_approval_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/template/{token}/delete")
|
||||
.route(web::post().to(delete_w2l_template_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/result/{token}")
|
||||
.route(web::get().to(get_w2l_inference_result_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/result/{token}/edit")
|
||||
.route(web::post().to(edit_w2l_inference_result_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/result/{token}/delete")
|
||||
.route(web::post().to(delete_w2l_inference_result_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/job/{token}")
|
||||
.route(web::get().to(get_w2l_inference_job_status_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/upload_template_job/{token}")
|
||||
.route(web::get().to(get_w2l_upload_template_job_status_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== RETRIEVAL ROUTES ("GENERIC_DOWNLOAD_JOBS") ====================
|
||||
|
||||
fn add_remote_download_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
RouteBuilder::from_app(app)
|
||||
// NB: These are the new route names
|
||||
.add_post("/v1/remote_download/enqueue", enqueue_generic_download_handler)
|
||||
.add_get("/v1/remote_download/job_status/{token}", get_generic_download_job_status_handler)
|
||||
// NB: These are the old, deprecated route names that should be removed
|
||||
.add_post("/retrieval/enqueue", enqueue_generic_download_handler)
|
||||
.add_get("/retrieval/job_status/{token}", get_generic_download_job_status_handler)
|
||||
.into_app()
|
||||
}
|
||||
|
||||
// ==================== CATEGORY ROUTES ====================
|
||||
|
||||
fn add_category_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(
|
||||
web::scope("/v1/category")
|
||||
.service(
|
||||
web::scope("/list")
|
||||
.service(
|
||||
web::resource("/tts")
|
||||
.route(web::get().to(list_tts_categories_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(
|
||||
web::scope("/computed_assignments")
|
||||
.service(
|
||||
web::resource("/tts")
|
||||
.route(web::get().to(list_fully_computed_assigned_tts_categories_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
)
|
||||
.service(
|
||||
web::scope("/category")
|
||||
.service(
|
||||
web::resource("/create")
|
||||
.route(web::post().to(create_category_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/view/{token}")
|
||||
.route(web::get().to(get_category_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::scope("/list")
|
||||
.service(
|
||||
web::resource("/tts")
|
||||
.route(web::get().to(list_tts_categories_handler)) // TODO: Deprecate with use of /v1* copy
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(
|
||||
web::scope("/assign")
|
||||
.service(
|
||||
web::resource("/tts")
|
||||
.route(web::post().to(assign_tts_category_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(
|
||||
web::scope("/assignments")
|
||||
.service(
|
||||
web::resource("/tts/{token}")
|
||||
.route(web::get().to(list_tts_model_assigned_categories_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== API TOKEN ROUTES ====================
|
||||
|
||||
fn add_api_token_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/api_tokens")
|
||||
.service(web::resource("/create")
|
||||
.route(web::post().to(create_api_token_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/edit")
|
||||
.route(web::post().to(edit_api_token_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/{api_token}/delete")
|
||||
.route(web::post().to(delete_api_token_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/list")
|
||||
.route(web::get().to(list_api_tokens_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== API TOKEN ROUTES ====================
|
||||
|
||||
fn add_voice_clone_request_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/voice_clone_requests")
|
||||
.service(web::resource("/create")
|
||||
.route(web::post().to(create_voice_clone_request_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/check")
|
||||
.route(web::post().to(check_if_voice_clone_request_submitted_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== INVESTOR DEMO MODE ====================
|
||||
|
||||
fn add_investor_demo_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/demo_mode")
|
||||
.service(web::resource("/enable")
|
||||
.route(web::get().to(enable_demo_mode_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/disable")
|
||||
.route(web::get().to(disable_demo_mode_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== FLAG ROUTES ====================
|
||||
|
||||
fn add_flag_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/flags")
|
||||
.service(web::scope("/design_refresh")
|
||||
.service(web::resource("/enable")
|
||||
.route(web::get().to(enable_design_refresh_flag_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/disable")
|
||||
.route(web::get().to(disable_design_refresh_flag_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== MEDIA UPLOAD ROUTES ====================
|
||||
|
||||
fn add_media_upload_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/media_uploads")
|
||||
.service(web::resource("/upload")
|
||||
.route(web::post().to(upload_media_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/upload_audio")
|
||||
.route(web::post().to(upload_audio_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/upload_image")
|
||||
.route(web::post().to(upload_image_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/by_session/{media_type}")
|
||||
.route(web::get().to(list_user_media_uploads_of_type_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== TRENDING ROUTES ====================
|
||||
|
||||
fn add_trending_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/trending")
|
||||
.service(web::resource("/tts_models")
|
||||
.route(web::get().to(list_trending_tts_models_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
//fn add_image_gen_routes<T,B> (app:App<T>)-> App<T>
|
||||
// where
|
||||
// B: MessageBody,
|
||||
// T: ServiceFactory<
|
||||
// ServiceRequest,
|
||||
// Config = (),
|
||||
// Response = ServiceResponse<B>,
|
||||
// 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<T, B>(app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = ()
|
||||
>
|
||||
{
|
||||
app.service(web::scope("/v1/engine")
|
||||
.service(web::resource("/create_scene")
|
||||
.route(web::post().to(create_scene_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/scene/{token}")
|
||||
.route(web::get().to(get_scene_handler))
|
||||
.route(web::post().to(update_scene_handler))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn add_voice_designer_routes<T,B> (app:App<T>)-> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(
|
||||
web::scope("/v1/voice_designer")
|
||||
.service(
|
||||
web::scope("/dataset")
|
||||
.route("/create", web::post().to(create_dataset_handler))
|
||||
.service(web::resource("/{dataset_token}/update")
|
||||
.route(web::post().to(update_dataset_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.route("/{dataset_token}", web::get().to(get_dataset_handler))
|
||||
.route("/{dataset_token}/delete", web::delete().to(delete_dataset_handler))
|
||||
.route("/user/{username}/list", web::get().to(list_datasets_by_user_handler))
|
||||
.route("/session/list", web::get().to(list_datasets_by_session_handler))
|
||||
)
|
||||
.service(
|
||||
web::scope("/voice")
|
||||
.route("/list", web::get().to(list_available_voices_handler))
|
||||
.route("/search", web::post().to(search_voices))
|
||||
.route("/create", web::post().to(create_voice_handler))
|
||||
.route("/{voice_token}", web::get().to(get_voice_handler))
|
||||
.route("/{voice_token}/update", web::post().to(update_voice_handler))
|
||||
.route("/{voice_token}/delete", web::delete().to(delete_voice_handler))
|
||||
.route("/user/{username}/list", web::get().to(list_voices_by_user_handler))
|
||||
.route("/session/list", web::get().to(list_voices_by_session_handler))
|
||||
)
|
||||
.service(
|
||||
web::scope("/sample")
|
||||
.route("/upload", web::post().to(upload_zs_sample_handler))
|
||||
.route("/{sample_token}/delete", web::delete().to(delete_sample_handler))
|
||||
.route("/dataset/{dataset_token}/list", web::get().to(list_samples_by_dataset_handler))
|
||||
)
|
||||
.service(
|
||||
web::scope("/inference")
|
||||
.route("/enqueue_tts", web::post().to(enqueue_tts_request))
|
||||
.route("/enqueue_vc", web::post().to(enqueue_vc_request))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::{web, App, Error, HttpResponse};
|
||||
|
||||
pub fn add_control_plane_routes<T, B> (app: App<T>) -> App<T>
|
||||
pub fn add_control_plane_sora_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
use crate::http_server::deprecated_endpoints::stubs::app_model_downloads::get_app_model_downloads_handler;
|
||||
use crate::http_server::deprecated_endpoints::stubs::app_news::get_app_news_handler;
|
||||
use crate::http_server::deprecated_endpoints::stubs::app_plans::get_app_plans_handler;
|
||||
use crate::http_server::deprecated_endpoints::stubs::post_app_analytics::post_app_analytics_handler;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
|
||||
pub fn add_desktop_vc_app_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/vc")
|
||||
.service(web::resource("/report_analytics")
|
||||
.route(web::post().to(post_app_analytics_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/news")
|
||||
.route(web::get().to(get_app_news_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/features")
|
||||
.route(web::get().to(get_app_plans_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/downloads")
|
||||
.route(web::get().to(get_app_model_downloads_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub (super) mod add_legacy_routes;
|
||||
mod beta_key_routes;
|
||||
mod control_plane_sora_routes;
|
||||
mod image_studio_routes;
|
||||
mod model_download_routes;
|
||||
mod studio_gen2_routes;
|
||||
mod workflow_routes;
|
||||
mod desktop_vc_app_routes;
|
||||
@@ -1,18 +1,4 @@
|
||||
mod add_control_plane_routes;
|
||||
mod add_credits_routes;
|
||||
mod add_generate_routes;
|
||||
mod add_image_studio_routes;
|
||||
mod add_stripe_artcraft_routes;
|
||||
mod add_studio_gen2_routes;
|
||||
mod add_webhook_routes;
|
||||
mod beta_key_routes;
|
||||
mod job_routes;
|
||||
mod media_files_routes;
|
||||
mod model_download_routes;
|
||||
mod moderation_routes;
|
||||
mod tag_routes;
|
||||
mod user_routes;
|
||||
mod weights_routes;
|
||||
mod workflow_routes;
|
||||
pub mod add_routes;
|
||||
mod add_subscription_routes;
|
||||
mod application_routes;
|
||||
mod legacy_routes;
|
||||
mod service_routes;
|
||||
@@ -0,0 +1,51 @@
|
||||
use crate::http_server::endpoints::misc::default_route_404::default_route_404;
|
||||
use crate::http_server::endpoints::misc::detect_locale_handler::detect_locale_handler;
|
||||
use crate::http_server::endpoints::misc::root_index::get_root_index;
|
||||
use crate::http_server::endpoints::service::health_check_handler::get_health_check_handler;
|
||||
use crate::http_server::endpoints::service::public_info_handler::get_public_info_handler;
|
||||
use crate::http_server::endpoints::service::status_alert_handler::status_alert_handler;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::{web, App, Error, HttpResponse};
|
||||
|
||||
/// Add the core service routes.
|
||||
pub fn add_service_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app
|
||||
.service(
|
||||
web::resource("/_status")
|
||||
.route(web::get().to(get_health_check_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
// TODO(bt,2023-01-21): Couldn't scope to /v1/, actix routing table might not like collision
|
||||
web::resource("/server_info")
|
||||
.route(web::get().to(get_public_info_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/detect_locale")
|
||||
.route(web::get().to(detect_locale_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(
|
||||
web::resource("/v1/status_alert_check")
|
||||
.route(web::get().to(status_alert_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/")
|
||||
.route(web::get().to(get_root_index))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.default_service( web::route().to(default_route_404))
|
||||
}
|
||||
@@ -11,6 +11,8 @@ use mysql_queries::queries::ip_bans::list_ip_bans::list_ip_bans;
|
||||
|
||||
const DYNAMIC_BAN_LIST_NAME : &str = "DYNAMIC_POLLING_IP_BAN_LIST";
|
||||
|
||||
const WAIT_BETWEEN_POLLS_MILLIS: u64 = 1_000 * 60 * 5; // 5 minutes
|
||||
|
||||
pub async fn poll_ip_bans(
|
||||
ip_ban_list: IpBanList,
|
||||
mysql_pool: MySqlPool,
|
||||
@@ -48,6 +50,6 @@ pub async fn poll_ip_bans(
|
||||
},
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(20_000)).await;
|
||||
tokio::time::sleep(Duration::from_millis(WAIT_BETWEEN_POLLS_MILLIS)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-VKW89949V0"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-VKW89949V0');
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<title>ArtCraft</title>
|
||||
<title>ArtCraft. AI Video and Images. Fast and Open Desktop App.</title>
|
||||
<meta name="description" content="ArtCraft is an Open Desktop app for generating AI Video and Images. You own ArtCraft!" />
|
||||
<meta name="keywords" content="ArtCraft, AI, AI Video, AI Images, Open Source" />
|
||||
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@@ -14,6 +26,7 @@
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -15,20 +15,29 @@ import Footer from "../../components/footer";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import ModelBadgeGrid from "../../components/model-badge-grid";
|
||||
|
||||
//const MAC_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_2025.06.04.dmg";
|
||||
//const MAC_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_2025.07.13.dmg";
|
||||
//const MAC_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_2025.07.17.dmg";
|
||||
//const MAC_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_2025.07.28.dmg";
|
||||
const MAC_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_2025.08.23.dmg"; // NB: The "08.22 (twenty two)" release is broken.
|
||||
//const MAC_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_2025.09.17.dmg"; // BROKEN
|
||||
// Current Mac version (update this when a new version is released)
|
||||
// If a rollback is necessary, roll back to the last known good date.
|
||||
//const MAC_VERSION = "2025.06.04";
|
||||
//const MAC_VERSION = "2025.07.13";
|
||||
//const MAC_VERSION = "2025.07.17";
|
||||
//const MAC_VERSION = "2025.07.28";
|
||||
//const MAC_VERSION = "2025.08.23"; // NB: The "08.22 (twenty two)" release is broken.
|
||||
const MAC_VERSION = "2025.09.23";
|
||||
|
||||
// Current Windows version (update this when a new version is released)
|
||||
// If a rollback is necessary, roll back to the last known good date.
|
||||
//const WINDOWS_VERSION = "2025.06.04";
|
||||
//const WINDOWS_VERSION = "2025.07.13";
|
||||
//const WINDOWS_VERSION = "2025.07.17";
|
||||
//const WINDOWS_VERSION = "2025.07.28";
|
||||
//const WINDOWS_VERSION = "2025.08.22";
|
||||
const WINDOWS_VERSION = "2025.09.23";
|
||||
|
||||
//const WINDOWS_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_2025.06.04.exe";
|
||||
//const WINDOWS_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_2025.07.13.exe";
|
||||
//const WINDOWS_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_2025.07.17.exe";
|
||||
//const WINDOWS_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_2025.07.28.exe";
|
||||
const WINDOWS_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_2025.08.22.exe";
|
||||
//const WINDOWS_LINK = "https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_2025.09.17.exe"; // BROKEN
|
||||
// Link to the current production Mac build
|
||||
const MAC_LINK = `https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/mac/ArtCraft_0.0.1_universal_${MAC_VERSION}.dmg`;
|
||||
|
||||
// Link to the current production Windows build
|
||||
const WINDOWS_LINK = `https://pub-3b58c874772a4e04b9c291815224128c.r2.dev/windows/ArtCraft_0.0.1_x64-setup_${WINDOWS_VERSION}.exe`;
|
||||
|
||||
const fadeUpVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
BASE_API="https://api.storyteller.ai"
|
||||
GOOGLE_API="https://storage.googleapis.com"
|
||||
FUNNEL_API="https://style.storyteller.ai"
|
||||
CDN_API="https://cdn.storyteller.ai"
|
||||
GRAVATAR_API="https://www.gravatar.com"
|
||||
DEPLOY_PRIME_URL="https://studio-staging.studio.storyteller.ai"
|
||||
REACT_APP_PUBLIC_POSTHOG_KEY="phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb"
|
||||
REACT_APP_PUBLIC_POSTHOG_UI="https://us.i.posthog.com"
|
||||
DEPLOY_CONTEXT="DEVELOPMENT"
|
||||
CONTEXT="DEVELOPMENT"
|
||||
UPLOAD_API_VIDEO="https://upload.storyteller.ai"
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Login | ArtCraft 3D</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<!-- <link rel="stylesheet" href="/src/styles.css" /> -->
|
||||
</head>
|
||||
<body className="overflow-hidden bg-ui-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/login.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Signup | ArtCraft 3D</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<!-- <link rel="stylesheet" href="/src/styles.css" /> -->
|
||||
</head>
|
||||
<body className="overflow-hidden bg-ui-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/signup.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,14 @@
|
||||
import { ApiManager, ApiResponse } from "./ApiManager";
|
||||
import { UserInfo } from "~/models";
|
||||
|
||||
interface SignupRequest {
|
||||
username: string;
|
||||
email_address: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
signup_source?: string;
|
||||
};
|
||||
|
||||
export class UsersApi extends ApiManager {
|
||||
public GetSession(): Promise<
|
||||
ApiResponse<{
|
||||
@@ -113,26 +121,26 @@ export class UsersApi extends ApiManager {
|
||||
email,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
signupSource,
|
||||
}: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
passwordConfirmation: string;
|
||||
signupSource?: string;
|
||||
}): Promise<ApiResponse<{ signedSession?: string }>> {
|
||||
const endpoint = `${this.getApiSchemeAndHost()}/v1/create_account`;
|
||||
const body = {
|
||||
let body: SignupRequest = {
|
||||
email_address: email,
|
||||
password,
|
||||
password_confirmation: passwordConfirmation,
|
||||
username,
|
||||
};
|
||||
if (!!signupSource) {
|
||||
body.signup_source = signupSource;
|
||||
}
|
||||
return await this.post<
|
||||
{
|
||||
username: string;
|
||||
email_address: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
},
|
||||
SignupRequest,
|
||||
{
|
||||
success: boolean;
|
||||
signed_session?: string;
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
import { useRef, StrictMode } from "react";
|
||||
import { useNavigate, useSearchParams, BrowserRouter } from "react-router-dom";
|
||||
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
faKey,
|
||||
faSpinnerThird,
|
||||
faUser,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { AUTH_STATUS } from "~/enums";
|
||||
import { authentication, login, logout } from "~/signals";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import { ConfirmationModal } from "~/components/reusable/ConfirmationModal";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
|
||||
import "./styles/normalize.css";
|
||||
import "./styles/tailwind.css";
|
||||
import "./styles/base.css";
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
|
||||
import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
import { GlobalSettingsManager } from "./pages/PageEnigma/GlobalSettingsManager";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
config.autoAddCss = false; /* eslint-disable import/first */
|
||||
|
||||
// TODO(bt,2025-04-19): Make these configurable
|
||||
const ENV = {
|
||||
GOOGLE_API: "https://studio.storyteller.ai",
|
||||
FUNNEL_API: "https://studio.storyteller.ai",
|
||||
CDN_API: "https://cdn-2.fakeyou.com",
|
||||
GRAVATAR_API: "https://studio.storyteller.ai",
|
||||
DEPLOY_PRIME_URL: "https://studio.storyteller.ai",
|
||||
};
|
||||
|
||||
export default function LoginScreen() {
|
||||
useSignals();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const { status: authStatus } = authentication;
|
||||
const authLoaderMessage = getAuthLoaderMessage();
|
||||
const shouldShowLoader = checkShouldShowLoader();
|
||||
|
||||
const showNoAccessModal = authStatus.value === AUTH_STATUS.NO_ACCESS;
|
||||
|
||||
const handleOnSumbit = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (formRef.current) {
|
||||
const form = new FormData(formRef.current);
|
||||
const usernameOrEmail = form.get("usernameOrEmail")?.toString();
|
||||
const password = form.get("password")?.toString();
|
||||
if (usernameOrEmail && password && login) {
|
||||
login({
|
||||
usernameOrEmail,
|
||||
password,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useSignalEffect(() => {
|
||||
const redirectPath = searchParams.get("redirect");
|
||||
if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
|
||||
navigate(redirectPath ? redirectPath : "/");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed flex h-full w-full flex-col items-center justify-center bg-[#1b1b1f] bg-center bg-repeat">
|
||||
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<img
|
||||
src="/resources/images/artcraft-logo-3.png"
|
||||
alt="ArtCraft Logo"
|
||||
className="h-9 select-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full max-w-xl rounded-xl shadow-lg">
|
||||
<div className="glass glass-no-hover relative mx-auto w-full rounded-none rounded-t-xl p-6 shadow-none">
|
||||
<h1 className="mb-8 text-center text-2xl font-bold">
|
||||
Log in to 3D Stage Editor
|
||||
</h1>
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleOnSumbit}
|
||||
className={twMerge(
|
||||
"flex flex-col gap-4",
|
||||
shouldShowLoader && "opacity-0",
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
label="Username or Email"
|
||||
icon={faUser}
|
||||
name="usernameOrEmail"
|
||||
placeholder="Username or Email"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
icon={faKey}
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<Button className="mt-4 py-3">Continue</Button>
|
||||
</form>
|
||||
{shouldShowLoader && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-black/50">
|
||||
<div className="flex items-center text-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<p className="text-sm text-white">{authLoaderMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
|
||||
{/* <a
|
||||
href="https://storyteller.ai/password-reset"
|
||||
className="grow text-sm font-medium !text-brand-primary-400 transition-all duration-150 hover:!text-brand-primary-300"
|
||||
>
|
||||
Forgot your password?
|
||||
</a> */}
|
||||
<a
|
||||
href="/"
|
||||
className="grow text-sm font-medium !text-brand-primary-400 transition-all duration-150 hover:!text-brand-primary-300"
|
||||
>
|
||||
Back
|
||||
</a>
|
||||
<div className="flex justify-end gap-1 text-sm">
|
||||
<p className="opacity-75">Don't have an account?</p>
|
||||
<a
|
||||
href="/signup"
|
||||
className="font-medium text-brand-primary-400 hover:text-brand-primary-300"
|
||||
>
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
text="We're in a closed beta and you'll need a beta key to use this app."
|
||||
title="Unauthorized"
|
||||
open={showNoAccessModal}
|
||||
onClose={handleClose}
|
||||
cancelText="Close"
|
||||
onCancel={handleClose}
|
||||
okText="Get Beta Key"
|
||||
onOk={() => {
|
||||
handleClose();
|
||||
if (window) {
|
||||
window.open("https://storyteller.ai/beta-key/redeem", "_blank");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
if (authentication.status.value !== AUTH_STATUS.LOGGED_OUT) {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
const checkShouldShowLoader = () => {
|
||||
return (
|
||||
authentication.status.value === AUTH_STATUS.LOGGING ||
|
||||
authentication.status.value === AUTH_STATUS.LOGGED_IN
|
||||
);
|
||||
};
|
||||
|
||||
const getAuthLoaderMessage = () => {
|
||||
if (authentication.status.value === AUTH_STATUS.LOGGED_IN) {
|
||||
return "Authenticated, Redirecting...";
|
||||
}
|
||||
if (authentication.status.value === AUTH_STATUS.GET_USER_INFO) {
|
||||
return "Getting User Info...";
|
||||
}
|
||||
return "Getting Session...";
|
||||
};
|
||||
|
||||
// TODO: Replace environment variables from `root.tsx`
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<>
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<GlobalSettingsManager env={ENV} />
|
||||
<LoginScreen />
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
</>,
|
||||
);
|
||||
@@ -43,6 +43,27 @@ export const HistoryStack = ({
|
||||
selectedImageToken,
|
||||
blurredBackgroundUrl,
|
||||
}: HistoryStackProps) => {
|
||||
const handleSelectWithPreload = (image: BaseSelectorImage) => {
|
||||
const preload = new Image();
|
||||
preload.crossOrigin = "anonymous";
|
||||
(
|
||||
preload as HTMLImageElement & { decoding?: "sync" | "async" | "auto" }
|
||||
).decoding = "async";
|
||||
preload.src = image.url;
|
||||
const select = () => onImageSelect(image);
|
||||
// Prefer decode() when available to ensure the image is fully decoded before swap
|
||||
// Fallback to onload/onerror which also ensures layout is ready
|
||||
const decodeFn = (
|
||||
preload as HTMLImageElement & { decode?: () => Promise<void> }
|
||||
).decode;
|
||||
if (typeof decodeFn === "function") {
|
||||
decodeFn.call(preload).then(select).catch(select);
|
||||
} else {
|
||||
preload.onload = select;
|
||||
preload.onerror = select;
|
||||
}
|
||||
};
|
||||
|
||||
useImageEditCompleteEvent(async (event) => {
|
||||
const newBundle: ImageBundle = {
|
||||
images: event.edited_images.map(
|
||||
@@ -174,13 +195,11 @@ export const HistoryStack = ({
|
||||
<Button
|
||||
key={image.mediaToken}
|
||||
className={twMerge(
|
||||
"group relative aspect-square h-full w-full overflow-hidden rounded-lg border-2 bg-transparent p-0 hover:bg-transparent hover:opacity-80",
|
||||
"group relative aspect-square h-full w-full shrink-0 overflow-hidden rounded-lg border-2 bg-transparent p-0 hover:bg-transparent hover:opacity-80",
|
||||
selectedImageToken === image.mediaToken &&
|
||||
"border-primary hover:opacity-100",
|
||||
)}
|
||||
onClick={() => {
|
||||
onImageSelect(image);
|
||||
}}
|
||||
onClick={() => handleSelectWithPreload(image)}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
|
||||
@@ -197,6 +197,7 @@ class Scene {
|
||||
);
|
||||
obj = new THREE.Mesh(geometry, image_material);
|
||||
obj.userData["media_id"] = name;
|
||||
obj.userData["media_file_type"] = MediaFileType.Video;
|
||||
} else {
|
||||
const loader = new THREE.TextureLoader();
|
||||
// NB(bt,2025-08-26): Images loaded throughout the rest of ArtCraft with the <img> tag set
|
||||
@@ -214,6 +215,7 @@ class Scene {
|
||||
});
|
||||
obj = new THREE.Mesh(geometry, image_material);
|
||||
obj.userData["media_id"] = name;
|
||||
obj.userData["media_file_type"] = MediaFileType.Image;
|
||||
}
|
||||
} else if (name == "PointLight") {
|
||||
const light_material = new THREE.MeshBasicMaterial({
|
||||
@@ -553,7 +555,14 @@ class Scene {
|
||||
url.includes(".jpeg") ||
|
||||
url.includes(".mp4")
|
||||
) {
|
||||
return await this.instantiate("Image::" + url);
|
||||
const obj = await this.instantiate("Image::" + url);
|
||||
obj.position.copy(position);
|
||||
if (url.includes(".mp4")) {
|
||||
obj.userData["media_file_type"] = MediaFileType.Video;
|
||||
} else {
|
||||
obj.userData["media_file_type"] = MediaFileType.Image;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +588,15 @@ class Scene {
|
||||
url.includes(".jpeg") ||
|
||||
url.includes(".mp4")
|
||||
) {
|
||||
return await this.instantiate("Image::" + url);
|
||||
const obj = await this.instantiate("Image::" + url);
|
||||
// Preserve original token to enable copy/paste and timeline mapping
|
||||
obj.userData["media_id"] = media_id;
|
||||
if (url.includes(".mp4")) {
|
||||
obj.userData["media_file_type"] = MediaFileType.Video;
|
||||
} else {
|
||||
obj.userData["media_file_type"] = MediaFileType.Image;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
return await this.loadGlbWithPlaceholder(
|
||||
media_id,
|
||||
|
||||
@@ -147,18 +147,34 @@ export class SceneManager implements SceneManagerAPI {
|
||||
media_token: string,
|
||||
name: string,
|
||||
position: THREE.Vector3,
|
||||
): Promise<THREE.Object3D<THREE.Object3DEventMap>> {
|
||||
): Promise<THREE.Object3D<THREE.Object3DEventMap> | undefined> {
|
||||
if (media_token.includes("SKY::")) {
|
||||
const token = media_token.replace("SKY::", "");
|
||||
this.scene.updateSkybox(token);
|
||||
} else if (media_token !== "Parim") {
|
||||
return await this.scene.loadObject(
|
||||
// Support direct image/video URL pastes via the Image::<url> scheme
|
||||
if (media_token.startsWith("Image::")) {
|
||||
const url = media_token.replace("Image::", "");
|
||||
const obj = await this.scene.loadObjectFromUrl(url, position);
|
||||
if (obj) {
|
||||
obj.name = name;
|
||||
obj.userData["name"] = name;
|
||||
}
|
||||
return obj as THREE.Object3D<THREE.Object3DEventMap>;
|
||||
}
|
||||
|
||||
const obj = await this.scene.loadObject(
|
||||
media_token,
|
||||
name,
|
||||
true,
|
||||
position,
|
||||
this.version,
|
||||
);
|
||||
if (obj) {
|
||||
obj.name = name;
|
||||
obj.userData["name"] = name;
|
||||
}
|
||||
return obj as THREE.Object3D<THREE.Object3DEventMap> | undefined;
|
||||
} else {
|
||||
return this.scene.instantiate(name, position);
|
||||
}
|
||||
@@ -288,6 +304,9 @@ export class SceneManager implements SceneManagerAPI {
|
||||
const wasCharacter: boolean = !!this.copiedObject.userData.isCharacter;
|
||||
|
||||
const obj = await this.create(media_id, name, position);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
this.scene.setColor(obj.uuid, color);
|
||||
obj.position.copy(position.add(new THREE.Vector3(0.5, 0.0, 0.5)));
|
||||
obj.rotation.copy(rotation);
|
||||
|
||||
@@ -57,20 +57,25 @@ export const signUp = async ({
|
||||
email,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
signupSource,
|
||||
}: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
passwordConfirmation: string;
|
||||
signupSource?: string;
|
||||
}) => {
|
||||
updateAuthStatus(AUTH_STATUS.LOGGING);
|
||||
|
||||
console.log(">>> Signup with source: ", signupSource);
|
||||
|
||||
const usersApi = new UsersApi();
|
||||
const response = await usersApi.Signup({
|
||||
email,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
username,
|
||||
signupSource,
|
||||
});
|
||||
console.log(response);
|
||||
if (!response.success || !response.data) {
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { GlobalSettingsManager } from "./pages/PageEnigma/GlobalSettingsManager";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { addToast, authentication, signUp } from "~/signals";
|
||||
import { FormEvent, useRef, useState, StrictMode } from "react";
|
||||
import { AUTH_STATUS, ToastTypes } from "~/enums";
|
||||
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
|
||||
import { useNavigate, useSearchParams, BrowserRouter } from "react-router-dom";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import "./styles/normalize.css";
|
||||
import "./styles/tailwind.css";
|
||||
import "./styles/base.css";
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
|
||||
import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
config.autoAddCss = false; /* eslint-disable import/first */
|
||||
|
||||
const regEx =
|
||||
/^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
|
||||
|
||||
// TODO(bt,2025-04-19): Make these configurable
|
||||
const ENV = {
|
||||
GOOGLE_API: "https://studio.storyteller.ai",
|
||||
FUNNEL_API: "https://studio.storyteller.ai",
|
||||
CDN_API: "https://cdn-2.fakeyou.com",
|
||||
GRAVATAR_API: "https://studio.storyteller.ai",
|
||||
DEPLOY_PRIME_URL: "https://studio.storyteller.ai",
|
||||
};
|
||||
|
||||
export default function SignUpScreen() {
|
||||
useSignals();
|
||||
const navigate = useNavigate();
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const [usernameError, setUsernameError] = useState("");
|
||||
const [emailError, setEmailError] = useState("");
|
||||
const [passwordError, setPasswordError] = useState("");
|
||||
const [passwordConfirmationError, setPasswordConfirmationError] =
|
||||
useState("");
|
||||
const [searchParams] = useSearchParams();
|
||||
const { status: authStatus } = authentication;
|
||||
|
||||
const handleOnSubmit = (ev: FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (formRef.current) {
|
||||
const form = new FormData(formRef.current);
|
||||
const email = form.get("email")?.toString();
|
||||
const password = form.get("password")?.toString();
|
||||
const passwordConfirmation = form
|
||||
.get("password-confirmation")
|
||||
?.toString();
|
||||
const username = form.get("username")?.toString();
|
||||
setUsernameError("");
|
||||
if (!username || username.length < 6) {
|
||||
setUsernameError("Username must be at least 6 characters long");
|
||||
}
|
||||
setEmailError("");
|
||||
if (!email || !email.match(regEx)) {
|
||||
setEmailError("Email must be a valid email address");
|
||||
}
|
||||
setPasswordError("");
|
||||
if (!password || password.length < 8) {
|
||||
setPasswordError("Password must be at least 8 characters long");
|
||||
}
|
||||
setPasswordConfirmationError("");
|
||||
if (!passwordError && passwordConfirmation !== password) {
|
||||
setPasswordConfirmationError("Passwords do not match");
|
||||
}
|
||||
|
||||
if (
|
||||
!usernameError &&
|
||||
!passwordError &&
|
||||
!emailError &&
|
||||
!passwordConfirmationError &&
|
||||
signUp
|
||||
) {
|
||||
signUp({
|
||||
username: username!,
|
||||
email: email!,
|
||||
password: password!,
|
||||
passwordConfirmation: passwordConfirmation!,
|
||||
}).then((error) => {
|
||||
console.log("Error occured", error);
|
||||
if (error) {
|
||||
addToast(ToastTypes.ERROR, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}; // end handleOnSubmit
|
||||
|
||||
useSignalEffect(() => {
|
||||
const redirectPath = searchParams.get("redirect");
|
||||
if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
|
||||
navigate(redirectPath ? redirectPath : "/");
|
||||
return;
|
||||
}
|
||||
if (authStatus.value === AUTH_STATUS.NO_ACCESS) {
|
||||
window.open("https://storyteller.ai/", "_self");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed flex h-full w-full flex-col items-center justify-center overflow-auto bg-[#1b1b1f] bg-center bg-repeat">
|
||||
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<img
|
||||
src="/resources/images/artcraft-logo-3.png"
|
||||
alt="ArtCraft Logo"
|
||||
className="h-9 select-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full max-w-xl rounded-xl shadow-lg">
|
||||
<div className="glass glass-no-hover relative mx-auto w-full rounded-none rounded-t-xl p-6 shadow-none">
|
||||
<h1 className="mb-8 text-center text-2xl font-bold">Sign up</h1>
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleOnSubmit}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<Input
|
||||
label="Username"
|
||||
icon={faUser}
|
||||
placeholder="Username"
|
||||
name="username"
|
||||
errorMessage={usernameError}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="email"
|
||||
label="Email"
|
||||
icon={faEnvelope}
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
errorMessage={emailError}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Password"
|
||||
icon={faKey}
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autoComplete="current-password"
|
||||
errorMessage={passwordError}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Password Confirmation"
|
||||
icon={faKey}
|
||||
placeholder="Password Confirmation"
|
||||
name="password-confirmation"
|
||||
errorMessage={passwordConfirmationError}
|
||||
required
|
||||
/>
|
||||
<Button className="mt-4 py-3">Sign up</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
|
||||
<div className="flex w-full justify-start gap-1 text-sm">
|
||||
<p className="opacity-75"></p>
|
||||
<a
|
||||
href="/"
|
||||
className="font-medium text-brand-primary-400 hover:text-brand-primary-300"
|
||||
>
|
||||
Back
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex w-full justify-end gap-1 text-sm">
|
||||
<p className="opacity-75">Already have an account?</p>
|
||||
<a
|
||||
href="/login"
|
||||
className="font-medium text-brand-primary-400 hover:text-brand-primary-300"
|
||||
>
|
||||
Log in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Replace environment variables from `root.tsx`
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<>
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<GlobalSettingsManager env={ENV} />
|
||||
<SignUpScreen />
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
</>,
|
||||
);
|
||||
@@ -92,7 +92,7 @@ body {
|
||||
--st-controls-rgb: 62 62 65;
|
||||
--st-controls-border: #464646;
|
||||
--st-fg: #ffffff;
|
||||
--st-divider: #303030;
|
||||
--st-divider: #424242;
|
||||
--st-color-scheme: dark;
|
||||
--st-gallery-vignette-alpha: 0.3;
|
||||
--st-photo-tint-rgb: 0 0 0;
|
||||
@@ -110,7 +110,7 @@ body {
|
||||
--st-controls-rgb: 17 17 17;
|
||||
--st-controls-border: #242424;
|
||||
--st-fg: #ffffff;
|
||||
--st-divider: #242424;
|
||||
--st-divider: #303030;
|
||||
--st-color-scheme: dark;
|
||||
--st-photo-tint-rgb: 0 0 0;
|
||||
--st-photo-tint-alpha: 0;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/bin/bash
|
||||
# NB: This file is executed by Netlify to build the website
|
||||
|
||||
# Verbose printing, exit on failure.
|
||||
set -euxo pipefail
|
||||
|
||||
# Echo env vars
|
||||
env
|
||||
|
||||
# Add the GIT SHA to the build
|
||||
# This must be done before everything else, or it will get cached with the build.
|
||||
# (This might be making the builds less performant?)
|
||||
# COMMIT_REF is defined by Netlify to be the commit SHA
|
||||
# We want a short 8 character reference.
|
||||
echo "Labelling build with short SHA..."
|
||||
SHORT_SHA=$(echo "${COMMIT_REF}" | cut -c1-8)
|
||||
|
||||
echo "Baking current git SHA to code";
|
||||
|
||||
find . -type f -exec sed -i "s/%CURRENT_STORYTELLER_GIT_VERSION%/${SHORT_SHA}/g" {} +
|
||||
|
||||
####
|
||||
# Expressions hack
|
||||
####
|
||||
|
||||
echo "Baking expressions feature flag ENV to code";
|
||||
|
||||
echo "Value of EXPRESSIONS env var is: ${EXPRESSIONS}"
|
||||
|
||||
if [ "$EXPRESSIONS" = "TRUE" ] || [ "$EXPRESSIONS" = "true" ] ; then
|
||||
expressions_value="true"
|
||||
else
|
||||
expressions_value="false"
|
||||
fi
|
||||
|
||||
echo "Value of expressions is: ${expressions_value}"
|
||||
|
||||
find . -type f -exec sed -i "s/%EXPRESSIONS_VALUE%/${expressions_value}/g" {} +
|
||||
|
||||
echo "Baking URL/API ENV configs to code";
|
||||
|
||||
# NB: We can't use slashes as the sed escape character:
|
||||
# https://stackoverflow.com/a/27787551
|
||||
d=$'\03'
|
||||
find . -type f -exec sed -i "s${d}%BUILD_BASE_API%${d}${BASE_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%BUILD_GOOGLE_API%${d}${GOOGLE_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%BUILD_FUNNEL_API%${d}${FUNNEL_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%BUILD_CDN_API%${d}${CDN_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%BUILD_UPLOAD_VIDEO%${d}${UPLOAD_VIDEO_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%BUILD_MEDIA_VIDEO%${d}${MEDIA_VIDEO_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%BUILD_GRAVATAR_VIDEO%${d}${GRAVATAR_API}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%DEPLOY_PRIME_URL%${d}${DEPLOY_PRIME_URL}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%DEPLOY_CONTEXT%${d}${DEPLOY_CONTEXT}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%CONTEXT%${d}${CONTEXT}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%UPLOAD_API_VIDEO%${d}${UPLOAD_API_VIDEO}${d}g" {} +
|
||||
|
||||
# Replace Posthog env variables in the code
|
||||
find . -type f -exec sed -i "s${d}%REACT_APP_PUBLIC_POSTHOG_KEY%${d}${REACT_APP_PUBLIC_POSTHOG_KEY}${d}g" {} +
|
||||
find . -type f -exec sed -i "s${d}%REACT_APP_PUBLIC_POSTHOG_UI%${d}${REACT_APP_PUBLIC_POSTHOG_UI}${d}g" {} +
|
||||
|
||||
# TODO: Run tests when we add them.
|
||||
|
||||
# Ensure the environment variables are exported
|
||||
export REACT_APP_PUBLIC_POSTHOG_KEY
|
||||
export REACT_APP_PUBLIC_POSTHOG_UI
|
||||
export CONTEXT
|
||||
# Run build.x
|
||||
npm run build
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"dev": "echo 'editor3d' && vite dev",
|
||||
"staging-build": "vite build",
|
||||
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint playground/* .",
|
||||
"start": "netlify serve",
|
||||
"typecheck": "tsc",
|
||||
"update:icons": "npm update @awesome.me/kit-fde2be5eb0",
|
||||
"chromatic": "npx chromatic --project-token=chpt_0945f90d5d3626c",
|
||||
@@ -34,9 +33,6 @@
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@mediapipe/tasks-vision": "^0.10.20",
|
||||
"@netlify/edge-functions": "^2.3.1",
|
||||
"@netlify/remix-edge-adapter": "^3.2.0",
|
||||
"@netlify/remix-runtime": "^2.2.0",
|
||||
"@preact/signals-react": "^2.0.1",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@remix-run/deno": "^2.8.1",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
# Kill any process running on port 5741, which will block startup
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
# Add the GIT SHA to the build
|
||||
# This must be done before everything else, or it will get cached with the build.
|
||||
# (This might be making the builds less performant?)
|
||||
# COMMIT_REF is defined by Netlify to be the commit SHA
|
||||
# We want a short 8 character reference.
|
||||
echo "Labelling build with short SHA..."
|
||||
SHORT_SHA=$(echo "${COMMIT_REF}" | cut -c1-8)
|
||||
|
||||
echo "Baking current git SHA to code";
|
||||
find . -type f -exec sed -i "s/%CURRENT_STORYTELLER_GIT_VERSION%/${SHORT_SHA}/g" {} +
|
||||
|
||||
echo "Install packages"
|
||||
npm install
|
||||
|
||||
echo "Build Editor 3D"
|
||||
nx build editor3d --verbose
|
||||
|
||||
echo "Change to project dir"
|
||||
pushd apps/editor3d/
|
||||
|
||||
echo "Copy files to putput directory"
|
||||
cp _headers dist/
|
||||
#cp _redirects dist/
|
||||
#cp "404.html" dist/
|
||||
|
||||
echo "List final files in build"
|
||||
find dist/
|
||||
@@ -1,6 +1,5 @@
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { netlifyPlugin } from "@netlify/remix-edge-adapter/plugin";
|
||||
import path from 'path'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
@@ -15,13 +14,10 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'app/index.html'),
|
||||
login: resolve(__dirname, 'app/login.html'),
|
||||
signup: resolve(__dirname, 'app/signup.html'),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
netlifyPlugin(),
|
||||
tsconfigPaths(),
|
||||
],
|
||||
server: {
|
||||
|
||||
@@ -2,6 +2,14 @@ import { ApiManager, ApiResponse } from "./ApiManager.js";
|
||||
import { UserInfo } from "./models/Users.js";
|
||||
import { FetchProxy as fetch } from "@storyteller/tauri-utils";
|
||||
|
||||
interface SignupRequest {
|
||||
username: string;
|
||||
email_address: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
signup_source?: string;
|
||||
};
|
||||
|
||||
export class UsersApi extends ApiManager {
|
||||
private async authFetch<B, T>(
|
||||
endpoint: string,
|
||||
@@ -149,28 +157,28 @@ export class UsersApi extends ApiManager {
|
||||
email,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
signupSource,
|
||||
}: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
passwordConfirmation: string;
|
||||
signupSource?: string;
|
||||
}): Promise<ApiResponse<{ signedSession?: string }>> {
|
||||
const endpoint = `${this.getApiSchemeAndHost()}/v1/create_account`;
|
||||
const body = {
|
||||
const body : SignupRequest = {
|
||||
email_address: email,
|
||||
password,
|
||||
password_confirmation: passwordConfirmation,
|
||||
username,
|
||||
};
|
||||
if (!!signupSource) {
|
||||
body.signup_source = signupSource;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.authFetch<
|
||||
{
|
||||
username: string;
|
||||
email_address: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
},
|
||||
SignupRequest,
|
||||
{
|
||||
success: boolean;
|
||||
signed_session?: string;
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
import { ArtCraftSignUp } from "./artcraft-signup";
|
||||
import { UsersApi } from "@storyteller/api";
|
||||
import { useLoginModalStore } from "./useLoginModalStore";
|
||||
|
||||
const SIGNUP_SOURCE_ARTCRAFT = "artcraft";
|
||||
|
||||
interface LoginModalProps {
|
||||
onClose?: () => void;
|
||||
videoSrc2D?: string;
|
||||
@@ -249,6 +252,7 @@ export function LoginModal({
|
||||
email,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
signupSource: SIGNUP_SOURCE_ARTCRAFT,
|
||||
});
|
||||
console.log(signupResponse);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Model,
|
||||
VIDEO_MODELS,
|
||||
IMAGE_MODELS,
|
||||
ALL_MODELS_BY_ID,
|
||||
IMAGE_MODELS_BY_ID,
|
||||
} from "@storyteller/model-list";
|
||||
import { ModelTag } from "@storyteller/model-list";
|
||||
|
||||
@@ -14,7 +14,7 @@ export type ModelList = Omit<PopoverItem, "selected">[];
|
||||
|
||||
const withIcon = (creatorIcon: any, fallback: any) => creatorIcon || fallback;
|
||||
|
||||
const buildItems2 = (
|
||||
const buildItems = (
|
||||
models: Model[],
|
||||
fallbackIcon: any
|
||||
) =>
|
||||
@@ -37,35 +37,66 @@ const buildItems2 = (
|
||||
* access to the object directly.
|
||||
*/
|
||||
|
||||
export const CANVAS_2D_PAGE_MODEL_LIST : ModelList = buildItems2(
|
||||
IMAGE_MODELS.filter((m) => m.tags?.includes(ModelTag.InstructiveEdit)),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
export const TEXT_TO_IMAGE_PAGE_MODEL_LIST : ModelList =
|
||||
buildItems(
|
||||
(function() : Model[] {
|
||||
const set : Set<Model> = new Set();
|
||||
set.add(IMAGE_MODELS_BY_ID.get("flux_pro_1_1")!); // Don't put Midjourney first!
|
||||
IMAGE_MODELS
|
||||
.filter((model) => !model.usesInpaintingMask) // We can't use masked inpainting models here
|
||||
.filter((model) => model.id !== "flux_pro_kontext_max") // NB: Flux Pro Kontext Max requires an input image
|
||||
.forEach((m) => set.add(m));
|
||||
return Array.from(set);
|
||||
})(),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const STAGE_3D_PAGE_MODEL_LIST : ModelList = buildItems2(
|
||||
IMAGE_MODELS.filter((m) => m.tags?.includes(ModelTag.InstructiveEdit)),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
export const CANVAS_2D_PAGE_MODEL_LIST : ModelList =
|
||||
buildItems(
|
||||
(function() : Model[] {
|
||||
const set : Set<Model> = new Set();
|
||||
set.add(IMAGE_MODELS_BY_ID.get("gpt_image_1")!); // Place gpt_image_1 first.
|
||||
IMAGE_MODELS
|
||||
.filter((m) => m.tags?.includes(ModelTag.InstructiveEdit))
|
||||
.forEach((m) => set.add(m));
|
||||
return Array.from(set);
|
||||
})(),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const IMAGE_EDITOR_PAGE_MODEL_LIST : ModelList = buildItems2(
|
||||
//[
|
||||
// ALL_MODELS_BY_ID.get("flux_pro_inpaint")!,
|
||||
// ALL_MODELS_BY_ID.get("flux_dev_juggernaut_inpaint")!,
|
||||
// ALL_MODELS_BY_ID.get("flux_pro_kontext_max")!,
|
||||
//],
|
||||
IMAGE_MODELS.filter((m) => m.canEditImages),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
export const STAGE_3D_PAGE_MODEL_LIST : ModelList =
|
||||
buildItems(
|
||||
(function() : Model[] {
|
||||
const set : Set<Model> = new Set();
|
||||
set.add(IMAGE_MODELS_BY_ID.get("gpt_image_1")!); // Place gpt_image_1 first.
|
||||
IMAGE_MODELS
|
||||
.filter((m) => m.tags?.includes(ModelTag.InstructiveEdit))
|
||||
.forEach((m) => set.add(m));
|
||||
return Array.from(set);
|
||||
})(),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const IMAGE_TO_VIDEO_PAGE_MODEL_LIST : ModelList = buildItems2(
|
||||
VIDEO_MODELS,
|
||||
<FontAwesomeIcon icon={faFilm} className="h-4 w-4" />
|
||||
);
|
||||
export const IMAGE_EDITOR_PAGE_MODEL_LIST : ModelList =
|
||||
buildItems(
|
||||
//[
|
||||
// ALL_MODELS_BY_ID.get("flux_pro_inpaint")!,
|
||||
// ALL_MODELS_BY_ID.get("flux_dev_juggernaut_inpaint")!,
|
||||
// ALL_MODELS_BY_ID.get("flux_pro_kontext_max")!,
|
||||
//],
|
||||
(function() : Model[] {
|
||||
const set : Set<Model> = new Set();
|
||||
//set.add(IMAGE_MODELS_BY_ID.get("gpt_image_1")!); // Place gpt_image_1 first.
|
||||
IMAGE_MODELS
|
||||
.filter((m) => m.canEditImages)
|
||||
.forEach((m) => set.add(m));
|
||||
return Array.from(set);
|
||||
})(),
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const TEXT_TO_IMAGE_PAGE_MODEL_LIST : ModelList = buildItems2(
|
||||
IMAGE_MODELS
|
||||
.filter((model) => !model.usesInpaintingMask) // We can't use masked inpainting models here
|
||||
.filter((model) => model.id !== "flux_pro_kontext_max") // NB: Flux Pro Kontext Max requires an input image
|
||||
,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
export const IMAGE_TO_VIDEO_PAGE_MODEL_LIST : ModelList =
|
||||
buildItems(
|
||||
VIDEO_MODELS,
|
||||
<FontAwesomeIcon icon={faFilm} className="h-4 w-4" />
|
||||
);
|
||||
@@ -396,11 +396,11 @@ export const ImagePromptRow = ({
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"grow grid py-2 px-3 grid-cols-1",
|
||||
isVideo && showEndFrameSection && "grid-cols-2 gap-5"
|
||||
"grow grid grid-cols-1",
|
||||
isVideo && showEndFrameSection && "grid-cols-2"
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 py-2 px-3">
|
||||
<div className="flex flex-col grow gap-1">
|
||||
<div className="flex items-center gap-2 opacity-90 text-base-fg">
|
||||
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
|
||||
@@ -550,18 +550,23 @@ export const ImagePromptRow = ({
|
||||
</div>
|
||||
</div>
|
||||
{isVideo && showEndFrameSection && (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col grow gap-1">
|
||||
<div className="flex items-center gap-2 opacity-90 text-base-fg">
|
||||
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
|
||||
<span className="text-sm font-medium flex items-center gap-1.5">
|
||||
Ending Frame{" "}
|
||||
<span className="text-base-fg/60 text-xs">(optional)</span>
|
||||
<div className="flex gap-3 pe-3">
|
||||
<div className="flex grow gap-1">
|
||||
<div className="w-[1px] h-full bg-white/10" />
|
||||
<div className="flex flex-col grow gap-1 p-2">
|
||||
<div className="flex items-center gap-2 opacity-90 text-base-fg">
|
||||
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
|
||||
<span className="text-sm font-medium flex items-center gap-1.5">
|
||||
Ending Frame{" "}
|
||||
<span className="text-base-fg/60 text-xs">
|
||||
(optional)
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[13px] text-base-fg/60">
|
||||
How the animation ends
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[13px] text-base-fg/60">
|
||||
How the animation ends
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
{endFrameImage ? (
|
||||
@@ -656,6 +661,7 @@ export const ImagePromptRow = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-2 flex items-center">
|
||||
<div className="w-[1px] h-full bg-white/10" />
|
||||
<div className="flex items-center gap-2 w-[1px] h-full bg-base-fg/20 dark:bg-base-fg/10 rounded-lg" />
|
||||
<div className="p-2">
|
||||
<Button
|
||||
|
||||
@@ -311,7 +311,7 @@ export const PromptBoxEdit = ({
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-9 bg-[#5F5F68]/60 px-3 text-white hover:bg-[#5F5F68]/90"
|
||||
className="h-9 bg-ui-controls/60 px-3 text-base-fg hover:bg-ui-controls/90"
|
||||
onClick={onFitPressed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFrame} className="h-4 w-4" />
|
||||
|
||||
@@ -148,3 +148,11 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
usesInpaintingMask: true,
|
||||
}),
|
||||
];
|
||||
|
||||
export const IMAGE_MODELS_BY_ID : Map<string, ImageModel> = new Map(
|
||||
IMAGE_MODELS.map((model) => [model.id, model])
|
||||
);
|
||||
|
||||
if (IMAGE_MODELS_BY_ID.size !== IMAGE_MODELS.length) {
|
||||
throw new Error("All image models must have unique IDs");
|
||||
}
|
||||
|
||||
Generated
+456
-25
File diff suppressed because it is too large
Load Diff
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
git branch -D artcraft-release
|
||||
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
git checkout -b artcraft-release
|
||||
git push origin artcraft-release
|
||||
|
||||
git checkout main
|
||||
|
||||
Reference in New Issue
Block a user