mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-30 15:25:57 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375ed76f3f | ||
|
|
2a09d7001b |
@@ -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<String>,
|
||||
|
||||
/// On error, we have a list of error messages.
|
||||
pub maybe_errors: Option<Vec<SubmitJobError>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmitJobError {
|
||||
pub error_type: Option<String>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn submit_job(req: SubmitJobRequest<'_>) -> Result<SubmitJobResponse, MidjourneyError> {
|
||||
@@ -146,25 +156,45 @@ pub async fn submit_job(req: SubmitJobRequest<'_>) -> Result<SubmitJobResponse,
|
||||
job_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FailurePayload {
|
||||
r#type: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct RawResponse {
|
||||
success: Vec<SuccessPayload>,
|
||||
failure: Option<Vec<FailurePayload>>,
|
||||
}
|
||||
|
||||
let response = serde_json::from_str::<RawResponse>(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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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<reqwest::Error> 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<serde_json::Error> for MidjourneyApiError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
Self::DeserializationError(error)
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ pub struct GetMidjourneyUserIdRequest {
|
||||
pub cookie_header: String,
|
||||
}
|
||||
|
||||
pub async fn get_midjourney_user_id(req: GetMidjourneyUserIdRequest) -> Result<MidjourneyUserId, MidjourneyError> {
|
||||
pub async fn get_midjourney_user_id_from_storage(req: GetMidjourneyUserIdRequest) -> Result<MidjourneyUserId, MidjourneyError> {
|
||||
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<M
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::midjourney_hostname::MidjourneyHostname;
|
||||
use crate::recipes::get_midjourney_user_id::{get_midjourney_user_id, GetMidjourneyUserIdRequest};
|
||||
use crate::recipes::get_midjourney_user_id_from_storage::{get_midjourney_user_id_from_storage, GetMidjourneyUserIdRequest};
|
||||
use errors::AnyhowResult;
|
||||
use filesys::read_to_trimmed_string::read_to_trimmed_string;
|
||||
|
||||
@@ -37,7 +37,7 @@ mod tests {
|
||||
async fn test() -> 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?;
|
||||
@@ -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;
|
||||
pub mod get_user_info;
|
||||
pub mod text_to_image;
|
||||
@@ -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<String>,
|
||||
|
||||
/// On error, we have a list of error messages.
|
||||
pub maybe_errors: Option<Vec<TextToImageError>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextToImageError {
|
||||
pub error_type: Option<String>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<TextToImageResponse, MidjourneyError> {
|
||||
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()
|
||||
}),
|
||||
})
|
||||
}
|
||||
+1
@@ -248,6 +248,7 @@ pub async fn dispatch_request(
|
||||
}
|
||||
Some(ImageModel::Midjourney) => {
|
||||
return handle_midjourney(
|
||||
app,
|
||||
request,
|
||||
app_env_configs,
|
||||
mj_creds_manager,
|
||||
|
||||
@@ -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),
|
||||
|
||||
+67
-9
@@ -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<Vec<TextToImageError>>
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
if let Some(errors) = maybe_errors {
|
||||
if !errors.is_empty() {
|
||||
let messages: Vec<String> = 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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
pub mod warning_events;
|
||||
+14
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod flash_user_input_error_event;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -136,6 +136,9 @@
|
||||
{
|
||||
"path": "../../libs/tauri-utils"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/tauri-events"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/api"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './lib/tauri-events';
|
||||
export * from './lib/common/BasicEventWrapper';
|
||||
export * from './lib/events/RefreshAccountStateEvent';
|
||||
|
||||
export * from './lib/events/FlashUserInputErrorEvent';
|
||||
|
||||
@@ -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<void>) => {
|
||||
useEffect(() => {
|
||||
let isUnmounted = false;
|
||||
let unlisten: Promise<UnlistenFn>;
|
||||
|
||||
const setup = async () => {
|
||||
unlisten = listen<BasicEventWrapper<FlashUserInputErrorEvent>>(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());
|
||||
};
|
||||
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user