From 68caef8e1f367824fb66dc211dc251fcdd279b22 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 8 Oct 2025 15:06:09 -0400 Subject: [PATCH] task schema + queries 2.0 wip --- .../artcraft/artcraft_active_users.sql | 20 +++++ .../20250708000000_create_tasks_table.sql | 4 + .../desktop/artcraft/src/core/commands/mod.rs | 3 +- .../task_queue/get_task_queue_command.rs | 85 +++++++++++++++++++ .../src/core/commands/task_queue/mod.rs | 1 + .../src/core/state/data_dir/app_state_dir.rs | 3 +- crates/desktop/artcraft/src/core/state/mod.rs | 1 + .../src/core/state/task_database_version.rs | 10 +++ .../src/queries/list_tasks_for_frontend.rs | 81 ++++++++++++++++++ .../database/sqlite_tasks/src/queries/mod.rs | 3 +- .../enums/src/common/generation_provider.rs | 3 + 11 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 _sql/actions_and_analytics/artcraft/artcraft_active_users.sql create mode 100644 crates/desktop/artcraft/src/core/commands/task_queue/get_task_queue_command.rs create mode 100644 crates/desktop/artcraft/src/core/commands/task_queue/mod.rs create mode 100644 crates/desktop/artcraft/src/core/state/task_database_version.rs create mode 100644 crates/schema/database/sqlite_tasks/src/queries/list_tasks_for_frontend.rs diff --git a/_sql/actions_and_analytics/artcraft/artcraft_active_users.sql b/_sql/actions_and_analytics/artcraft/artcraft_active_users.sql new file mode 100644 index 0000000000..bb90e9c83c --- /dev/null +++ b/_sql/actions_and_analytics/artcraft/artcraft_active_users.sql @@ -0,0 +1,20 @@ +-- noinspection SqlDialectInspectionForFile +-- noinspection SqlNoDataSourceInspectionForFile +-- noinspection SqlResolveForFile + +select + u.display_name, + count(*) as sessions, + a.session_duration_seconds / 60 / 60 as hours, + a.measurement_count, + u.email_address, + a.os_platform, + a.os_version, + u.created_at, + u.maybe_source, + a.last_active_at +from analytics_app_sessions as a +left outer join users as u +on a.user_token = u.token +group by a.user_token +order by hours desc; \ No newline at end of file diff --git a/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql b/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql index 830b46617f..f63423b175 100644 --- a/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql +++ b/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql @@ -9,6 +9,7 @@ -- tasks_v1.sqlite - initial version -- tasks_v2.sqlite - added model_type (nullable) -- tasks_v3.sqlite - added frontend_caller, comments +-- tasks_v4.sqlite - added is_dismissed_by_user CREATE TABLE tasks ( -- Task auto-incrementing primary key. @@ -50,6 +51,9 @@ CREATE TABLE tasks ( -- will be re-emitted back to the frontend. frontend_subscriber_payload TEXT, + -- Whether the user has dismissed the task from view. + is_dismissed_by_user INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT (unixepoch('now')), updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')), completed_at INTEGER DEFAULT NULL diff --git a/crates/desktop/artcraft/src/core/commands/mod.rs b/crates/desktop/artcraft/src/core/commands/mod.rs index 9a4a2135e1..889111ea20 100644 --- a/crates/desktop/artcraft/src/core/commands/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/mod.rs @@ -7,4 +7,5 @@ pub mod load_without_cors_command; pub mod media_files; pub mod platform_info_command; pub mod providers; -pub mod response; \ No newline at end of file +pub mod response; +mod task_queue; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/task_queue/get_task_queue_command.rs b/crates/desktop/artcraft/src/core/commands/task_queue/get_task_queue_command.rs new file mode 100644 index 0000000000..fd52a16d13 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/task_queue/get_task_queue_command.rs @@ -0,0 +1,85 @@ +use crate::core::commands::response::shorthand::ResponseOrErrorMessage; +use crate::core::commands::response::success_response_wrapper::SerializeMarker; +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::data_dir::app_data_root::AppDataRoot; +use crate::core::state::provider_priority::{Provider, ProviderPriorityStore}; +use crate::core::state::task_database::TaskDatabase; +use crate::services::fal::state::fal_credential_manager::FalCredentialManager; +use crate::services::fal::state::fal_task_queue::FalTaskQueue; +use crate::services::sora::state::sora_task_queue::SoraTaskQueue; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; +use chrono::{DateTime, Utc}; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_model_type::TaskModelType; +use enums::tauri::tasks::task_status::TaskStatus; +use enums::tauri::tasks::task_type::TaskType; +use errors::AnyhowResult; +use log::{error, info, warn}; +use serde_derive::{Deserialize, Serialize}; +use storyteller_client::endpoints::media_files::delete_media_file::delete_media_file; +use tauri::{AppHandle, State}; +use tokens::tokens::media_files::MediaFileToken; +use tokens::tokens::sqlite::tasks::TaskId; + +#[derive(Serialize)] +pub struct GetTaskQueueCommandResponse { + tasks: Vec, +} + +#[derive(Serialize)] +pub struct TaskQueueTask { + pub id: TaskId, + pub task_status: TaskStatus, + pub task_type: TaskType, + pub model_type: Option, + pub provider: Option, + pub provider_job_id: Option, + pub created_at: DateTime, + pub updated_at: DateTime, + pub completed_at: Option>, +} + +impl SerializeMarker for GetTaskQueueCommandResponse {} + +#[tauri::command] +pub async fn get_task_queue_command( + app: AppHandle, + app_env_configs: State<'_, AppEnvConfigs>, + task_database: State<'_, TaskDatabase>, +) -> ResponseOrErrorMessage { + + info!("get_task_queue_command called"); + + let result = handle_request( + &app, + &app_env_configs, + &task_database, + ).await; + + if let Err(err) = result { + error!("get_task_queue_command failed: {:?}", err); + return Err("get_task_queue_command failed".into()) + } + + Ok(GetTaskQueueCommandResponse{}.into()) +} + +pub async fn handle_request( + app: &AppHandle, + app_env_configs: &AppEnvConfigs, + task_database: State<'_, TaskDatabase>, +) -> AnyhowResult<()> { + + let creds = storyteller_creds_manager.get_credentials()?; + + let _result = delete_media_file( + &app_env_configs.storyteller_host, + creds.as_ref(), + &request.media_file_token + ).await?; + + // TODO: Send event to frontend that the file was deleted. + + Ok(()) +} diff --git a/crates/desktop/artcraft/src/core/commands/task_queue/mod.rs b/crates/desktop/artcraft/src/core/commands/task_queue/mod.rs new file mode 100644 index 0000000000..2304b23a3d --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/task_queue/mod.rs @@ -0,0 +1 @@ +mod get_task_queue_command; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/state/data_dir/app_state_dir.rs b/crates/desktop/artcraft/src/core/state/data_dir/app_state_dir.rs index ef39db2d44..66223b5a40 100644 --- a/crates/desktop/artcraft/src/core/state/data_dir/app_state_dir.rs +++ b/crates/desktop/artcraft/src/core/state/data_dir/app_state_dir.rs @@ -1,4 +1,5 @@ use crate::core::state::data_dir::trait_data_subdir::DataSubdir; +use crate::core::state::task_database_version::TASK_DATABASE_VERSION; use std::path::{Path, PathBuf}; #[derive(Clone)] @@ -24,7 +25,7 @@ impl AppStateDir { pub fn get_tasks_sqlite_database_path(&self) -> PathBuf { // NB: This must be incremented on any change to the database schema (including comments). // The database houses ephemeral content and will be migrated automatically. - self.path.join("tasks_v3.sqlite") + self.path.join(format!("tasks_v{TASK_DATABASE_VERSION}.sqlite")) } pub fn get_window_size_config_file(&self) -> PathBuf { diff --git a/crates/desktop/artcraft/src/core/state/mod.rs b/crates/desktop/artcraft/src/core/state/mod.rs index 34df30d255..c21ce0f85f 100644 --- a/crates/desktop/artcraft/src/core/state/mod.rs +++ b/crates/desktop/artcraft/src/core/state/mod.rs @@ -7,4 +7,5 @@ pub mod expanduser; pub mod os_platform; pub mod provider_priority; pub mod task_database; +pub mod task_database_version; pub mod window; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/state/task_database_version.rs b/crates/desktop/artcraft/src/core/state/task_database_version.rs new file mode 100644 index 0000000000..eef293ca86 --- /dev/null +++ b/crates/desktop/artcraft/src/core/state/task_database_version.rs @@ -0,0 +1,10 @@ + + +/// Used to bump the task database schema version. +/// Migrations on customers' machines tend to fail on existing databases, +/// even if we instruct that the database file be deleted first. +/// It's easiest to just write to a new file and migrate from scratch. +/// We increment this whenever we make a change to the database schema. +/// This prevents deadlock on startup if the schema has changed from the +/// previous version. +pub const TASK_DATABASE_VERSION: u32 = 4; diff --git a/crates/schema/database/sqlite_tasks/src/queries/list_tasks_for_frontend.rs b/crates/schema/database/sqlite_tasks/src/queries/list_tasks_for_frontend.rs new file mode 100644 index 0000000000..bef0f35313 --- /dev/null +++ b/crates/schema/database/sqlite_tasks/src/queries/list_tasks_for_frontend.rs @@ -0,0 +1,81 @@ +use crate::connection::TaskDbConnection; +use crate::error::SqliteTasksError; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_model_type::TaskModelType; +use enums::tauri::tasks::task_status::TaskStatus; +use enums::tauri::tasks::task_type::TaskType; +use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; +use tokens::tokens::sqlite::tasks::TaskId; + +pub struct CreateTaskArgs<'a> { + pub status: TaskStatus, + pub task_type: TaskType, + pub model_type: Option, + pub provider: GenerationProvider, + pub provider_job_id: Option<&'a str>, + pub frontend_caller: Option, + pub frontend_subscriber_id: Option<&'a str>, + pub frontend_subscriber_payload: Option<&'a str>, +} +pub struct TaskList { + pub tasks: Vec, +} + +pub struct TaskItem { + pub status: TaskStatus, + pub task_type: TaskType, + pub model_type: Option, + pub provider: GenerationProvider, + pub provider_job_id: Option, + pub frontend_caller: Option, + pub frontend_subscriber_id: Option, + pub frontend_subscriber_payload: Option, +} + +pub async fn list_tasks_for_frontend( + db: & TaskDbConnection +) -> Result { + let task_id = TaskId::generate(); + + //model_type, + //frontend_caller, + let query = sqlx::query_as!( + TaskItemRaw, + r#" + SELECT + id, + task_status, + task_type, + provider, + provider_job_id, + frontend_subscriber_id, + frontend_subscriber_payload + FROM tasks + "#); + + let _r = query.fetch(db.get_pool()).await?; + + Ok(()) +} + +//fn query(&self) -> Query { +//} + + + +struct TaskItemRaw { + id: String, + task_status: String, + task_type: String, + //model_type: Option, + //id: TaskId, + //task_status: TaskStatus, + //task_type: TaskType, + //model_type: Option, + provider: Option, + provider_job_id: Option, + //frontend_caller: Option, + frontend_subscriber_id: Option, + frontend_subscriber_payload: Option, +} + diff --git a/crates/schema/database/sqlite_tasks/src/queries/mod.rs b/crates/schema/database/sqlite_tasks/src/queries/mod.rs index d10010fa9d..4c2e380f87 100644 --- a/crates/schema/database/sqlite_tasks/src/queries/mod.rs +++ b/crates/schema/database/sqlite_tasks/src/queries/mod.rs @@ -1,4 +1,5 @@ pub mod create_task; pub mod list_tasks_by_provider_and_status; pub mod list_tasks_by_provider_and_tokens; -pub mod update_task_status; \ No newline at end of file +pub mod update_task_status; +mod list_tasks_for_frontend; \ No newline at end of file diff --git a/crates/schema/public/enums/src/common/generation_provider.rs b/crates/schema/public/enums/src/common/generation_provider.rs index 86bf01235c..59765753e8 100644 --- a/crates/schema/public/enums/src/common/generation_provider.rs +++ b/crates/schema/public/enums/src/common/generation_provider.rs @@ -22,6 +22,9 @@ impl_enum_display_and_debug_using_to_str!(GenerationProvider); impl_mysql_enum_coders!(GenerationProvider); impl_mysql_from_row!(GenerationProvider); +// For Tauri +impl_sqlite_enum_coders!(GenerationProvider); + // NB: We can derive `sqlx::Type` instead of using `impl_mysql_enum_coders` impl GenerationProvider {