new logging endpoint (#999)
This commit is contained in:
1 parent
67f428eee7
commit
f772f93f90
27 files changed
+696
-24
No files matched your search
+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 total_generation_count = ?,\n image_generation_count = ?,\n video_generation_count = ?,\n object_generation_count = ?,\n text_to_image_count = ?,\n image_to_image_count = ?,\n text_to_video_count = ?,\n image_to_video_count = ?,\n text_to_object_count = ?,\n image_to_object_count = ?,\n image_page_prompt_count = ?,\n video_page_prompt_count = ?,\n edit_page_prompt_count = ?,\n stage_page_prompt_count = ?,\n object_page_prompt_count = ?,\n other_page_prompt_count = ?,\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 total_generation_count = ?,\n image_generation_count = ?,\n video_generation_count = ?,\n object_generation_count = ?,\n text_to_image_count = ?,\n image_to_image_count = ?,\n text_to_video_count = ?,\n image_to_video_count = ?,\n text_to_object_count = ?,\n image_to_object_count = ?,\n image_page_prompt_count = ?,\n video_page_prompt_count = ?,\n edit_page_prompt_count = ?,\n stage_page_prompt_count = ?,\n object_page_prompt_count = ?,\n other_page_prompt_count = ?,\n last_active_at = NOW()\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 47
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4b3d363a4f5a088d2040cedd010e66964ea4d5e3b3a94aaad79bc122ea165715"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"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,32 +2,109 @@ 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";
|
||||
/// This is used in Artcraft versions 0.0.1 (several) through 0.3.0
|
||||
/// Clients to this version do not send generation count data (even if the endpoint supports it).
|
||||
pub const LOG_ACTIVE_USER_V1_PATH: &str = "/v1/analytics/active_user";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
/// This is used in Artcraft versions 0.4.0 and later.
|
||||
pub const LOG_ACTIVE_USER_V2_PATH: &str = "/v1/analytics/active_user_v2";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Default)]
|
||||
pub struct LogAppActiveUserRequest {
|
||||
/// Clientside-generated session token.
|
||||
/// If present, it'll be validated against an expected format.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maybe_app_session_token: Option<AppSessionToken>,
|
||||
|
||||
|
||||
/// An override for the application platform/OS (windows, mac, linux).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maybe_os_platform: Option<String>,
|
||||
|
||||
/// An override for the version of the OS (e.g. 10.15.7, 11, 22.04).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maybe_os_version: Option<String>,
|
||||
|
||||
/// An override for the application name.
|
||||
/// If set together with `maybe_app_version`, the two will be
|
||||
/// If set together with `maybe_app_version`, the two will be
|
||||
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maybe_app_name: Option<String>,
|
||||
|
||||
/// An override for the application version.
|
||||
/// If set together with `maybe_app_name`, the two will be
|
||||
/// If set together with `maybe_app_name`, the two will be
|
||||
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maybe_app_version: Option<String>,
|
||||
|
||||
|
||||
/// How long the user has been active in the app, in seconds.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maybe_session_duration_seconds: Option<u64>,
|
||||
|
||||
// ========== GENERATION COUNTS ==========
|
||||
|
||||
/// Number of items generated across all types.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub total_generation_count: Option<u16>,
|
||||
|
||||
/// Number of images generated.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_generation_count: Option<u16>,
|
||||
|
||||
/// Number of videos generated.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub video_generation_count: Option<u16>,
|
||||
|
||||
/// Number of objects generated (meshes, etc.).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub object_generation_count: Option<u16>,
|
||||
|
||||
/// Number of images generated with text to image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_to_image_count: Option<u16>,
|
||||
|
||||
/// Number of images generated with image to image (image edits).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_to_image_count: Option<u16>,
|
||||
|
||||
/// Number of videos generated with text to video.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_to_video_count: Option<u16>,
|
||||
|
||||
/// Number of videos generated with image to video.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_to_video_count: Option<u16>,
|
||||
|
||||
/// Number of objects generated with text to object.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_to_object_count: Option<u16>,
|
||||
|
||||
/// Number of objects generated with image to object.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_to_object_count: Option<u16>,
|
||||
|
||||
/// Number of prompts from the image page.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_page_prompt_count: Option<u16>,
|
||||
|
||||
/// Number of prompts from the video page.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub video_page_prompt_count: Option<u16>,
|
||||
|
||||
/// Number of prompts from the edit page.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edit_page_prompt_count: Option<u16>,
|
||||
|
||||
/// Number of prompts from the stage page.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stage_page_prompt_count: Option<u16>,
|
||||
|
||||
/// Number of prompts from the object page.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub object_page_prompt_count: Option<u16>,
|
||||
|
||||
/// Number of prompts from other pages.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub other_page_prompt_count: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_query_string_post_request::basic_query_string_post_request;
|
||||
use artcraft_api_defs::analytics::log_active_user::{LogAppActiveUserRequest, LogAppActiveUserResponse, LOG_ACTIVE_USER_PATH};
|
||||
use artcraft_api_defs::analytics::log_active_user::{LogAppActiveUserRequest, LogAppActiveUserResponse, LOG_ACTIVE_USER_V1_PATH};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub async fn log_active_user(
|
||||
@@ -41,7 +41,7 @@ pub async fn log_active_user(
|
||||
|
||||
Ok(basic_query_string_post_request(
|
||||
api_host,
|
||||
LOG_ACTIVE_USER_PATH,
|
||||
LOG_ACTIVE_USER_V1_PATH,
|
||||
maybe_creds,
|
||||
&query,
|
||||
).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::analytics::log_active_user::{LogAppActiveUserRequest, LogAppActiveUserResponse, LOG_ACTIVE_USER_V2_PATH};
|
||||
|
||||
pub async fn log_active_user_v2(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: LogAppActiveUserRequest,
|
||||
) -> Result<LogAppActiveUserResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
LOG_ACTIVE_USER_V2_PATH,
|
||||
maybe_creds,
|
||||
&request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod log_active_user;
|
||||
pub mod log_active_user;
|
||||
pub mod log_active_user_v2;
|
||||
+10
@@ -12,6 +12,8 @@ use crate::core::events::generation_events::generation_enqueue_failure_event::Ge
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
@@ -97,6 +99,7 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
request: EnqueueImageBgRemovalCommand,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
@@ -109,6 +112,7 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
&app,
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
@@ -157,6 +161,7 @@ pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
@@ -185,6 +190,11 @@ pub async fn handle_request(
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
+12
@@ -43,6 +43,8 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
@@ -204,6 +206,7 @@ pub async fn enqueue_edit_image_command(
|
||||
request: EnqueueEditImageCommand,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
@@ -219,6 +222,7 @@ pub async fn enqueue_edit_image_command(
|
||||
&app,
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
@@ -269,6 +273,7 @@ pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
@@ -342,6 +347,13 @@ pub async fn handle_request(
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
// TODO: We don't know that this is strictly the edit page. It could be the stage. It might also be nice to distinguish inpainting.
|
||||
let num_images = request.image_count.map(|count| count as u16).unwrap_or(1);
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
+12
@@ -41,6 +41,8 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -155,6 +157,7 @@ pub async fn enqueue_image_inpaint_command(
|
||||
request: EnqueueInpaintImageCommand,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
@@ -170,6 +173,7 @@ pub async fn enqueue_image_inpaint_command(
|
||||
&app,
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
@@ -220,6 +224,7 @@ pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
@@ -272,6 +277,13 @@ pub async fn handle_request(
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
// TODO: We don't know that this is strictly the edit page. It could be the stage. It might also be nice to distinguish inpainting.
|
||||
let num_images = request.image_count.map(|count| count as u16).unwrap_or(1);
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -30,6 +30,8 @@ use serde::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
@@ -97,6 +99,7 @@ pub async fn enqueue_image_to_gaussian_command(
|
||||
app: AppHandle,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
worldlabs_creds_manager: State<'_, WorldlabsCredentialManager>,
|
||||
@@ -108,6 +111,7 @@ pub async fn enqueue_image_to_gaussian_command(
|
||||
request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&worldlabs_creds_manager,
|
||||
@@ -168,6 +172,7 @@ pub async fn handle_request(
|
||||
request: EnqueueImageToGaussianRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
worldlabs_creds_manager: &WorldlabsCredentialManager,
|
||||
@@ -201,7 +206,12 @@ pub async fn handle_request(
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_object_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::OtherPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -19,6 +19,8 @@ use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
@@ -88,6 +90,7 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
@@ -101,6 +104,7 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
@@ -171,6 +175,7 @@ pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
@@ -203,6 +208,10 @@ pub async fn handle_request(
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_object_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::ObjectPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
+10
@@ -14,6 +14,7 @@ use crate::core::events::generation_events::common::GenerationAction;
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
@@ -27,6 +28,7 @@ use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
@@ -175,6 +177,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
app: AppHandle,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
grok_creds_manager: State<'_, GrokCredentialManager>,
|
||||
@@ -190,6 +193,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&grok_creds_manager,
|
||||
@@ -262,6 +266,7 @@ pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
grok_creds_manager: &GrokCredentialManager,
|
||||
@@ -331,5 +336,10 @@ pub async fn handle_request(
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_video_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::VideoPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
+23
@@ -35,6 +35,8 @@ use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
@@ -179,6 +181,7 @@ pub async fn enqueue_text_to_image_command(
|
||||
app: AppHandle,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
mj_creds_manager: State<'_, MidjourneyCredentialManager>,
|
||||
@@ -197,6 +200,7 @@ pub async fn enqueue_text_to_image_command(
|
||||
request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&mj_creds_manager,
|
||||
@@ -267,6 +271,7 @@ pub async fn handle_request(
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
@@ -310,6 +315,24 @@ pub async fn handle_request(
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
let num_images = request.number_images.map(|n| n as u16).unwrap_or(1);
|
||||
|
||||
let is_image_to_image = request.image_media_tokens
|
||||
.as_ref()
|
||||
.map(|tokens| !tokens.is_empty())
|
||||
.unwrap_or(false);
|
||||
|
||||
let usage_type = if is_image_to_image {
|
||||
ArtcraftUsageType::ImageToResult
|
||||
} else {
|
||||
ArtcraftUsageType::TextToResult
|
||||
};
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, usage_type, ArtcraftUsagePage::ImagePage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::core::lifecycle::startup::tasks::spawn_sora_task_polling_thread::spaw
|
||||
use crate::core::lifecycle::startup::tasks::spawn_storyteller_threads::spawn_storyteller_threads;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::artcraft_platform_info::ArtcraftPlatformInfo;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::state::grok_image_prompt_queue::GrokImagePromptQueue;
|
||||
@@ -29,6 +30,7 @@ pub async fn handle_tauri_startup(
|
||||
root: AppDataRoot,
|
||||
app_env_configs: AppEnvConfigs,
|
||||
artcraft_platform_info: ArtcraftPlatformInfo,
|
||||
artcraft_usage_tracker: ArtcraftUsageTracker,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
sora_credential_manager: SoraCredentialManager,
|
||||
sora_task_queue: SoraTaskQueue,
|
||||
@@ -61,6 +63,7 @@ pub async fn handle_tauri_startup(
|
||||
spawn_storyteller_threads(
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&artcraft_platform_info,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::artcraft_platform_info::ArtcraftPlatformInfo;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::storyteller::threads::storyteller_activity_thread::storyteller_activity_thread;
|
||||
@@ -10,6 +11,7 @@ use tauri::{AppHandle, Manager};
|
||||
pub fn spawn_storyteller_threads(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
artcraft_platform_info: &ArtcraftPlatformInfo,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
@@ -25,6 +27,7 @@ pub fn spawn_storyteller_threads(
|
||||
tauri::async_runtime::spawn(storyteller_activity_thread(
|
||||
app_env_configs.clone(),
|
||||
artcraft_platform_info.clone(),
|
||||
artcraft_usage_tracker.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
));
|
||||
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker_data::ArtcraftUsageTrackerData;
|
||||
use std::sync::{Arc, LockResult, RwLock};
|
||||
use log::{error, warn};
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
/// Track generation volume so we know which areas to focus efforts on.
|
||||
/// But also note: https://en.wikipedia.org/wiki/Survivorship_bias
|
||||
#[derive(Clone)]
|
||||
pub struct ArtcraftUsageTracker {
|
||||
data: Arc<RwLock<ArtcraftUsageTrackerData>>,
|
||||
}
|
||||
|
||||
|
||||
impl ArtcraftUsageTracker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Arc::new(RwLock::new(ArtcraftUsageTrackerData::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_image_generation(&self, num_images: u16, usage_type: ArtcraftUsageType, page: ArtcraftUsagePage) -> Result<(), ArtcraftError> {
|
||||
let mut data = self.get()?;
|
||||
data.total_generation_count += num_images;
|
||||
data.image_generation_count += num_images;
|
||||
match usage_type {
|
||||
ArtcraftUsageType::TextToResult => {
|
||||
data.text_to_image_count += num_images;
|
||||
}
|
||||
ArtcraftUsageType::ImageToResult => {
|
||||
data.image_to_image_count += num_images;
|
||||
}
|
||||
ArtcraftUsageType::Other => {}
|
||||
}
|
||||
match page {
|
||||
ArtcraftUsagePage::ImagePage => {
|
||||
data.image_page_prompt_count += num_images;
|
||||
}
|
||||
ArtcraftUsagePage::EditPage => {
|
||||
data.edit_page_prompt_count += num_images;
|
||||
}
|
||||
ArtcraftUsagePage::StagePage => {
|
||||
data.stage_page_prompt_count += num_images;
|
||||
}
|
||||
ArtcraftUsagePage::OtherPage => {
|
||||
data.other_page_prompt_count += num_images;
|
||||
}
|
||||
ArtcraftUsagePage::VideoPage => {} // NB: Does not generate images.
|
||||
ArtcraftUsagePage::ObjectPage => {} // NB: Does not generate images.
|
||||
}
|
||||
self.set(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn record_video_generation(&self, num_videos: u16, usage_type: ArtcraftUsageType, page: ArtcraftUsagePage) -> Result<(), ArtcraftError> {
|
||||
let mut data = self.get()?;
|
||||
data.total_generation_count += num_videos;
|
||||
data.video_generation_count += num_videos;
|
||||
match usage_type {
|
||||
ArtcraftUsageType::TextToResult => {
|
||||
data.text_to_video_count += num_videos;
|
||||
}
|
||||
ArtcraftUsageType::ImageToResult => {
|
||||
data.image_to_video_count += num_videos;
|
||||
}
|
||||
ArtcraftUsageType::Other => {}
|
||||
}
|
||||
match page {
|
||||
ArtcraftUsagePage::VideoPage => {
|
||||
data.video_page_prompt_count += num_videos;
|
||||
}
|
||||
ArtcraftUsagePage::OtherPage => {
|
||||
data.other_page_prompt_count += num_videos;
|
||||
}
|
||||
ArtcraftUsagePage::ImagePage => {} // NB: Does not generate videos.
|
||||
ArtcraftUsagePage::EditPage => {} // NB: Does not generate videos.
|
||||
ArtcraftUsagePage::StagePage => {} // NB: Does not generate videos.
|
||||
ArtcraftUsagePage::ObjectPage => {} // NB: Does not generate videos.
|
||||
}
|
||||
self.set(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn record_object_generation(&self, num_objects: u16, usage_type: ArtcraftUsageType, page: ArtcraftUsagePage) -> Result<(), ArtcraftError> {
|
||||
let mut data = self.get()?;
|
||||
data.total_generation_count += num_objects;
|
||||
data.object_generation_count += num_objects;
|
||||
match usage_type {
|
||||
ArtcraftUsageType::TextToResult => {
|
||||
data.text_to_object_count += num_objects;
|
||||
}
|
||||
ArtcraftUsageType::ImageToResult => {
|
||||
data.image_to_object_count += num_objects;
|
||||
}
|
||||
ArtcraftUsageType::Other => {}
|
||||
}
|
||||
match page {
|
||||
ArtcraftUsagePage::ObjectPage => {
|
||||
data.object_page_prompt_count += num_objects;
|
||||
}
|
||||
ArtcraftUsagePage::OtherPage => {
|
||||
data.other_page_prompt_count += num_objects;
|
||||
}
|
||||
ArtcraftUsagePage::ImagePage => {} // NB: Does not generate objects.
|
||||
ArtcraftUsagePage::VideoPage => {} // NB: Does not generate objects.
|
||||
ArtcraftUsagePage::EditPage => {} // NB: Does not generate objects.
|
||||
ArtcraftUsagePage::StagePage => {} // NB: Does not generate objects.
|
||||
}
|
||||
self.set(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Result<ArtcraftUsageTrackerData, ArtcraftError> {
|
||||
match self.data.read() {
|
||||
Ok(data) => Ok(data.clone()),
|
||||
Err(err) => {
|
||||
error!("Lock read error: {:?}", err);
|
||||
Err(ArtcraftError::RwLockReadError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, data: ArtcraftUsageTrackerData) -> Result<(), ArtcraftError> {
|
||||
match self.data.write() {
|
||||
Ok(mut lock) => {
|
||||
*lock = data;
|
||||
Ok(())
|
||||
},
|
||||
Err(err) => {
|
||||
error!("Lock write error: {:?}", err);
|
||||
Err(ArtcraftError::RwLockWriteError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
/// Track generation volume so we know which areas to focus efforts on.
|
||||
/// But also note: https://en.wikipedia.org/wiki/Survivorship_bias
|
||||
#[derive(Clone)]
|
||||
pub struct ArtcraftUsageTrackerData {
|
||||
pub total_generation_count: u16,
|
||||
pub image_generation_count: u16,
|
||||
pub video_generation_count: u16,
|
||||
pub object_generation_count: u16,
|
||||
pub text_to_image_count: u16,
|
||||
pub image_to_image_count: u16,
|
||||
pub text_to_video_count: u16,
|
||||
pub image_to_video_count: u16,
|
||||
pub text_to_object_count: u16,
|
||||
pub image_to_object_count: u16,
|
||||
pub image_page_prompt_count: u16,
|
||||
pub video_page_prompt_count: u16,
|
||||
pub edit_page_prompt_count: u16,
|
||||
pub stage_page_prompt_count: u16,
|
||||
pub object_page_prompt_count: u16,
|
||||
pub other_page_prompt_count: u16,
|
||||
}
|
||||
|
||||
impl ArtcraftUsageTrackerData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
total_generation_count: 0,
|
||||
image_generation_count: 0,
|
||||
video_generation_count: 0,
|
||||
object_generation_count: 0,
|
||||
text_to_image_count: 0,
|
||||
image_to_image_count: 0,
|
||||
text_to_video_count: 0,
|
||||
image_to_video_count: 0,
|
||||
text_to_object_count: 0,
|
||||
image_to_object_count: 0,
|
||||
image_page_prompt_count: 0,
|
||||
video_page_prompt_count: 0,
|
||||
edit_page_prompt_count: 0,
|
||||
stage_page_prompt_count: 0,
|
||||
object_page_prompt_count: 0,
|
||||
other_page_prompt_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ArtcraftUsageType {
|
||||
TextToResult,
|
||||
ImageToResult,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ArtcraftUsagePage {
|
||||
ImagePage,
|
||||
VideoPage,
|
||||
EditPage,
|
||||
StagePage,
|
||||
ObjectPage,
|
||||
OtherPage,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod artcraft_usage_tracker;
|
||||
pub mod artcraft_usage_type;
|
||||
mod artcraft_usage_tracker_data;
|
||||
@@ -2,6 +2,7 @@ pub mod app_env_configs;
|
||||
pub mod app_preferences;
|
||||
pub mod app_startup_time;
|
||||
pub mod artcraft_platform_info;
|
||||
pub mod artcraft_usage_tracker;
|
||||
pub mod data_dir;
|
||||
pub mod expanduser;
|
||||
pub mod os_platform;
|
||||
|
||||
@@ -72,6 +72,7 @@ use tauri_plugin_dialog;
|
||||
use tauri_plugin_http;
|
||||
use tauri_plugin_log::Target;
|
||||
use tauri_plugin_log::TargetKind;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@@ -124,6 +125,9 @@ pub fn run() {
|
||||
|
||||
let worldlabs_bearer_bridge = WorldlabsBearerBridge::empty();
|
||||
let worldlabs_bearer_bridge_2 = worldlabs_bearer_bridge.clone();
|
||||
|
||||
let artcraft_usage_tracker = ArtcraftUsageTracker::new();
|
||||
let artcraft_usage_tracker_2 = artcraft_usage_tracker.clone();
|
||||
|
||||
println!("Initializing backend runtime...");
|
||||
|
||||
@@ -159,6 +163,7 @@ pub fn run() {
|
||||
root,
|
||||
env_config,
|
||||
artcraft_platform_info_2,
|
||||
artcraft_usage_tracker_2,
|
||||
storyteller_creds,
|
||||
sora_creds,
|
||||
sora_tasks,
|
||||
@@ -181,6 +186,7 @@ pub fn run() {
|
||||
.manage(app_env_configs)
|
||||
.manage(app_preferences)
|
||||
.manage(artcraft_platform_info)
|
||||
.manage(artcraft_usage_tracker)
|
||||
.manage(grok_creds_manager)
|
||||
.manage(grok_prompt_queue)
|
||||
.manage(midjourney_creds_manager)
|
||||
|
||||
+24
-1
@@ -22,12 +22,14 @@ use sqlite_tasks::queries::update_task_status::{update_task_status, UpdateTaskAr
|
||||
use std::time::Instant;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::endpoints::analytics::log_active_user::log_active_user;
|
||||
use storyteller_client::endpoints::analytics::log_active_user_v2::log_active_user_v2;
|
||||
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 storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::app_session::AppSessionToken;
|
||||
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
|
||||
// TODO: Configure this with the build and increment.
|
||||
const CLIENT_NAME : &str = "artcraft";
|
||||
@@ -38,6 +40,7 @@ const ERROR_SLEEP_MILLIS : u64 = 1_000 * 60 * 3; // 3 minutes;
|
||||
pub async fn storyteller_activity_thread(
|
||||
app_env_configs: AppEnvConfigs,
|
||||
artcraft_platform_info: ArtcraftPlatformInfo,
|
||||
artcraft_usage_tracker: ArtcraftUsageTracker,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
let startup = Instant::now();
|
||||
@@ -48,6 +51,7 @@ pub async fn storyteller_activity_thread(
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&storyteller_creds_manager,
|
||||
startup,
|
||||
&artcraft_platform_info,
|
||||
@@ -63,6 +67,7 @@ pub async fn storyteller_activity_thread(
|
||||
|
||||
async fn polling_loop(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
startup: Instant,
|
||||
artcraft_platform_info: &ArtcraftPlatformInfo,
|
||||
@@ -85,6 +90,8 @@ async fn polling_loop(
|
||||
},
|
||||
};
|
||||
|
||||
let usage_data = artcraft_usage_tracker.get()?;
|
||||
|
||||
let time_since_startup = Instant::now().duration_since(startup);
|
||||
|
||||
let request = LogAppActiveUserRequest {
|
||||
@@ -94,11 +101,27 @@ async fn polling_loop(
|
||||
maybe_os_platform: Some(artcraft_platform_info.os_platform.as_str().to_owned()),
|
||||
maybe_os_version: Some(artcraft_platform_info.os_version.clone()),
|
||||
maybe_session_duration_seconds: Some(time_since_startup.as_secs()),
|
||||
total_generation_count: Some(usage_data.total_generation_count),
|
||||
image_generation_count: Some(usage_data.image_generation_count),
|
||||
video_generation_count: Some(usage_data.video_generation_count),
|
||||
object_generation_count: Some(usage_data.object_generation_count),
|
||||
text_to_image_count: Some(usage_data.text_to_image_count),
|
||||
image_to_image_count: Some(usage_data.image_to_image_count),
|
||||
text_to_video_count: Some(usage_data.text_to_video_count),
|
||||
image_to_video_count: Some(usage_data.image_to_video_count),
|
||||
text_to_object_count: Some(usage_data.text_to_object_count),
|
||||
image_to_object_count: Some(usage_data.image_to_object_count),
|
||||
image_page_prompt_count: Some(usage_data.image_page_prompt_count),
|
||||
video_page_prompt_count: Some(usage_data.video_page_prompt_count),
|
||||
edit_page_prompt_count: Some(usage_data.edit_page_prompt_count),
|
||||
stage_page_prompt_count: Some(usage_data.stage_page_prompt_count),
|
||||
object_page_prompt_count: Some(usage_data.object_page_prompt_count),
|
||||
other_page_prompt_count: Some(usage_data.other_page_prompt_count),
|
||||
};
|
||||
|
||||
debug!("Logging active user with storyteller.");
|
||||
|
||||
let result = log_active_user(
|
||||
let result = log_active_user_v2(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
|
||||
+81
@@ -18,6 +18,23 @@ pub struct UpsertAnalyticsAppSession<'a> {
|
||||
pub os_platform: Option<&'a str>,
|
||||
pub os_version: Option<&'a str>,
|
||||
pub session_duration_seconds: Option<u64>,
|
||||
// Generation counts
|
||||
pub total_generation_count: u16,
|
||||
pub image_generation_count: u16,
|
||||
pub video_generation_count: u16,
|
||||
pub object_generation_count: u16,
|
||||
pub text_to_image_count: u16,
|
||||
pub image_to_image_count: u16,
|
||||
pub text_to_video_count: u16,
|
||||
pub image_to_video_count: u16,
|
||||
pub text_to_object_count: u16,
|
||||
pub image_to_object_count: u16,
|
||||
pub image_page_prompt_count: u16,
|
||||
pub video_page_prompt_count: u16,
|
||||
pub edit_page_prompt_count: u16,
|
||||
pub stage_page_prompt_count: u16,
|
||||
pub object_page_prompt_count: u16,
|
||||
pub other_page_prompt_count: u16,
|
||||
}
|
||||
|
||||
impl <'a> UpsertAnalyticsAppSession<'a> {
|
||||
@@ -35,6 +52,22 @@ SET
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
total_generation_count = ?,
|
||||
image_generation_count = ?,
|
||||
video_generation_count = ?,
|
||||
object_generation_count = ?,
|
||||
text_to_image_count = ?,
|
||||
image_to_image_count = ?,
|
||||
text_to_video_count = ?,
|
||||
image_to_video_count = ?,
|
||||
text_to_object_count = ?,
|
||||
image_to_object_count = ?,
|
||||
image_page_prompt_count = ?,
|
||||
video_page_prompt_count = ?,
|
||||
edit_page_prompt_count = ?,
|
||||
stage_page_prompt_count = ?,
|
||||
object_page_prompt_count = ?,
|
||||
other_page_prompt_count = ?,
|
||||
first_active_at = NOW(),
|
||||
last_active_at = NOW()
|
||||
ON DUPLICATE KEY UPDATE
|
||||
@@ -46,6 +79,22 @@ ON DUPLICATE KEY UPDATE
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
total_generation_count = ?,
|
||||
image_generation_count = ?,
|
||||
video_generation_count = ?,
|
||||
object_generation_count = ?,
|
||||
text_to_image_count = ?,
|
||||
image_to_image_count = ?,
|
||||
text_to_video_count = ?,
|
||||
image_to_video_count = ?,
|
||||
text_to_object_count = ?,
|
||||
image_to_object_count = ?,
|
||||
image_page_prompt_count = ?,
|
||||
video_page_prompt_count = ?,
|
||||
edit_page_prompt_count = ?,
|
||||
stage_page_prompt_count = ?,
|
||||
object_page_prompt_count = ?,
|
||||
other_page_prompt_count = ?,
|
||||
last_active_at = NOW()
|
||||
"#,
|
||||
// Insert case
|
||||
@@ -57,6 +106,22 @@ ON DUPLICATE KEY UPDATE
|
||||
self.os_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
self.total_generation_count,
|
||||
self.image_generation_count,
|
||||
self.video_generation_count,
|
||||
self.object_generation_count,
|
||||
self.text_to_image_count,
|
||||
self.image_to_image_count,
|
||||
self.text_to_video_count,
|
||||
self.image_to_video_count,
|
||||
self.text_to_object_count,
|
||||
self.image_to_object_count,
|
||||
self.image_page_prompt_count,
|
||||
self.video_page_prompt_count,
|
||||
self.edit_page_prompt_count,
|
||||
self.stage_page_prompt_count,
|
||||
self.object_page_prompt_count,
|
||||
self.other_page_prompt_count,
|
||||
// Update case
|
||||
self.namespace.to_str(),
|
||||
self.user_token.as_str(),
|
||||
@@ -65,6 +130,22 @@ ON DUPLICATE KEY UPDATE
|
||||
self.os_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
self.total_generation_count,
|
||||
self.image_generation_count,
|
||||
self.video_generation_count,
|
||||
self.object_generation_count,
|
||||
self.text_to_image_count,
|
||||
self.image_to_image_count,
|
||||
self.text_to_video_count,
|
||||
self.image_to_video_count,
|
||||
self.text_to_object_count,
|
||||
self.image_to_object_count,
|
||||
self.image_page_prompt_count,
|
||||
self.video_page_prompt_count,
|
||||
self.edit_page_prompt_count,
|
||||
self.stage_page_prompt_count,
|
||||
self.object_page_prompt_count,
|
||||
self.other_page_prompt_count,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -116,6 +116,22 @@ pub async fn log_app_active_user_handler(
|
||||
os_platform: request.maybe_os_platform.as_deref(),
|
||||
os_version: request.maybe_os_version.as_deref(),
|
||||
session_duration_seconds: request.maybe_session_duration_seconds,
|
||||
total_generation_count: 0,
|
||||
image_generation_count: 0,
|
||||
video_generation_count: 0,
|
||||
object_generation_count: 0,
|
||||
text_to_image_count: 0,
|
||||
image_to_image_count: 0,
|
||||
text_to_video_count: 0,
|
||||
image_to_video_count: 0,
|
||||
text_to_object_count: 0,
|
||||
image_to_object_count: 0,
|
||||
image_page_prompt_count: 0,
|
||||
video_page_prompt_count: 0,
|
||||
edit_page_prompt_count: 0,
|
||||
stage_page_prompt_count: 0,
|
||||
object_page_prompt_count: 0,
|
||||
other_page_prompt_count: 0,
|
||||
};
|
||||
|
||||
upsert.upsert_with_connection(&mut mysql_connection).await?;
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_helpers::extractors::get_request_user_agent::get_request_user_agent;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::analytics::log_active_user::{LogAppActiveUserRequest, LogAppActiveUserResponse};
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
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;
|
||||
|
||||
const CLIENT_WAIT_FOR_RETRY_MILLIS: u64 = 1_000 * 60 * 1; // Every minute
|
||||
|
||||
/// Log an active app user (JSON body version) - user must be logged in.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Analytics",
|
||||
path = "/v1/analytics/active_user_v2",
|
||||
request_body = LogAppActiveUserRequest,
|
||||
responses(
|
||||
(status = 200, description = "Success", body = LogAppActiveUserSuccessResponse),
|
||||
),
|
||||
)]
|
||||
pub async fn log_app_active_user_json_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<LogAppActiveUserRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<LogAppActiveUserResponse>, CommonWebError>
|
||||
{
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await?;
|
||||
|
||||
let user_token = maybe_user_session
|
||||
.ok_or(CommonWebError::NotAuthorized)?
|
||||
.user_token;
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
info!("Logging active user (JSON): {:?}", request);
|
||||
|
||||
let app_version = {
|
||||
let user_agent = get_request_user_agent(&http_request);
|
||||
|
||||
let mut maybe_recorded_version = None;
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
maybe_recorded_version = Some(user_agent.to_string());
|
||||
};
|
||||
|
||||
let pair = (
|
||||
request.maybe_app_name.as_deref(),
|
||||
request.maybe_app_version.as_deref(),
|
||||
);
|
||||
|
||||
match pair {
|
||||
(Some(name), Some(version)) => {
|
||||
let version = version.trim().to_string();
|
||||
let name = name.trim().to_string();
|
||||
if !version.is_empty() && !name.is_empty() {
|
||||
maybe_recorded_version = Some(format!("{}/{}", name, version));
|
||||
}
|
||||
},
|
||||
(Some(name), None) => {
|
||||
let name = name.trim().to_string();
|
||||
if !name.is_empty() {
|
||||
maybe_recorded_version = Some(name);
|
||||
}
|
||||
},
|
||||
(None, Some(version)) => {
|
||||
let version = version.trim().to_string();
|
||||
if !version.is_empty() {
|
||||
maybe_recorded_version = Some(version);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
maybe_recorded_version
|
||||
};
|
||||
|
||||
let upsert = UpsertAnalyticsAppActiveUser {
|
||||
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?;
|
||||
|
||||
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,
|
||||
total_generation_count: request.total_generation_count.unwrap_or(0),
|
||||
image_generation_count: request.image_generation_count.unwrap_or(0),
|
||||
video_generation_count: request.video_generation_count.unwrap_or(0),
|
||||
object_generation_count: request.object_generation_count.unwrap_or(0),
|
||||
text_to_image_count: request.text_to_image_count.unwrap_or(0),
|
||||
image_to_image_count: request.image_to_image_count.unwrap_or(0),
|
||||
text_to_video_count: request.text_to_video_count.unwrap_or(0),
|
||||
image_to_video_count: request.image_to_video_count.unwrap_or(0),
|
||||
text_to_object_count: request.text_to_object_count.unwrap_or(0),
|
||||
image_to_object_count: request.image_to_object_count.unwrap_or(0),
|
||||
image_page_prompt_count: request.image_page_prompt_count.unwrap_or(0),
|
||||
video_page_prompt_count: request.video_page_prompt_count.unwrap_or(0),
|
||||
edit_page_prompt_count: request.edit_page_prompt_count.unwrap_or(0),
|
||||
stage_page_prompt_count: request.stage_page_prompt_count.unwrap_or(0),
|
||||
object_page_prompt_count: request.object_page_prompt_count.unwrap_or(0),
|
||||
other_page_prompt_count: request.other_page_prompt_count.unwrap_or(0),
|
||||
};
|
||||
|
||||
upsert.upsert_with_connection(&mut mysql_connection).await?;
|
||||
}
|
||||
|
||||
Ok(Json(LogAppActiveUserResponse {
|
||||
success: true,
|
||||
wait_for_retry_millis: CLIENT_WAIT_FOR_RETRY_MILLIS,
|
||||
}))
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod log_browser_session_handler;
|
||||
pub mod log_app_active_user_handler;
|
||||
pub mod log_app_active_user_handler;
|
||||
pub mod log_app_active_user_json_handler;
|
||||
+5
@@ -1,4 +1,5 @@
|
||||
use crate::http_server::endpoints::analytics::log_app_active_user_handler::log_app_active_user_handler;
|
||||
use crate::http_server::endpoints::analytics::log_app_active_user_json_handler::log_app_active_user_json_handler;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
@@ -21,5 +22,9 @@ where
|
||||
.route(web::post().to(log_app_active_user_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/active_user_v2")
|
||||
.route(web::post().to(log_app_active_user_json_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user