From 06e82eb9dcf3ba68301e678bab52d6b750efb480 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sat, 16 Aug 2025 02:45:21 -0400 Subject: [PATCH] midjourney fixes 3 (#764) --- .../src/endpoints/submit_job.rs | 48 +++++++++--- .../src/error/midjourney_api_error.rs | 20 ----- ...=> get_midjourney_user_id_from_storage.rs} | 6 +- .../midjourney_client/src/recipes/mod.rs | 5 +- .../src/recipes/text_to_image.rs | 49 ++++++++++++ .../image/enqueue_text_to_image_command.rs | 1 + .../enqueue/image/internal_image_error.rs | 7 ++ .../image/midjourney/handle_midjourney.rs | 76 ++++++++++++++++--- .../desktop/artcraft/src/core/events/mod.rs | 3 +- .../flash_user_input_error_event.rs | 14 ++++ .../src/core/events/warning_events/mod.rs | 1 + .../enums/src/tauri/ux/tauri_event_name.rs | 14 +++- .../app/src/pages/PageEnigma/PageEnigma.tsx | 8 +- frontend/apps/editor3d/tsconfig.json | 3 + frontend/libs/tauri-events/src/index.ts | 2 +- .../lib/events/FlashUserInputErrorEvent.ts | 34 +++++++++ 16 files changed, 244 insertions(+), 47 deletions(-) rename crates/api_clients/midjourney_client/src/recipes/{get_midjourney_user_id.rs => get_midjourney_user_id_from_storage.rs} (79%) create mode 100644 crates/api_clients/midjourney_client/src/recipes/text_to_image.rs create mode 100644 crates/desktop/artcraft/src/core/events/warning_events/flash_user_input_error_event.rs create mode 100644 crates/desktop/artcraft/src/core/events/warning_events/mod.rs create mode 100644 frontend/libs/tauri-events/src/lib/events/FlashUserInputErrorEvent.ts diff --git a/crates/api_clients/midjourney_client/src/endpoints/submit_job.rs b/crates/api_clients/midjourney_client/src/endpoints/submit_job.rs index 8f501fd874..c1240ee20d 100644 --- a/crates/api_clients/midjourney_client/src/endpoints/submit_job.rs +++ b/crates/api_clients/midjourney_client/src/endpoints/submit_job.rs @@ -1,7 +1,7 @@ use crate::error::midjourney_api_error::MidjourneyApiError; use crate::error::midjourney_client_error::MidjourneyClientError; use crate::error::midjourney_error::MidjourneyError; -use log::error; +use log::{error, warn}; use serde::{Deserialize, Serialize}; use crate::client::midjourney_hostname::MidjourneyHostname; @@ -18,7 +18,17 @@ pub struct SubmitJobRequest<'a> { #[derive(Debug, Clone)] pub struct SubmitJobResponse { - pub job_id: String, + /// On success, the job ID is returned. + pub maybe_job_id: Option, + + /// On error, we have a list of error messages. + pub maybe_errors: Option>, +} + +#[derive(Debug, Clone)] +pub struct SubmitJobError { + pub error_type: Option, + pub message: Option, } pub async fn submit_job(req: SubmitJobRequest<'_>) -> Result { @@ -146,25 +156,45 @@ pub async fn submit_job(req: SubmitJobRequest<'_>) -> Result, + failure: Option>, } let response = serde_json::from_str::(response_body) .map_err(|err| MidjourneyApiError::DeserializationError(err))?; - let job_id = match response.success.get(0) { - None => { - error!("No job id found in body: {:?}", response_body); - return Err(MidjourneyApiError::NoJobId.into()); + let maybe_job_id = response.success + .get(0) + .map(|s| s.job_id.clone()); + + if maybe_job_id.is_none() { + warn!("No job id found in body: {:?}", response_body); + } + + let mut maybe_errors = None; + + if let Some(failures) = response.failure.as_ref() { + if !failures.is_empty() { + maybe_errors = Some(failures.iter() + .map(|f| SubmitJobError { + error_type: Some(f.r#type.clone()), + message: Some(f.message.clone()), + }).collect()); } - Some(item) => item.job_id.clone(), - }; + } Ok(SubmitJobResponse { - job_id, + maybe_job_id, + maybe_errors, }) } diff --git a/crates/api_clients/midjourney_client/src/error/midjourney_api_error.rs b/crates/api_clients/midjourney_client/src/error/midjourney_api_error.rs index 45b5de4d9a..59081f7d7e 100644 --- a/crates/api_clients/midjourney_client/src/error/midjourney_api_error.rs +++ b/crates/api_clients/midjourney_client/src/error/midjourney_api_error.rs @@ -6,9 +6,6 @@ use std::io; #[derive(Debug)] pub enum MidjourneyApiError { - /// There was no job ID in the otherwise valid-looking response. - NoJobId, - /// There was no user ID in an otherwise valid-looking response. NoUserId, @@ -59,9 +56,6 @@ pub enum MidjourneyApiError { /// A network error occurred. NetworkError(String), - // /// Uncategorized reqwest error. - // OtherReqwestError(reqwest::Error), - /// An error doing file I/O (on our side) IoError(io::Error), @@ -75,7 +69,6 @@ impl Display for MidjourneyApiError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { // Response body errors - Self::NoJobId => write!(f, "No job ID found in the response body."), Self::NoUserId => write!(f, "No user ID found in the response body."), Self::NoUserProps => write!(f, "No user properties found in the index HTML payload."), Self::NoInitialAuthUser => write!(f, "No initialAuthUser in the index HTML payload."), @@ -100,24 +93,11 @@ impl Display for MidjourneyApiError { // I/O errors Self::IoError(error) => write!(f, "IO error: {}", error), // Other - // Self::OtherReqwestError(error) => write!(f, "Reqwest error: {}", error), Self::Other(error) => write!(f, "Other error: {}", error), } } } -/*impl From for MidjourneyApiError { - fn from(error: reqwest::Error) -> Self { - if error.is_timeout() { - Self::Timeout(error.to_string()) - } else if error.is_connect() { - Self::NetworkError(error.to_string()) - } else { - Self::OtherReqwestError(error) - } - } -}*/ - impl From for MidjourneyApiError { fn from(error: serde_json::Error) -> Self { Self::DeserializationError(error) diff --git a/crates/api_clients/midjourney_client/src/recipes/get_midjourney_user_id.rs b/crates/api_clients/midjourney_client/src/recipes/get_midjourney_user_id_from_storage.rs similarity index 79% rename from crates/api_clients/midjourney_client/src/recipes/get_midjourney_user_id.rs rename to crates/api_clients/midjourney_client/src/recipes/get_midjourney_user_id_from_storage.rs index 3e8f27776e..1c0a112452 100644 --- a/crates/api_clients/midjourney_client/src/recipes/get_midjourney_user_id.rs +++ b/crates/api_clients/midjourney_client/src/recipes/get_midjourney_user_id_from_storage.rs @@ -9,7 +9,7 @@ pub struct GetMidjourneyUserIdRequest { pub cookie_header: String, } -pub async fn get_midjourney_user_id(req: GetMidjourneyUserIdRequest) -> Result { +pub async fn get_midjourney_user_id_from_storage(req: GetMidjourneyUserIdRequest) -> Result { let items = storage_list(GetStorageListRequest { hostname: req.hostname, cookie_header: req.cookie_header, @@ -28,7 +28,7 @@ pub async fn get_midjourney_user_id(req: GetMidjourneyUserIdRequest) -> Result AnyhowResult<()> { let cookie_header = read_to_trimmed_string("/Users/bt/secrets/midjourney/cookie.txt")?; - let result = get_midjourney_user_id(GetMidjourneyUserIdRequest { + let result = get_midjourney_user_id_from_storage(GetMidjourneyUserIdRequest { cookie_header, hostname: MidjourneyHostname::Standard, }).await?; diff --git a/crates/api_clients/midjourney_client/src/recipes/mod.rs b/crates/api_clients/midjourney_client/src/recipes/mod.rs index 0733e69f92..7ec937df23 100644 --- a/crates/api_clients/midjourney_client/src/recipes/mod.rs +++ b/crates/api_clients/midjourney_client/src/recipes/mod.rs @@ -1,3 +1,4 @@ -mod get_midjourney_user_id; +mod get_midjourney_user_id_from_storage; pub mod channel_id; -pub mod get_user_info; \ No newline at end of file +pub mod get_user_info; +pub mod text_to_image; \ No newline at end of file diff --git a/crates/api_clients/midjourney_client/src/recipes/text_to_image.rs b/crates/api_clients/midjourney_client/src/recipes/text_to_image.rs new file mode 100644 index 0000000000..77d15bce6f --- /dev/null +++ b/crates/api_clients/midjourney_client/src/recipes/text_to_image.rs @@ -0,0 +1,49 @@ +use crate::client::midjourney_hostname::MidjourneyHostname; +use crate::endpoints::submit_job::{submit_job, SubmitJobRequest}; +use crate::error::midjourney_error::MidjourneyError; +use crate::recipes::channel_id::ChannelId; + +pub struct TextToImageRequest<'a> { + pub prompt: &'a str, + pub channel_id: &'a ChannelId, + pub hostname: MidjourneyHostname, + pub cookie_header: String, +} + +#[derive(Debug, Clone)] +pub struct TextToImageResponse { + /// On success, the job ID is returned. + pub maybe_job_id: Option, + + /// On error, we have a list of error messages. + pub maybe_errors: Option>, +} + +#[derive(Debug, Clone)] +pub struct TextToImageError { + pub error_type: Option, + pub message: Option, +} + +/// Slightly more ergonomic text-to-image API. +/// As we add more `submit_job()` cases, we'll keep this simple. +pub async fn text_to_image(req: TextToImageRequest<'_>) -> Result { + let channel_id = req.channel_id.to_string(); + + let response = submit_job(SubmitJobRequest { + prompt: req.prompt, + channel_id: &channel_id, + hostname: req.hostname, + cookie_header: req.cookie_header, + }).await?; + + Ok(TextToImageResponse { + maybe_job_id: response.maybe_job_id, + maybe_errors: response.maybe_errors.map(|errs| { + errs.into_iter().map(|e| TextToImageError { + error_type: e.error_type, + message: e.message, + }).collect() + }), + }) +} diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image/enqueue_text_to_image_command.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image/enqueue_text_to_image_command.rs index 35a489153d..42bc44dea4 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image/enqueue_text_to_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/image/enqueue_text_to_image_command.rs @@ -248,6 +248,7 @@ pub async fn dispatch_request( } Some(ImageModel::Midjourney) => { return handle_midjourney( + app, request, app_env_configs, mj_creds_manager, diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image/internal_image_error.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image/internal_image_error.rs index 75b284280e..087c656197 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image/internal_image_error.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/image/internal_image_error.rs @@ -11,7 +11,14 @@ pub enum InternalImageError { NoModelSpecified, NoProviderAvailable, NeedsFalApiKey, + NeedsMidjourneyCredentials, + NeedsMidjourneyUserInfo, + NeedsMidjourneyUserId, + + /// Generic - possibly this came with an error message that we sent as an event. + MidjourneyJobEnqueueFailed, + NeedsSoraCredentials, NeedsStorytellerCredentials, FalError(FalErrorPlus), diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image/midjourney/handle_midjourney.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image/midjourney/handle_midjourney.rs index b19f6eba00..86a9f64b67 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image/midjourney/handle_midjourney.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/image/midjourney/handle_midjourney.rs @@ -4,8 +4,10 @@ use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_co use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent; use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; +use crate::core::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent; use crate::core::model::image_models::ImageModel; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::core::state::data_dir::app_data_root::AppDataRoot; @@ -22,6 +24,9 @@ use idempotency::uuid::generate_random_uuid; use log::{error, info}; use midjourney_client::client::midjourney_hostname::MidjourneyHostname; use midjourney_client::endpoints::submit_job::{submit_job, SubmitJobRequest}; +use midjourney_client::error::midjourney_api_error::MidjourneyApiError; +use midjourney_client::recipes::channel_id::ChannelId; +use midjourney_client::recipes::text_to_image::{text_to_image, TextToImageError, TextToImageRequest}; use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest}; use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; @@ -29,8 +34,10 @@ use std::time::Duration; use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; use storyteller_client::generate::image::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image; use tauri::AppHandle; +use tokens::tokens::media_files::MediaFileToken; pub async fn handle_midjourney( + app: &AppHandle, request: EnqueueTextToImageRequest, app_env_configs: &AppEnvConfigs, mj_creds_manager: &MidjourneyCredentialManager, @@ -47,6 +54,27 @@ pub async fn handle_midjourney( }, }; + // TODO: We can request population of the user info if absent or expired. + + let user_info = match mj_creds_manager.maybe_copy_user_info() { + Ok(Some(user_info)) => user_info, + Ok(None) => { + return Err(InternalImageError::NeedsMidjourneyUserInfo); + } + Err(err) => { + error!("Error reading Midjourney user info: {:?}", err); + return Err(InternalImageError::NeedsMidjourneyUserInfo); + }, + }; + + let channel_id = match user_info.user_id { + Some(user_id) => ChannelId::UserId(user_id), + None => { + error!("Midjourney user info does not contain a user ID."); + return Err(InternalImageError::NeedsMidjourneyUserId); + } + }; + info!("Calling midjourney ..."); let cookie_header = creds.to_cookie_string(); @@ -55,25 +83,30 @@ pub async fn handle_midjourney( .as_deref() .unwrap_or(""); - - let result = submit_job(SubmitJobRequest { + let result = text_to_image(TextToImageRequest { prompt, - channel_id: "singleplayer_f8a57ac3-e416-4dd4-9be8-2c4223691b01", // TODO: DO NOT COMMIT + channel_id: &channel_id, hostname: MidjourneyHostname::Standard, cookie_header, }).await; - let job_id = match result { - Ok(result) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued MidJourney. Job token: {}",result.job_id); - result.job_id - } + let result = match result { + Ok(result) => result, Err(err) => { error!("Failed to use MidJourney: {:?}", err); return Err(InternalImageError::MidjourneyError(err)); } }; + + let job_id = match result.maybe_job_id { + Some(job_id) => job_id, + None => { + error!("Failed to enqueue MidJourney: No job ID returned."); + return handle_midjourney_errors(app, result.maybe_errors); + } + }; + + info!("Successfully enqueued MidJourney. Job token: {}", job_id); Ok(TaskEnqueueSuccess { provider: GenerationProvider::Midjourney, @@ -82,3 +115,28 @@ pub async fn handle_midjourney( task_type: TaskType::ImageGeneration, }) } + +fn handle_midjourney_errors( + app: &AppHandle, + maybe_errors: Option> +) -> Result { + if let Some(errors) = maybe_errors { + if !errors.is_empty() { + let messages: Vec = errors.iter() + .map(|e| format!("{:?}", e)) + .collect(); + + let combined_message = messages.join("; "); + + let event = FlashUserInputErrorEvent { + message: format!("Midjourney Error: {}", combined_message), + }; + + if let Err(err) = event.send(&app) { + error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open + } + } + } + + Err(InternalImageError::MidjourneyJobEnqueueFailed) +} diff --git a/crates/desktop/artcraft/src/core/events/mod.rs b/crates/desktop/artcraft/src/core/events/mod.rs index 5574c16772..f3e7c25b00 100644 --- a/crates/desktop/artcraft/src/core/events/mod.rs +++ b/crates/desktop/artcraft/src/core/events/mod.rs @@ -1,5 +1,6 @@ pub mod basic_sendable_event_trait; +pub mod functional_events; pub mod generation_events; pub mod sendable_event_error; pub mod sendable_event_trait; -pub mod functional_events; \ No newline at end of file +pub mod warning_events; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/events/warning_events/flash_user_input_error_event.rs b/crates/desktop/artcraft/src/core/events/warning_events/flash_user_input_error_event.rs new file mode 100644 index 0000000000..8586d52a34 --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/warning_events/flash_user_input_error_event.rs @@ -0,0 +1,14 @@ +use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; +use enums::tauri::ux::tauri_event_name::TauriEventName; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct FlashUserInputErrorEvent { + pub message: String, +} + +impl BasicSendableEvent for FlashUserInputErrorEvent { + const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::FlashUserInputErrorEvent; + const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure; +} diff --git a/crates/desktop/artcraft/src/core/events/warning_events/mod.rs b/crates/desktop/artcraft/src/core/events/warning_events/mod.rs new file mode 100644 index 0000000000..a8a256fb0d --- /dev/null +++ b/crates/desktop/artcraft/src/core/events/warning_events/mod.rs @@ -0,0 +1 @@ +pub mod flash_user_input_error_event; \ No newline at end of file diff --git a/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs b/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs index 8e2253e74f..a79afc7e0f 100644 --- a/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs +++ b/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs @@ -47,6 +47,11 @@ pub enum TauriEventName { /// Refresh account states #[serde(rename = "refresh_account_state_event")] RefreshAccountStateEvent, + + /// Warning event: + /// Flash a user input error message + #[serde(rename = "flash_user_input_error_event")] + FlashUserInputErrorEvent, } impl_enum_display_and_debug_using_to_str!(TauriEventName); @@ -64,6 +69,7 @@ impl TauriEventName { Self::GenerationFailedEvent => "generation-failed-event", Self::CanvasBgRemovedEvent => "canvas_bg_removed_event", Self::RefreshAccountStateEvent => "refresh_account_state_event", + Self::FlashUserInputErrorEvent => "flash_user_input_error_event", } } @@ -75,6 +81,7 @@ impl TauriEventName { "generation-failed-event" => Ok(Self::GenerationFailedEvent), "canvas_bg_removed_event" => Ok(Self::CanvasBgRemovedEvent), "refresh_account_state_event" => Ok(Self::RefreshAccountStateEvent), + "flash_user_input_error_event" => Ok(Self::FlashUserInputErrorEvent), _ => Err(format!("invalid tauri_event_name: {:?}", job_status)), } } @@ -89,6 +96,7 @@ impl TauriEventName { Self::GenerationFailedEvent, Self::CanvasBgRemovedEvent, Self::RefreshAccountStateEvent, + Self::FlashUserInputErrorEvent, ]) } } @@ -109,6 +117,7 @@ mod tests { assert_serialization(TauriEventName::GenerationFailedEvent, "generation-failed-event"); assert_serialization(TauriEventName::CanvasBgRemovedEvent, "canvas_bg_removed_event"); assert_serialization(TauriEventName::RefreshAccountStateEvent, "refresh_account_state_event"); + assert_serialization(TauriEventName::FlashUserInputErrorEvent, "flash_user_input_error_event"); } #[test] @@ -119,6 +128,7 @@ mod tests { assert_eq!(TauriEventName::GenerationFailedEvent.to_str(), "generation-failed-event"); assert_eq!(TauriEventName::CanvasBgRemovedEvent.to_str(), "canvas_bg_removed_event"); assert_eq!(TauriEventName::RefreshAccountStateEvent.to_str(), "refresh_account_state_event"); + assert_eq!(TauriEventName::FlashUserInputErrorEvent.to_str(), "flash_user_input_error_event"); } #[test] @@ -129,18 +139,20 @@ mod tests { assert_eq!(TauriEventName::from_str("generation-failed-event").unwrap(), TauriEventName::GenerationFailedEvent); assert_eq!(TauriEventName::from_str("canvas_bg_removed_event").unwrap(), TauriEventName::CanvasBgRemovedEvent); assert_eq!(TauriEventName::from_str("refresh_account_state_event").unwrap(), TauriEventName::RefreshAccountStateEvent); + assert_eq!(TauriEventName::from_str("flash_user_input_error_event").unwrap(), TauriEventName::FlashUserInputErrorEvent); } #[test] fn all_variants() { let mut variants = TauriEventName::all_variants(); - assert_eq!(variants.len(), 6); + assert_eq!(variants.len(), 7); assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueSuccessEvent)); assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueFailureEvent)); assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationCompleteEvent)); assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationFailedEvent)); assert_eq!(variants.pop_first(), Some(TauriEventName::CanvasBgRemovedEvent)); assert_eq!(variants.pop_first(), Some(TauriEventName::RefreshAccountStateEvent)); + assert_eq!(variants.pop_first(), Some(TauriEventName::FlashUserInputErrorEvent)); assert_eq!(variants.pop_first(), None); } } diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx index ce63f1c2a9..d59e9e3f08 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx @@ -5,7 +5,7 @@ import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia"; import { useQueueHandler } from "./hooks/useQueueHandler"; import { ErrorDialog } from "~/components"; import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals"; -import { Toaster } from "@storyteller/ui-toaster"; +import { toast, Toaster } from "@storyteller/ui-toaster"; import { EditorLoadingBar } from "./comps/EditorLoadingBar"; import { useSignals } from "@preact/signals-react/runtime"; import { useEffect, useState } from "react"; @@ -34,6 +34,7 @@ import { actionReminderProps, ActionReminderModal, } from "@storyteller/ui-action-reminder-modal"; +import { useFlashUserInputErrorEvent } from "@storyteller/tauri-events"; export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => { useSignals(); @@ -87,6 +88,11 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => { useGenerationCompleteEvent(); useGenerationFailedEvent(); + useFlashUserInputErrorEvent(async (event) => { + console.log("Flash user input error event received:", event); + toast.error(event.message); + }); + useEffect(() => { console.log("installing event listeners"); InstallSounds(); diff --git a/frontend/apps/editor3d/tsconfig.json b/frontend/apps/editor3d/tsconfig.json index b3fad4b8fb..d85bc49534 100644 --- a/frontend/apps/editor3d/tsconfig.json +++ b/frontend/apps/editor3d/tsconfig.json @@ -136,6 +136,9 @@ { "path": "../../libs/tauri-utils" }, + { + "path": "../../libs/tauri-events" + }, { "path": "../../libs/api" } diff --git a/frontend/libs/tauri-events/src/index.ts b/frontend/libs/tauri-events/src/index.ts index 6950e0d7dc..c0fffe071d 100644 --- a/frontend/libs/tauri-events/src/index.ts +++ b/frontend/libs/tauri-events/src/index.ts @@ -1,4 +1,4 @@ export * from './lib/tauri-events'; export * from './lib/common/BasicEventWrapper'; export * from './lib/events/RefreshAccountStateEvent'; - +export * from './lib/events/FlashUserInputErrorEvent'; diff --git a/frontend/libs/tauri-events/src/lib/events/FlashUserInputErrorEvent.ts b/frontend/libs/tauri-events/src/lib/events/FlashUserInputErrorEvent.ts new file mode 100644 index 0000000000..90e4161c6b --- /dev/null +++ b/frontend/libs/tauri-events/src/lib/events/FlashUserInputErrorEvent.ts @@ -0,0 +1,34 @@ +import { listen, UnlistenFn } from '@tauri-apps/api/event'; +import { BasicEventWrapper } from '../common/BasicEventWrapper'; +import { useEffect } from 'react'; + +const EVENT_NAME : string = 'flash_user_input_error_event'; + +export interface FlashUserInputErrorEvent { + message: string, +} + +export const useFlashUserInputErrorEvent = (asyncCallback: (event: FlashUserInputErrorEvent) => Promise) => { + useEffect(() => { + let isUnmounted = false; + let unlisten: Promise; + + const setup = async () => { + unlisten = listen>(EVENT_NAME, async (wrappedEvent) => { + await asyncCallback(wrappedEvent.payload.data); + }); + + if (isUnmounted) { + unlisten.then(f => f()); // Unsubscribe if unmounted early. + } + }; + + setup(); + + return () => { + isUnmounted = true; + unlisten.then(f => f()); + }; + + }, []); +}