From e75e5624ed307ddeef5027a6e29bb9ec7c4cf0f6 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 28 May 2025 23:53:13 -0400 Subject: [PATCH] Better events (Rust side) --- .vscode/settings.json | 17 +++++-- .../core/events/basic_sendable_event_trait.rs | 50 +++++++++++++++++++ .../core/events/generation_events/common.rs | 18 +++++++ .../generation_complete_event.rs | 15 ++++++ .../generation_enqueue_failure_event.rs | 18 +++++++ .../generation_enqueue_success_event.rs | 15 ++++++ .../generation_failed_event.rs | 18 +++++++ .../src/core/events/generation_events/mod.rs | 5 ++ .../desktop/artcraft/src/core/events/mod.rs | 3 +- .../src/core/events/notification_event.rs | 45 ----------------- .../src/core/events/sendable_event_trait.rs | 4 +- .../fal/threads/fal_task_polling_thread.rs | 1 - .../sora/commands/sora_image_remix_command.rs | 31 +++++++++++- 13 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs create mode 100644 crates/desktop/artcraft/src/core/events/generation_events/common.rs create mode 100644 crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs create mode 100644 crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs create mode 100644 crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs create mode 100644 crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs create mode 100644 crates/desktop/artcraft/src/core/events/generation_events/mod.rs delete mode 100644 crates/desktop/artcraft/src/core/events/notification_event.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 465b876762..a929bf339b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,15 @@ { - "files.exclude": { - "**/*~": true, - "frontend_old/**": true - } + "files.exclude": { + "**/*~": true, + ".sqlx/**": true, + "_elasticsearch/**": true, + "_sql/**": true, + "frontend_old/**": true, + "includes/**": true, + "target/**": true, + "test_data/**": true, + "tmp/**": true, + "virtualenv/**": true, + "windows_dlls/**": true, + } } diff --git a/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs b/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs new file mode 100644 index 0000000000..92c28ea36a --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs @@ -0,0 +1,50 @@ +use crate::core::events::sendable_event_error::SendableEventError; +use log::info; +use serde::Serialize; +use tauri::{AppHandle, Emitter}; + +// Tagged enums here are waaay too "Rustic" +// #[derive(Clone, Serialize)] +// #[serde(rename_all = "camelCase", rename_all_fields = "camelCase", tag = "event", content = "data")] +// pub enum BasicEvent { +// Success(T), +// Failure(T), +// } + +#[derive(Copy, Clone, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum BasicEventStatus { + Success, + Failure, +} + +/// Wrap the event with "status" and "data" fields. +#[derive(Clone, Serialize)] +#[serde(rename_all = "snake_case")] +struct BasicEventWrapper { + status: BasicEventStatus, + data: T, +} + +pub trait BasicSendableEvent : Serialize { + /// This is the name of the event that the frontend subscribes to. + const FRONTEND_EVENT_NAME: &'static str; + + /// This is the status of the event: Success, Failure, etc. + const EVENT_STATUS: BasicEventStatus; + + /// Default implementation of send(). + /// This serializes and sends the event to the frontend. + fn send(&self, app: &AppHandle) -> Result<(), SendableEventError> { + info!("Sending event to frontend: {}", Self::FRONTEND_EVENT_NAME); + let wrapped = BasicEventWrapper { + status: Self::EVENT_STATUS, + data: self.clone(), + }; + let result = app.emit(Self::FRONTEND_EVENT_NAME, wrapped); + if let Err(err) = result { + return Err(SendableEventError::from(err)); + } + Ok(()) + } +} diff --git a/crates/desktop/artcraft/src/core/events/generation_events/common.rs b/crates/desktop/artcraft/src/core/events/generation_events/common.rs new file mode 100644 index 0000000000..59560fc249 --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/generation_events/common.rs @@ -0,0 +1,18 @@ +use serde::Serialize; + +#[derive(Debug, Copy, Clone, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum GenerationServiceName { + Sora, + Fal, +} + +#[derive(Debug, Copy, Clone, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum GenerationAction { + GenerateImage, + GenerateVideo, + RemoveBackground, + #[serde(rename = "image_to_3d")] + ImageTo3d, +} diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs new file mode 100644 index 0000000000..8579497044 --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs @@ -0,0 +1,15 @@ +use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; +use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceName}; +use serde_derive::Serialize; + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GenerationCompleteEvent { + pub service: GenerationServiceName, + pub action: GenerationAction, +} + +impl BasicSendableEvent for GenerationCompleteEvent { + const FRONTEND_EVENT_NAME: &'static str = "generation-complete-event"; + const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success; +} diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs new file mode 100644 index 0000000000..65cf73f4e5 --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs @@ -0,0 +1,18 @@ +use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; +use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceName}; +use serde_derive::Serialize; + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GenerationEnqueueFailureEvent { + pub service: GenerationServiceName, + pub action: GenerationAction, + + /// User-facing reason for the failure, if available. + pub reason: Option, +} + +impl BasicSendableEvent for GenerationEnqueueFailureEvent { + const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-failure-event"; + const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure; +} diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs new file mode 100644 index 0000000000..c54d15d68a --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs @@ -0,0 +1,15 @@ +use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; +use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceName}; +use serde_derive::Serialize; + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GenerationEnqueueSuccessEvent { + pub service: GenerationServiceName, + pub action: GenerationAction, +} + +impl BasicSendableEvent for GenerationEnqueueSuccessEvent { + const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-success-event"; + const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success; +} diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs new file mode 100644 index 0000000000..872e81a0dc --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs @@ -0,0 +1,18 @@ +use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; +use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceName}; +use serde_derive::Serialize; + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GenerationFailedEvent { + pub service: GenerationServiceName, + pub action: GenerationAction, + + /// User-facing reason for the failure, if available. + pub reason: Option, +} + +impl BasicSendableEvent for GenerationFailedEvent { + const FRONTEND_EVENT_NAME: &'static str = "generation-failed-event"; + const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure; +} diff --git a/crates/desktop/artcraft/src/core/events/generation_events/mod.rs b/crates/desktop/artcraft/src/core/events/generation_events/mod.rs new file mode 100644 index 0000000000..67ff3ba2df --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/generation_events/mod.rs @@ -0,0 +1,5 @@ +pub mod common; +pub mod generation_enqueue_failure_event; +pub mod generation_enqueue_success_event; +pub mod generation_complete_event; +pub mod generation_failed_event; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/events/mod.rs b/crates/desktop/artcraft/src/core/events/mod.rs index 23955acbf0..7483d8a04c 100644 --- a/crates/desktop/artcraft/src/core/events/mod.rs +++ b/crates/desktop/artcraft/src/core/events/mod.rs @@ -1,3 +1,4 @@ -pub mod notification_event; +pub mod basic_sendable_event_trait; +pub mod generation_events; pub mod sendable_event_error; pub mod sendable_event_trait; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/events/notification_event.rs b/crates/desktop/artcraft/src/core/events/notification_event.rs deleted file mode 100644 index 5435c91850..0000000000 --- a/crates/desktop/artcraft/src/core/events/notification_event.rs +++ /dev/null @@ -1,45 +0,0 @@ -use serde::Serialize; - -#[derive(Clone, Copy, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum NotificationModelType { - ImageSegmentation, - TextEncoder, - TextTokenizer, - Unet, - Vae, - Json, - ClipEncoder, - FluxModel, -} - -#[derive(Clone, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum NotificationEvent<'a> { - ModelDownloadStarted { - model_name: &'a str, - model_type: NotificationModelType, - }, - ModelDownloadProgress { - model_name: &'a str, - model_type: NotificationModelType, - currently_downloaded_bytes: usize, - total_file_bytes: usize, - }, - ModelDownloadComplete { - model_name: &'a str, - model_type: NotificationModelType, - }, - ModelLoading { - model_name: String, - model_type: NotificationModelType, - }, - ModelInference { - model_name: String, - model_type: NotificationModelType, - }, - ModelInferenceComplete { - model_name: String, - model_type: NotificationModelType, - }, -} diff --git a/crates/desktop/artcraft/src/core/events/sendable_event_trait.rs b/crates/desktop/artcraft/src/core/events/sendable_event_trait.rs index 4e1b11132f..11641ba869 100644 --- a/crates/desktop/artcraft/src/core/events/sendable_event_trait.rs +++ b/crates/desktop/artcraft/src/core/events/sendable_event_trait.rs @@ -1,15 +1,17 @@ -use log::info; use crate::core::events::sendable_event_error::SendableEventError; +use log::info; use serde::Serialize; use tauri::{AppHandle, Emitter}; /// Implement this on a Serializable object to be able to send it as an event to the frontend. +#[deprecated(note = "Use BasicSendableEvent instead as it wraps the event with status and data fields.")] pub trait SendableEvent : Serialize { /// This is the name of the event that the frontend subscribes to. const FRONTEND_EVENT_NAME: &'static str; /// Default implementation of send(). /// This serializes and sends the event to the frontend. + #[deprecated(note = "Use BasicSendableEvent instead as it wraps the event with status and data fields.")] fn send(&self, app: &AppHandle) -> Result<(), SendableEventError> { info!("Sending event to frontend: {}", Self::FRONTEND_EVENT_NAME); let result = app.emit(Self::FRONTEND_EVENT_NAME, self); diff --git a/crates/desktop/artcraft/src/services/fal/threads/fal_task_polling_thread.rs b/crates/desktop/artcraft/src/services/fal/threads/fal_task_polling_thread.rs index cb33f82c37..e5c6eae460 100644 --- a/crates/desktop/artcraft/src/services/fal/threads/fal_task_polling_thread.rs +++ b/crates/desktop/artcraft/src/services/fal/threads/fal_task_polling_thread.rs @@ -1,4 +1,3 @@ -use crate::core::events::sendable_event_trait::SendableEvent; use crate::core::state::data_dir::app_data_root::AppDataRoot; use crate::core::state::data_dir::trait_data_subdir::DataSubdir; use crate::core::utils::download_url_to_temp_dir::download_url_to_temp_dir; diff --git a/crates/desktop/artcraft/src/services/sora/commands/sora_image_remix_command.rs b/crates/desktop/artcraft/src/services/sora/commands/sora_image_remix_command.rs index 2fc07bd5d7..9dfc349dcb 100644 --- a/crates/desktop/artcraft/src/services/sora/commands/sora_image_remix_command.rs +++ b/crates/desktop/artcraft/src/services/sora/commands/sora_image_remix_command.rs @@ -1,5 +1,8 @@ use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; use crate::core::commands::response::shorthand::Response; +use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceName}; +use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; use crate::core::events::sendable_event_trait::SendableEvent; use crate::core::state::data_dir::app_data_root::AppDataRoot; use crate::core::state::data_dir::trait_data_subdir::DataSubdir; @@ -38,6 +41,7 @@ use storyteller_client::media_files::get_media_file::get_media_file; use storyteller_client::utils::api_host::ApiHost; use tauri::{AppHandle, Emitter, Manager, State}; use tokens::tokens::media_files::MediaFileToken; +use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds @@ -122,13 +126,26 @@ pub async fn sora_image_remix_command( Err(err) => { error!("error: {:?}", err); + // TODO(bt,2025-05-28): Remove old event types let event = SoraImageEnqueueFailureEvent {}; let result = event.send(&app); if let Err(err) = result { error!("Failed to emit event: {:?}", err); } - + + let event = GenerationEnqueueFailureEvent { + service: GenerationServiceName::Sora, + action: GenerationAction::GenerateImage, + reason: None, + }; + + let result = event.send(&app); + + if let Err(err) = result { + error!("Failed to emit event: {:?}", err); + } + let mut status = CommandErrorStatus::ServerError; let mut error_type = SoraImageRemixErrorType::ServerError; let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem."; @@ -160,6 +177,7 @@ pub async fn sora_image_remix_command( }) } Ok(_) => { + // TODO(bt,2025-05-28): Remove old event types let event = SoraImageEnqueueSuccessEvent {}; let result = event.send(&app); @@ -167,6 +185,17 @@ pub async fn sora_image_remix_command( error!("Failed to emit event: {:?}", err); } + let event = GenerationEnqueueSuccessEvent { + service: GenerationServiceName::Sora, + action: GenerationAction::GenerateImage, + }; + + let result = event.send(&app); + + if let Err(err) = result { + error!("Failed to emit event: {:?}", err); + } + Ok(().into()) } }