mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f587e19d25 | ||
|
|
cea29135ef | ||
|
|
8f2ebf0e69 | ||
|
|
ddfe83c514 | ||
|
|
a05cd8414a |
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO analytics_app_sessions\nSET\n session_token = ?,\n app_namespace = ?,\n user_token = ?,\n app_version = ?,\n os_platform = ?,\n os_version = ?,\n session_duration_seconds = ?,\n ip_address = ?,\n measurement_count = measurement_count + 1,\n first_active_at = NOW(),\n last_active_at = NOW()\nON DUPLICATE KEY UPDATE\n app_namespace = ?,\n user_token = ?,\n app_version = ?,\n os_platform = ?,\n os_version = ?,\n session_duration_seconds = ?,\n ip_address = ?,\n measurement_count = measurement_count + 1,\n last_active_at = NOW()\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 15
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ff39576a5ec6a23c67f6f61e7eba8392e70b05c42b540bea260a9a4cdc478a5b"
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Track user activity over time
|
||||
-- User clients ping the endpoint that writes this data.
|
||||
CREATE TABLE analytics_app_active_users (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
drop table analytics_app_sessions;
|
||||
@@ -0,0 +1,65 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Track user session durations
|
||||
-- User clients ping the endpoint that writes this data.
|
||||
CREATE TABLE analytics_app_sessions (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Generated client-side
|
||||
-- Wide enough for a UUID, but typically one of our internal tokens.
|
||||
session_token VARCHAR(36) NOT NULL,
|
||||
|
||||
-- If we host multiple sites with distinct categories, this will enable us to
|
||||
-- segregate analytics.
|
||||
app_namespace VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Who sent the event.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The "user agent" or app version string of the client.
|
||||
app_version VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform for the client.
|
||||
os_platform VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform version for the client.
|
||||
os_version VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- How long the current user's session has been open
|
||||
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The user's last known IP address.
|
||||
ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- Incrementing count of events.
|
||||
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
|
||||
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
|
||||
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== RECORD TIMESTAMPS ==========
|
||||
|
||||
-- Written at create only.
|
||||
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Updated on every ping.
|
||||
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (session_token),
|
||||
INDEX idx_app_namespace (app_namespace),
|
||||
INDEX idx_user_token (user_token),
|
||||
INDEX idx_os_platform (os_platform),
|
||||
INDEX idx_os_version (os_version),
|
||||
INDEX idx_app_version (app_version),
|
||||
INDEX idx_last_active_at (last_active_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,65 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Track user session durations
|
||||
-- User clients ping the endpoint that writes this data.
|
||||
CREATE TABLE analytics_app_sessions (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Generated client-side
|
||||
-- Wide enough for a UUID, but typically one of our internal tokens.
|
||||
session_token VARCHAR(36) NOT NULL,
|
||||
|
||||
-- If we host multiple sites with distinct categories, this will enable us to
|
||||
-- segregate analytics.
|
||||
app_namespace VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Who sent the event.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The "user agent" or app version string of the client.
|
||||
app_version VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform for the client.
|
||||
os_platform VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform version for the client.
|
||||
os_version VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- How long the current user's session has been open
|
||||
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The user's last known IP address.
|
||||
ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- Incrementing count of events.
|
||||
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
|
||||
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
|
||||
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== RECORD TIMESTAMPS ==========
|
||||
|
||||
-- Written at create only.
|
||||
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Updated on every ping.
|
||||
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (session_token),
|
||||
INDEX idx_app_namespace (app_namespace),
|
||||
INDEX idx_user_token (user_token),
|
||||
INDEX idx_os_platform (os_platform),
|
||||
INDEX idx_os_version (os_version),
|
||||
INDEX idx_app_version (app_version),
|
||||
INDEX idx_last_active_at (last_active_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,10 +1,15 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::app_session::AppSessionToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const LOG_ACTIVE_USER_PATH: &str = "/v1/analytics/active_user";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct LogAppActiveUserRequest {
|
||||
/// Clientside-generated session token.
|
||||
/// If present, it'll be validated against an expected format.
|
||||
pub maybe_app_session_token: Option<AppSessionToken>,
|
||||
|
||||
/// An override for the application platform/OS (windows, mac, linux).
|
||||
pub maybe_os_platform: Option<String>,
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ pub async fn log_active_user(
|
||||
|
||||
let mut query = HashMap::new();
|
||||
|
||||
if let Some(token) = request.maybe_app_session_token {
|
||||
query.insert("maybe_app_session_token".to_string(), token.to_string());
|
||||
}
|
||||
|
||||
if let Some(os_platform) = request.maybe_os_platform {
|
||||
query.insert("maybe_os_platform".to_string(), os_platform);
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_veo_3_fast_image_to_video::{GenerateVeo3FastImageToVideoRequest, GenerateVeo3FastImageToVideoResponse, GENERATE_VEO_3_FAST_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_veo_3_fast_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateVeo3FastImageToVideoRequest,
|
||||
) -> Result<GenerateVeo3FastImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_VEO_3_FAST_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_veo_3_image_to_video::{GenerateVeo3ImageToVideoRequest, GenerateVeo3ImageToVideoResponse, GENERATE_VEO_3_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_veo_3_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateVeo3ImageToVideoRequest,
|
||||
) -> Result<GenerateVeo3ImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_VEO_3_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -2,4 +2,6 @@ pub mod generate_kling_16_pro_image_to_video;
|
||||
pub mod generate_kling_21_master_image_to_video;
|
||||
pub mod generate_kling_21_pro_image_to_video;
|
||||
pub mod generate_seedance_1_0_lite_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
pub mod generate_veo_3_fast_image_to_video;
|
||||
pub mod generate_veo_3_image_to_video;
|
||||
|
||||
@@ -25,7 +25,7 @@ pub async fn basic_query_string_post_request<Res: DeserializeOwned>(
|
||||
// TODO: Please stop using this URL Builder library. It's not very safe or intuitive.
|
||||
let url = get_route(api_host, route_path, query_params);
|
||||
|
||||
info!("Requesting {:?}", &url);
|
||||
debug!("Requesting {:?}", &url);
|
||||
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
|
||||
+6
@@ -41,6 +41,12 @@ pub enum VideoModel {
|
||||
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
|
||||
#[serde(rename = "veo_3")]
|
||||
Veo3,
|
||||
|
||||
#[serde(rename = "veo_3_fast")]
|
||||
Veo3Fast,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
+59
@@ -13,6 +13,8 @@ use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProAspectRatio, GenerateKling21ProImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoRequest;
|
||||
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2AspectRatio, GenerateVeo2ImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_veo_3_fast_image_to_video::GenerateVeo3FastImageToVideoRequest;
|
||||
use artcraft_api_defs::generate::video::generate_veo_3_image_to_video::{GenerateVeo3AspectRatio, GenerateVeo3ImageToVideoRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
@@ -25,6 +27,8 @@ use storyteller_client::endpoints::generate::video::generate_kling_21_master_ima
|
||||
use storyteller_client::endpoints::generate::video::generate_kling_21_pro_image_to_video::generate_kling_21_pro_image_to_video;
|
||||
use storyteller_client::endpoints::generate::video::generate_seedance_1_0_lite_image_to_video::generate_seedance_1_0_lite_image_to_video;
|
||||
use storyteller_client::endpoints::generate::video::generate_veo_2_image_to_video::generate_veo_2_image_to_video;
|
||||
use storyteller_client::endpoints::generate::video::generate_veo_3_fast_image_to_video::generate_veo_3_fast_image_to_video;
|
||||
use storyteller_client::endpoints::generate::video::generate_veo_3_image_to_video::generate_veo_3_image_to_video;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::AppHandle;
|
||||
|
||||
@@ -202,6 +206,61 @@ pub async fn handle_video_artcraft(
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Veo3) => {
|
||||
info!("enqueue Veo 3 with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Veo3);
|
||||
let request = GenerateVeo3ImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateVeo3AspectRatio::WideSixteenNine),
|
||||
prompt: request.prompt,
|
||||
duration: None,
|
||||
resolution: None,
|
||||
generate_audio: None,
|
||||
};
|
||||
let result = generate_veo_3_image_to_video(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Veo 3 video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Veo 3 video generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Veo3Fast) => {
|
||||
info!("enqueue Veo 3 Fast with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Veo3Fast);
|
||||
let request = GenerateVeo3FastImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
prompt: request.prompt,
|
||||
duration: None,
|
||||
resolution: None,
|
||||
generate_audio: None,
|
||||
};
|
||||
let result = generate_veo_3_fast_image_to_video(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Veo 3 video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Veo 3 video generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued video generation");
|
||||
|
||||
@@ -71,6 +71,8 @@ impl TaskEnqueueSuccess{
|
||||
Some(GenerationModel::Kling21Master) => Some(TaskModelType::Kling21Master),
|
||||
Some(GenerationModel::Seedance10Lite) => Some(TaskModelType::Seedance10Lite),
|
||||
Some(GenerationModel::Veo2) => Some(TaskModelType::Veo2),
|
||||
Some(GenerationModel::Veo3) => Some(TaskModelType::Veo3),
|
||||
Some(GenerationModel::Veo3Fast) => Some(TaskModelType::Veo3Fast),
|
||||
Some(GenerationModel::Hunyuan3d2_0) => Some(TaskModelType::Hunyuan3d2_0),
|
||||
Some(GenerationModel::Hunyuan3d2_1) => Some(TaskModelType::Hunyuan3d2_1),
|
||||
Some(GenerationModel::Midjourney) => Some(TaskModelType::Midjourney), // NB: This is a generic Midjourney model, version unknown.
|
||||
|
||||
@@ -59,6 +59,10 @@ pub enum GenerationModel {
|
||||
Seedance10Lite,
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
#[serde(rename = "veo_3")]
|
||||
Veo3,
|
||||
#[serde(rename = "veo_3_fast")]
|
||||
Veo3Fast,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::GenerationServiceProvider;
|
||||
use crate::core::events::generation_events::generation_complete_event::GenerationCompleteEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_url_to_temp_dir::download_url_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
@@ -15,7 +16,6 @@ use storyteller_client::recipes::upload_media_file_from_file::upload_media_file_
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
|
||||
pub async fn fal_task_polling_thread(
|
||||
app_handle: AppHandle,
|
||||
|
||||
+10
-2
@@ -14,7 +14,7 @@ use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use log::{debug, error, info};
|
||||
use os_info::Info;
|
||||
use reqwest::Url;
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs, Task};
|
||||
@@ -27,6 +27,7 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::endpoints::jobs::list_session_jobs::{list_session_jobs, States};
|
||||
use storyteller_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::app_session::AppSessionToken;
|
||||
|
||||
// TODO: Configure this with the build and increment.
|
||||
const CLIENT_NAME : &str = "artcraft";
|
||||
@@ -40,12 +41,17 @@ pub async fn storyteller_activity_thread(
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
let startup = Instant::now();
|
||||
let app_session_token = AppSessionToken::generate();
|
||||
|
||||
info!("Session started at {:?} with token: {:?}", startup, app_session_token);
|
||||
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_env_configs,
|
||||
&storyteller_creds_manager,
|
||||
startup,
|
||||
&artcraft_platform_info,
|
||||
&app_session_token,
|
||||
).await;
|
||||
if let Err(err) = res {
|
||||
error!("An error occurred: {:?}", err);
|
||||
@@ -60,6 +66,7 @@ async fn polling_loop(
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
startup: Instant,
|
||||
artcraft_platform_info: &ArtcraftPlatformInfo,
|
||||
app_session_token: &AppSessionToken,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
let creds = storyteller_creds_manager.get_credentials()?;
|
||||
@@ -81,6 +88,7 @@ async fn polling_loop(
|
||||
let time_since_startup = Instant::now().duration_since(startup);
|
||||
|
||||
let request = LogAppActiveUserRequest {
|
||||
maybe_app_session_token: Some(app_session_token.clone()),
|
||||
maybe_app_name: Some(CLIENT_NAME.to_string()),
|
||||
maybe_app_version: Some(artcraft_platform_info.artcraft_version.clone()),
|
||||
maybe_os_platform: Some(artcraft_platform_info.os_platform.as_str().to_owned()),
|
||||
@@ -88,7 +96,7 @@ async fn polling_loop(
|
||||
maybe_session_duration_seconds: Some(time_since_startup.as_secs()),
|
||||
};
|
||||
|
||||
info!("Logging active user with storyteller.");
|
||||
debug!("Logging active user with storyteller.");
|
||||
|
||||
let result = log_active_user(
|
||||
&app_env_configs.storyteller_host,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod upsert_analytics_app_active_user;
|
||||
pub mod upsert_analytics_app_active_user;
|
||||
pub mod upsert_analytics_app_session;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
use crate::errors::mysql_error::MysqlError;
|
||||
use crate::errors::subtypes::upsert_error::UpsertError;
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use sqlx::mysql::MySqlArguments;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::query::Query;
|
||||
use sqlx::MySql;
|
||||
use tokens::tokens::app_session::AppSessionToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct UpsertAnalyticsAppSession<'a> {
|
||||
pub app_session_token: &'a AppSessionToken,
|
||||
// TODO: Swap PaymentsNamespace (artcraft,fakeyou,etc.) for an AnalyticsNamespace enum.
|
||||
pub namespace: PaymentsNamespace,
|
||||
pub user_token: &'a UserToken,
|
||||
pub ip_address: &'a str,
|
||||
pub app_version: Option<&'a str>,
|
||||
pub os_platform: Option<&'a str>,
|
||||
pub os_version: Option<&'a str>,
|
||||
pub session_duration_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
impl <'a> UpsertAnalyticsAppSession<'a> {
|
||||
fn query(&self) -> Query<MySql, MySqlArguments> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO analytics_app_sessions
|
||||
SET
|
||||
session_token = ?,
|
||||
app_namespace = ?,
|
||||
user_token = ?,
|
||||
app_version = ?,
|
||||
os_platform = ?,
|
||||
os_version = ?,
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
first_active_at = NOW(),
|
||||
last_active_at = NOW()
|
||||
ON DUPLICATE KEY UPDATE
|
||||
app_namespace = ?,
|
||||
user_token = ?,
|
||||
app_version = ?,
|
||||
os_platform = ?,
|
||||
os_version = ?,
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
last_active_at = NOW()
|
||||
"#,
|
||||
// Insert case
|
||||
self.app_session_token.as_str(),
|
||||
self.namespace.to_str(),
|
||||
self.user_token.as_str(),
|
||||
self.app_version,
|
||||
self.os_platform,
|
||||
self.os_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
// Update case
|
||||
self.namespace.to_str(),
|
||||
self.user_token.as_str(),
|
||||
self.app_version,
|
||||
self.os_platform,
|
||||
self.os_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn upsert_with_connection(
|
||||
&self,
|
||||
mysql_connection: &mut PoolConnection<MySql>
|
||||
) -> Result<(), MysqlError<UpsertError>> {
|
||||
let _query_result = self.query()
|
||||
.execute(&mut **mysql_connection)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,10 @@ pub enum TaskModelType {
|
||||
Seedance10Lite,
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
#[serde(rename = "veo_3")]
|
||||
Veo3,
|
||||
#[serde(rename = "veo_3_fast")]
|
||||
Veo3Fast,
|
||||
|
||||
// 3D Object generation models
|
||||
#[serde(rename = "hunyuan_3d_2.0")]
|
||||
@@ -83,6 +87,8 @@ impl TaskModelType {
|
||||
Self::Kling21Master => "kling_2.1_master",
|
||||
Self::Seedance10Lite => "seedance_1.0_lite",
|
||||
Self::Veo2 => "veo_2",
|
||||
Self::Veo3 => "veo_3",
|
||||
Self::Veo3Fast => "veo_3_fast",
|
||||
// 3D Object generation models
|
||||
Self::Hunyuan3d2_0 => "hunyuan_3d_2.0",
|
||||
Self::Hunyuan3d2_1 => "hunyuan_3d_2.1",
|
||||
@@ -109,6 +115,8 @@ impl TaskModelType {
|
||||
"kling_2.1_master" => Ok(Self::Kling21Master),
|
||||
"seedance_1.0_lite" => Ok(Self::Seedance10Lite),
|
||||
"veo_2" => Ok(Self::Veo2),
|
||||
"veo_3" => Ok(Self::Veo3),
|
||||
"veo_3_fast" => Ok(Self::Veo3Fast),
|
||||
// 3D Object generation models
|
||||
"hunyuan_3d_2.0" => Ok(Self::Hunyuan3d2_0),
|
||||
"hunyuan_3d_2.1" => Ok(Self::Hunyuan3d2_1),
|
||||
@@ -138,6 +146,8 @@ impl TaskModelType {
|
||||
Self::Kling21Master,
|
||||
Self::Seedance10Lite,
|
||||
Self::Veo2,
|
||||
Self::Veo3,
|
||||
Self::Veo3Fast,
|
||||
// 3D Object generation models
|
||||
Self::Hunyuan3d2_0,
|
||||
Self::Hunyuan3d2_1,
|
||||
@@ -173,6 +183,8 @@ mod tests {
|
||||
assert_serialization(TaskModelType::Kling21Master, "kling_2.1_master");
|
||||
assert_serialization(TaskModelType::Seedance10Lite, "seedance_1.0_lite");
|
||||
assert_serialization(TaskModelType::Veo2, "veo_2");
|
||||
assert_serialization(TaskModelType::Veo3, "veo_3");
|
||||
assert_serialization(TaskModelType::Veo3Fast, "veo_3_fast");
|
||||
// 3D Object generation models
|
||||
assert_serialization(TaskModelType::Hunyuan3d2_0, "hunyuan_3d_2.0");
|
||||
assert_serialization(TaskModelType::Hunyuan3d2_1, "hunyuan_3d_2.1");
|
||||
@@ -198,6 +210,8 @@ mod tests {
|
||||
assert_eq!(TaskModelType::Kling21Master.to_str(), "kling_2.1_master");
|
||||
assert_eq!(TaskModelType::Seedance10Lite.to_str(), "seedance_1.0_lite");
|
||||
assert_eq!(TaskModelType::Veo2.to_str(), "veo_2");
|
||||
assert_eq!(TaskModelType::Veo3.to_str(), "veo_3");
|
||||
assert_eq!(TaskModelType::Veo3Fast.to_str(), "veo_3_fast");
|
||||
// 3D Object generation models
|
||||
assert_eq!(TaskModelType::Hunyuan3d2_0.to_str(), "hunyuan_3d_2.0");
|
||||
assert_eq!(TaskModelType::Hunyuan3d2_1.to_str(), "hunyuan_3d_2.1");
|
||||
@@ -223,6 +237,8 @@ mod tests {
|
||||
assert_eq!(TaskModelType::from_str("kling_2.1_master").unwrap(), TaskModelType::Kling21Master);
|
||||
assert_eq!(TaskModelType::from_str("seedance_1.0_lite").unwrap(), TaskModelType::Seedance10Lite);
|
||||
assert_eq!(TaskModelType::from_str("veo_2").unwrap(), TaskModelType::Veo2);
|
||||
assert_eq!(TaskModelType::from_str("veo_3").unwrap(), TaskModelType::Veo3);
|
||||
assert_eq!(TaskModelType::from_str("veo_3_fast").unwrap(), TaskModelType::Veo3Fast);
|
||||
// 3D Object generation models
|
||||
assert_eq!(TaskModelType::from_str("hunyuan_3d_2.0").unwrap(), TaskModelType::Hunyuan3d2_0);
|
||||
assert_eq!(TaskModelType::from_str("hunyuan_3d_2.1").unwrap(), TaskModelType::Hunyuan3d2_1);
|
||||
@@ -231,7 +247,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TaskModelType::all_variants();
|
||||
assert_eq!(variants.len(), 18);
|
||||
assert_eq!(variants.len(), 20);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Schnell));
|
||||
@@ -250,6 +266,8 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling21Master));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance10Lite));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Veo2));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Veo3));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Veo3Fast));
|
||||
// 3D Object generation models
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Hunyuan3d2_0));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Hunyuan3d2_1));
|
||||
|
||||
@@ -23,6 +23,7 @@ use strum::EnumIter;
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
pub(crate) enum TokenPrefix {
|
||||
AnonymousVisitorTracking, // AVTs are not stored as primary keys in any table, but an index in many tables.
|
||||
AppSession, // NB: These are generated client-side (!)
|
||||
AuditLog,
|
||||
BatchGeneration,
|
||||
BetaKey,
|
||||
@@ -105,6 +106,7 @@ impl PrefixGenerator for TokenPrefix {
|
||||
fn prefix(self) -> &'static str {
|
||||
match self {
|
||||
Self::AnonymousVisitorTracking => "avt_",
|
||||
Self::AppSession => "app_session_",
|
||||
Self::AuditLog => "audit_",
|
||||
Self::BatchGeneration => "batch_g_",
|
||||
Self::BetaKey => "beta_key_",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// The primary key for users.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Default, ToSchema)]
|
||||
#[cfg_attr(feature = "database", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "database", sqlx(transparent))]
|
||||
pub struct AppSessionToken(pub String);
|
||||
|
||||
impl_mysql_token_from_row!(AppSessionToken);
|
||||
impl_string_token!(AppSessionToken);
|
||||
impl_crockford_generator!(AppSessionToken, 32usize, TokenPrefix::AppSession, CrockfordMixed);
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod anonymous_visitor_tracking;
|
||||
pub mod api_tokens_external;
|
||||
pub mod api_tokens_internal;
|
||||
pub mod app_session;
|
||||
pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
pub mod beta_keys;
|
||||
|
||||
+29
-1
@@ -5,11 +5,14 @@ use crate::state::server_state::ServerState;
|
||||
use actix_helpers::extractors::get_request_user_agent::get_request_user_agent;
|
||||
use actix_web::web::{Json, Query};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::info;
|
||||
use artcraft_api_defs::analytics::log_active_user::{LogAppActiveUserRequest, LogAppActiveUserResponse};
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use errors::AnyhowResult;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{info, warn};
|
||||
use mysql_queries::queries::analytics_active_users::upsert_analytics_app_active_user::UpsertAnalyticsAppActiveUser;
|
||||
use mysql_queries::queries::analytics_active_users::upsert_analytics_app_session::UpsertAnalyticsAppSession;
|
||||
use tokens::tokens::app_session::AppSessionToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
const CLIENT_WAIT_FOR_RETRY_MILLIS: u64 = 1_000 * 60 * 1; // Every minute
|
||||
@@ -100,6 +103,23 @@ pub async fn log_app_active_user_handler(
|
||||
};
|
||||
|
||||
upsert.upsert_with_connection(&mut mysql_connection).await?;
|
||||
|
||||
if let Some(token) = request.maybe_app_session_token.as_ref() {
|
||||
validate_app_session_token_format(token)?;
|
||||
|
||||
let upsert = UpsertAnalyticsAppSession{
|
||||
app_session_token: token,
|
||||
namespace: PaymentsNamespace::Artcraft,
|
||||
user_token: &user_token,
|
||||
ip_address: &ip_address,
|
||||
app_version: app_version.as_deref(),
|
||||
os_platform: request.maybe_os_platform.as_deref(),
|
||||
os_version: request.maybe_os_version.as_deref(),
|
||||
session_duration_seconds: request.maybe_session_duration_seconds,
|
||||
};
|
||||
|
||||
upsert.upsert_with_connection(&mut mysql_connection).await?;
|
||||
}
|
||||
|
||||
Ok(Json(LogAppActiveUserResponse {
|
||||
success: true,
|
||||
@@ -107,3 +127,11 @@ pub async fn log_app_active_user_handler(
|
||||
}))
|
||||
}
|
||||
|
||||
fn validate_app_session_token_format(app_session_token: &AppSessionToken) -> Result<(), CommonWebError> {
|
||||
if !app_session_token.as_str().starts_with(AppSessionToken::token_prefix()) {
|
||||
warn!("App session token has invalid prefix: {}", app_session_token.as_str());
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage("Invalid app session token format".to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as gpu from "detect-gpu";
|
||||
import { UsersApi } from "~/Classes/ApiManager";
|
||||
import { TurnOnGpu } from "~/pages/PageEnigma/TurnOnGpu";
|
||||
import { PrecisionSelector } from "./comps/PrecisionSelector/PrecisionSelector";
|
||||
import {
|
||||
precisionSelectedValue,
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
import { InstallSounds } from "~/pages/PageEnigma/InstallSounds";
|
||||
import { PageEditor } from "~/pages/PageEnigma/PageEditor";
|
||||
import { GalleryDragComponent } from "@storyteller/ui-gallery-modal";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import {
|
||||
PricingModal,
|
||||
CreditsModal,
|
||||
@@ -105,13 +103,6 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
InstallSounds();
|
||||
});
|
||||
|
||||
if (validGpu === "unknown") {
|
||||
return <LoadingDots />;
|
||||
}
|
||||
if (validGpu === "error") {
|
||||
return <TurnOnGpu />;
|
||||
}
|
||||
|
||||
const currentReminderModalProps = actionReminderProps.value;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import * as CopyToClipboard from "react-copy-to-clipboard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCopy } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
export const TurnOnGpu = () => {
|
||||
const [copied1, setCopied1] = useState(false);
|
||||
const [copied2, setCopied2] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (copied1) {
|
||||
setTimeout(() => setCopied1(false), 2000);
|
||||
}
|
||||
}, [copied1]);
|
||||
useEffect(() => {
|
||||
if (copied2) {
|
||||
setTimeout(() => setCopied2(false), 2000);
|
||||
}
|
||||
}, [copied2]);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 h-screen overflow-y-auto p-16">
|
||||
<div>
|
||||
Hardware acceleration is <b>required</b> for StoryTeller Studio to run
|
||||
properly. To check if this is enabled, please follow the steps for your
|
||||
browser below.
|
||||
</div>
|
||||
<div className="my-4">
|
||||
<b>Google Chrome: </b>
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
<div>
|
||||
Click the three stacked dots in upper right of the browser window.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Click the arrow next to <em>"Advanced"</em> and select{" "}
|
||||
<em>"System."</em>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Make sure the box next to{" "}
|
||||
<em>"Use hardware acceleration when available"</em> is
|
||||
toggled on.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Close and restart the browser for the change to take effect.
|
||||
</div>
|
||||
</li>
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
You can also copy and paste this link{" "}
|
||||
<CopyToClipboard.CopyToClipboard
|
||||
text="chrome://settings/system"
|
||||
onCopy={() => setCopied1(true)}
|
||||
>
|
||||
<span className="text-blue">
|
||||
chrome://settings/system
|
||||
{copied1 ? (
|
||||
<span style={{ color: "red" }}>Copied.</span>
|
||||
) : (
|
||||
<span className="text-sm">
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
</span>
|
||||
</CopyToClipboard.CopyToClipboard>
|
||||
into the address bar. This will bring you right to the system
|
||||
settings.
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
<div className="my-4">
|
||||
<b>Firefox:</b>
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
<div>
|
||||
Click on <em>"Firefox"</em> then{" "}
|
||||
<em>"Preferences."</em>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
In the <em>"General"</em> panel, go to the
|
||||
<em>"Performance" </em>section.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Uncheck the box next to
|
||||
<em>"Use recommended performance settings"</em> and make
|
||||
sure the box next to{" "}
|
||||
<em>"Use hardware acceleration when available"</em> is
|
||||
selected.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Close and restart the browser for the change to take effect.
|
||||
</div>
|
||||
</li>
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
If you need help, please follow this link:{" "}
|
||||
<a
|
||||
href="https://support.mozilla.org/en-US/kb/performance-settings?as=u&utm_source=inproduct"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue underline"
|
||||
>
|
||||
https://support.mozilla.org/en-US/kb/performance-settings?as=u&utm_source=inproduct
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
<div className="my-4">
|
||||
<b>Microsoft Edge:</b>
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
<div>
|
||||
Click the "3 dots" menu icon in the upper right of the
|
||||
browser window
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Click on <em>"Settings"</em> then{" "}
|
||||
<em>"System"</em> on the left side.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Toggle on{" "}
|
||||
<em>"Use hardware acceleration when available"</em> if
|
||||
this is not enabled.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Close and restart the browser for the change to take effect.
|
||||
</div>
|
||||
</li>
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
You can also copy and paste this link{" "}
|
||||
<CopyToClipboard.CopyToClipboard
|
||||
text="edge://settings/system"
|
||||
onCopy={() => setCopied2(true)}
|
||||
>
|
||||
<span className="text-blue">
|
||||
edge://settings/system
|
||||
{copied2 ? (
|
||||
<span style={{ color: "red" }}>Copied.</span>
|
||||
) : (
|
||||
<span className="text-sm">
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
</span>
|
||||
</CopyToClipboard.CopyToClipboard>
|
||||
into the address bar. This will bring you right to the system
|
||||
settings.
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
<div className="my-4">
|
||||
<b>Safari (Mac Only)</b>
|
||||
</div>
|
||||
<div>
|
||||
As of macOS Catalina (version 10.15), Hardware acceleration is
|
||||
automatically enabled, and there is no way turn this on and off. If you
|
||||
are using an older version of MacOS, please use the steps below:
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
<div>
|
||||
Click on the "Safari" then "Settings" >{" "}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Click "Advanced" on the top right of the Settings window
|
||||
and ensure "Use hardware acceleration" is enabled
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<div>
|
||||
If you are still having an issue with enabling hardware acceleration, we
|
||||
recommend you update your graphics card drivers, if possible. If these
|
||||
updates do not help, you may have to consider upgrading your graphics
|
||||
card or playing on a different device.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -62,4 +62,28 @@ export const VIDEO_MODELS: VideoModel[] = [
|
||||
startFrame: true,
|
||||
endFrame: false,
|
||||
}),
|
||||
new VideoModel({
|
||||
id: "veo_3",
|
||||
tauriId: "veo_3",
|
||||
fullName: "Google Veo 3",
|
||||
category: "video",
|
||||
creator: ModelCreator.Google,
|
||||
selectorName: "Google Veo 3",
|
||||
selectorDescription: "Slow, high-quality model",
|
||||
selectorBadges: ["2 min."],
|
||||
startFrame: true,
|
||||
endFrame: false,
|
||||
}),
|
||||
new VideoModel({
|
||||
id: "veo_3_fast",
|
||||
tauriId: "veo_3_fast",
|
||||
fullName: "Google Veo 3 Fast",
|
||||
category: "video",
|
||||
creator: ModelCreator.Google,
|
||||
selectorName: "Google Veo 3 Fast",
|
||||
selectorDescription: "High-quality model (faster)",
|
||||
selectorBadges: ["2 min."],
|
||||
startFrame: true,
|
||||
endFrame: false,
|
||||
}),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user