Compare commits

...
Author SHA1 Message Date
Brandon Thomas a5577c5479 add frontend bindings 2025-11-26 06:10:37 -05:00
Brandon Thomas a82c2df4e6 notify frontend of event completions 2025-11-26 05:48:22 -05:00
12 changed files with 449 additions and 73 deletions
@@ -1,8 +1,10 @@
pub mod canvas_background_removal_complete_event;
pub mod credits_balance_changed_event;
pub mod image_edit_complete_event;
pub mod object_generation_complete_event;
pub mod refresh_account_state_event;
pub mod show_provider_billing_modal_event;
pub mod show_provider_login_modal_event;
pub mod subscription_plan_changed_event;
pub mod text_to_image_generation_complete_event;
pub mod text_to_image_generation_complete_event;
pub mod video_generation_complete_event;
@@ -0,0 +1,26 @@
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
use enums::tauri::ux::tauri_event_name::TauriEventName;
use serde_derive::Serialize;
use tokens::tokens::media_files::MediaFileToken;
use url::Url;
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ObjectGenerationCompleteEvent {
pub generated_video: Option<GeneratedObject>,
pub maybe_frontend_subscriber_id: Option<String>,
pub maybe_frontend_subscriber_payload: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct GeneratedObject {
pub media_token: MediaFileToken,
pub cdn_url: Url,
pub maybe_thumbnail_template: Option<String>,
}
impl BasicSendableEvent for ObjectGenerationCompleteEvent {
const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::ObjectGenerationCompleteEvent;
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success;
}
@@ -0,0 +1,26 @@
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
use enums::tauri::ux::tauri_event_name::TauriEventName;
use serde_derive::Serialize;
use tokens::tokens::media_files::MediaFileToken;
use url::Url;
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct VideoGenerationCompleteEvent {
pub generated_video: Option<GeneratedVideo>,
pub maybe_frontend_subscriber_id: Option<String>,
pub maybe_frontend_subscriber_payload: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct GeneratedVideo {
pub media_token: MediaFileToken,
pub cdn_url: Url,
pub maybe_thumbnail_template: Option<String>,
}
impl BasicSendableEvent for VideoGenerationCompleteEvent {
const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::VideoGenerationCompleteEvent;
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success;
}
@@ -0,0 +1,244 @@
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::functional_events::image_edit_complete_event::{EditedImage, ImageEditCompleteEvent};
use crate::core::events::functional_events::object_generation_complete_event::{GeneratedObject, ObjectGenerationCompleteEvent};
use crate::core::events::functional_events::text_to_image_generation_complete_event::{GeneratedImage, TextToImageGenerationCompleteEvent};
use crate::core::events::functional_events::video_generation_complete_event::{GeneratedVideo, VideoGenerationCompleteEvent};
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use anyhow::anyhow;
use artcraft_api_defs::jobs::list_session_jobs::{ListSessionJobsItem, ListSessionResultDetailsResponse};
use artcraft_api_defs::utils::media_links_to_thumbnail_template::media_links_to_thumbnail_template;
use enums::tauri::tasks::task_type::TaskType;
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
use errors::AnyhowResult;
use log::{error, warn};
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs};
use sqlite_tasks::queries::task::Task;
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
use storyteller_client::endpoints::media_files::list_batch_generated_redux_media_files::list_batch_generated_redux_media_files;
use tauri::AppHandle;
use tokens::tokens::batch_generations::BatchGenerationToken;
use tokens::tokens::media_files::MediaFileToken;
use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent;
pub async fn maybe_handle_frontend_caller_notification(
app: &AppHandle,
app_env_configs: &AppEnvConfigs,
maybe_creds: Option<&StorytellerCredentialSet>,
task: &Task,
job: &ListSessionJobsItem,
) -> AnyhowResult<()> {
let job_result = match job.maybe_result {
Some(ref res) => res,
None => {
warn!("Job result is None for task: {:?}", task);
return Ok(()); // No result, nothing to do
},
};
match task.task_type {
TaskType::ImageGeneration => {
let _r = handle_image_generation(
app,
task,
job_result,
app_env_configs,
maybe_creds,
).await?;
}
TaskType::ImageInpaintEdit => {
let _r = handle_inpaint_image_generation(
app,
task,
job_result,
app_env_configs,
maybe_creds,
).await?;
}
TaskType::VideoGeneration => {
let _r = handle_video_generation(
app,
task,
job_result,
).await?;
}
TaskType::ObjectGeneration => {
let _r = handle_object_generation(
app,
task,
job_result,
).await?;
}
TaskType::BackgroundRemoval => {
let _r = handle_background_removal_generation(
app,
task,
job_result,
).await?;
}
}
Ok(())
}
async fn handle_image_generation(
app: &AppHandle,
task: &Task,
job_result: &ListSessionResultDetailsResponse,
app_env_configs: &AppEnvConfigs,
maybe_creds: Option<&StorytellerCredentialSet>,
) -> AnyhowResult<()> {
let generated_images = match job_result.maybe_batch_token.as_ref() {
None => {
vec![GeneratedImage {
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
cdn_url: job_result.media_links.cdn_url.clone(),
maybe_thumbnail_template: job_result.media_links.maybe_thumbnail_template.clone(),
}]
}
Some(batch_token) => {
let result = list_batch_generated_redux_media_files(
&app_env_configs.storyteller_host,
maybe_creds,
batch_token,
).await?;
if result.media_files.is_empty() {
return Err(anyhow!("No media files found for batch token: {}", batch_token));
}
result.media_files
.into_iter()
.map(|file| GeneratedImage {
media_token: file.token,
cdn_url: file.media_links.cdn_url,
maybe_thumbnail_template: file.media_links.maybe_thumbnail_template,
})
.collect()
}
};
let event = TextToImageGenerationCompleteEvent {
generated_images,
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
};
event.send_infallible(&app);
Ok(())
}
async fn handle_inpaint_image_generation(
app: &AppHandle,
task: &Task,
job_result: &ListSessionResultDetailsResponse,
app_env_configs: &AppEnvConfigs,
maybe_creds: Option<&StorytellerCredentialSet>,
) -> AnyhowResult<()> {
let edited_images = match job_result.maybe_batch_token.as_ref() {
None => {
vec![EditedImage {
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
cdn_url: job_result.media_links.cdn_url.clone(),
maybe_thumbnail_template: job_result.media_links.maybe_thumbnail_template.clone(),
}]
}
Some(batch_token) => {
let result = list_batch_generated_redux_media_files(
&app_env_configs.storyteller_host,
maybe_creds,
batch_token,
).await?;
if result.media_files.is_empty() {
return Err(anyhow!("No media files found for batch token: {}", batch_token));
}
result.media_files
.into_iter()
.map(|file| EditedImage {
media_token: file.token,
cdn_url: file.media_links.cdn_url,
maybe_thumbnail_template: file.media_links.maybe_thumbnail_template,
})
.collect()
}
};
let event = ImageEditCompleteEvent {
edited_images,
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
};
event.send_infallible(&app);
Ok(())
}
async fn handle_video_generation(
app: &AppHandle,
task: &Task,
job_result: &ListSessionResultDetailsResponse,
) -> AnyhowResult<()> {
// NB: For now, we only generate one video at a time.
let event = VideoGenerationCompleteEvent {
generated_video: Some(GeneratedVideo {
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
cdn_url: job_result.media_links.cdn_url.clone(),
maybe_thumbnail_template: media_links_to_thumbnail_template(&job_result.media_links)
.map(|s| s.to_owned()),
}),
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
};
event.send_infallible(&app);
Ok(())
}
async fn handle_object_generation(
app: &AppHandle,
task: &Task,
job_result: &ListSessionResultDetailsResponse,
) -> AnyhowResult<()> {
// NB: For now, we only generate one object (3d mesh) at a time.
let event = ObjectGenerationCompleteEvent {
generated_video: Some(GeneratedObject {
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
cdn_url: job_result.media_links.cdn_url.clone(),
maybe_thumbnail_template: media_links_to_thumbnail_template(&job_result.media_links)
.map(|s| s.to_owned()),
}),
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
};
event.send_infallible(&app);
Ok(())
}
async fn handle_background_removal_generation(
app: &AppHandle,
task: &Task,
job_result: &ListSessionResultDetailsResponse,
) -> AnyhowResult<()> {
let event = CanvasBackgroundRemovalCompleteEvent {
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
image_cdn_url: job_result.media_links.cdn_url.clone(),
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
};
event.send_infallible(&app);
Ok(())
}
@@ -1,3 +1,4 @@
pub (super) mod maybe_handle_frontend_caller_notification;
pub (super) mod maybe_handle_inpainting_complete_event;
pub (super) mod maybe_handle_text_to_image_complete_event;
pub (super) mod maybe_send_background_removal_complete_event;
@@ -12,6 +12,7 @@ use crate::core::utils::enum_conversion::task_type::to_generation_action;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use crate::services::storyteller::threads::events::maybe_handle_frontend_caller_notification::maybe_handle_frontend_caller_notification;
use crate::services::storyteller::threads::events::maybe_handle_inpainting_complete_event::maybe_handle_inpainting_complete_event;
use crate::services::storyteller::threads::events::maybe_handle_text_to_image_complete_event::maybe_handle_text_to_image_complete_event;
use crate::services::storyteller::threads::events::maybe_send_background_removal_complete_event::maybe_send_background_removal_complete_event;
@@ -197,7 +198,7 @@ async fn send_additional_events(
) {
info!("Attempting to dispatch events for completed Storyteller : {:?}", task);
let result = maybe_handle_text_to_image_complete_event(
let result = maybe_handle_frontend_caller_notification(
app_handle,
app_env_configs,
creds,
@@ -206,30 +207,42 @@ async fn send_additional_events(
).await;
if let Err(err) = result {
error!("Failed to send text-to-image complete event: {:?}", err);
error!("Failed to send generation complete event: {:?}", err);
}
let result = maybe_handle_inpainting_complete_event(
app_handle,
app_env_configs,
creds,
task,
job,
).await;
//let result = maybe_handle_text_to_image_complete_event(
// app_handle,
// app_env_configs,
// creds,
// task,
// job,
//).await;
if let Err(err) = result {
error!("Failed to send image inpainting complete event: {:?}", err);
}
//if let Err(err) = result {
// error!("Failed to send text-to-image complete event: {:?}", err);
//}
let result = maybe_send_background_removal_complete_event(
app_handle,
task,
job,
).await;
//let result = maybe_handle_inpainting_complete_event(
// app_handle,
// app_env_configs,
// creds,
// task,
// job,
//).await;
if let Err(err) = result {
error!("Failed to send background removal complete event: {:?}", err);
}
//if let Err(err) = result {
// error!("Failed to send image inpainting complete event: {:?}", err);
//}
//let result = maybe_send_background_removal_complete_event(
// app_handle,
// task,
// job,
//).await;
//if let Err(err) = result {
// error!("Failed to send background removal complete event: {:?}", err);
//}
}
fn get_thumbnail_template<'a>(job: &'a ListSessionJobsItem) -> Option<&'a str> {
@@ -54,7 +54,7 @@ pub enum TauriEventName {
CanvasBgRemovedEvent,
/// Special event:
/// Image edit is complete
/// Image generation is complete
#[serde(rename = "text_to_image_generation_complete_event")]
TextToImageGenerationCompleteEvent,
@@ -62,6 +62,16 @@ pub enum TauriEventName {
/// Image edit is complete
#[serde(rename = "image_edit_complete_event")]
ImageEditCompleteEvent,
/// Special event:
/// Object (3D mesh) generation is complete
#[serde(rename = "object_generation_complete_event")]
ObjectGenerationCompleteEvent,
/// Special event:
/// Video generation is complete
#[serde(rename = "video_generation_complete_event")]
VideoGenerationCompleteEvent,
/// Special event:
/// Refresh account states
@@ -102,6 +112,8 @@ impl TauriEventName {
Self::CanvasBgRemovedEvent => "canvas_bg_removed_event",
Self::TextToImageGenerationCompleteEvent => "text_to_image_generation_complete_event",
Self::ImageEditCompleteEvent => "image_edit_complete_event",
Self::ObjectGenerationCompleteEvent => "object_generation_complete_event",
Self::VideoGenerationCompleteEvent => "video_generation_complete_event",
Self::RefreshAccountStateEvent => "refresh_account_state_event",
Self::ShowProviderBillingModalEvent => "show_provider_billing_modal_event",
Self::ShowProviderLoginModalEvent => "show_provider_login_modal_event",
@@ -120,6 +132,8 @@ impl TauriEventName {
"canvas_bg_removed_event" => Ok(Self::CanvasBgRemovedEvent),
"text_to_image_generation_complete_event" => Ok(Self::TextToImageGenerationCompleteEvent),
"image_edit_complete_event" => Ok(Self::ImageEditCompleteEvent),
"object_generation_complete_event" => Ok(Self::ObjectGenerationCompleteEvent),
"video_generation_complete_event" => Ok(Self::VideoGenerationCompleteEvent),
"refresh_account_state_event" => Ok(Self::RefreshAccountStateEvent),
"show_provider_billing_modal_event" => Ok(Self::ShowProviderBillingModalEvent),
"show_provider_login_modal_event" => Ok(Self::ShowProviderLoginModalEvent),
@@ -141,6 +155,8 @@ impl TauriEventName {
Self::CanvasBgRemovedEvent,
Self::TextToImageGenerationCompleteEvent,
Self::ImageEditCompleteEvent,
Self::ObjectGenerationCompleteEvent,
Self::VideoGenerationCompleteEvent,
Self::RefreshAccountStateEvent,
Self::ShowProviderBillingModalEvent,
Self::ShowProviderLoginModalEvent,
@@ -168,6 +184,8 @@ mod tests {
assert_serialization(TauriEventName::CanvasBgRemovedEvent, "canvas_bg_removed_event");
assert_serialization(TauriEventName::TextToImageGenerationCompleteEvent, "text_to_image_generation_complete_event");
assert_serialization(TauriEventName::ImageEditCompleteEvent, "image_edit_complete_event");
assert_serialization(TauriEventName::ObjectGenerationCompleteEvent, "object_generation_complete_event");
assert_serialization(TauriEventName::VideoGenerationCompleteEvent, "video_generation_complete_event");
assert_serialization(TauriEventName::RefreshAccountStateEvent, "refresh_account_state_event");
assert_serialization(TauriEventName::ShowProviderBillingModalEvent, "show_provider_billing_modal_event");
assert_serialization(TauriEventName::ShowProviderLoginModalEvent, "show_provider_login_modal_event");
@@ -185,6 +203,8 @@ mod tests {
assert_eq!(TauriEventName::CanvasBgRemovedEvent.to_str(), "canvas_bg_removed_event");
assert_eq!(TauriEventName::TextToImageGenerationCompleteEvent.to_str(), "text_to_image_generation_complete_event");
assert_eq!(TauriEventName::ImageEditCompleteEvent.to_str(), "image_edit_complete_event");
assert_eq!(TauriEventName::ObjectGenerationCompleteEvent.to_str(), "object_generation_complete_event");
assert_eq!(TauriEventName::VideoGenerationCompleteEvent.to_str(), "video_generation_complete_event");
assert_eq!(TauriEventName::RefreshAccountStateEvent.to_str(), "refresh_account_state_event");
assert_eq!(TauriEventName::ShowProviderBillingModalEvent.to_str(), "show_provider_billing_modal_event");
assert_eq!(TauriEventName::ShowProviderLoginModalEvent.to_str(), "show_provider_login_modal_event");
@@ -202,6 +222,8 @@ mod tests {
assert_eq!(TauriEventName::from_str("canvas_bg_removed_event").unwrap(), TauriEventName::CanvasBgRemovedEvent);
assert_eq!(TauriEventName::from_str("text_to_image_generation_complete_event").unwrap(), TauriEventName::TextToImageGenerationCompleteEvent);
assert_eq!(TauriEventName::from_str("image_edit_complete_event").unwrap(), TauriEventName::ImageEditCompleteEvent);
assert_eq!(TauriEventName::from_str("object_generation_complete_event").unwrap(), TauriEventName::ObjectGenerationCompleteEvent);
assert_eq!(TauriEventName::from_str("video_generation_complete_event").unwrap(), TauriEventName::VideoGenerationCompleteEvent);
assert_eq!(TauriEventName::from_str("refresh_account_state_event").unwrap(), TauriEventName::RefreshAccountStateEvent);
assert_eq!(TauriEventName::from_str("show_provider_billing_modal_event").unwrap(), TauriEventName::ShowProviderBillingModalEvent);
assert_eq!(TauriEventName::from_str("show_provider_login_modal_event").unwrap(), TauriEventName::ShowProviderLoginModalEvent);
@@ -211,7 +233,7 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = TauriEventName::all_variants();
assert_eq!(variants.len(), 13);
assert_eq!(variants.len(), 15);
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueSuccessEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueFailureEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationCompleteEvent));
@@ -221,6 +243,8 @@ mod tests {
assert_eq!(variants.pop_first(), Some(TauriEventName::CanvasBgRemovedEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::TextToImageGenerationCompleteEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ImageEditCompleteEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ObjectGenerationCompleteEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::VideoGenerationCompleteEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::RefreshAccountStateEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ShowProviderBillingModalEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ShowProviderLoginModalEvent));
@@ -14,8 +14,8 @@ import { VideoModel } from "@storyteller/model-list";
import { animated, useSpring } from "@react-spring/web";
import { useImageToVideoStore } from "./ImageToVideoStore";
import {
useImageToVideoGenerationCompleteEvent,
ImageToVideoGenerationCompleteEvent,
useVideoGenerationCompleteEvent,
VideoGenerationCompleteEvent,
} from "@storyteller/tauri-events";
// import { Badge } from "@storyteller/ui-badge";
import { twMerge } from "tailwind-merge";
@@ -49,8 +49,8 @@ const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => {
clearJobTokens: () => {},
};
useImageToVideoGenerationCompleteEvent(
async (event: ImageToVideoGenerationCompleteEvent) => {
useVideoGenerationCompleteEvent(
async (event: VideoGenerationCompleteEvent) => {
if (!event.generated_video) return;
completeBatch(
{
+2 -1
View File
@@ -1,11 +1,12 @@
export * from "./lib/common/BasicEventWrapper";
export * from "./lib/events/functional/CreditsBalanceChangedEvent";
export * from "./lib/events/functional/ImageEditCompleteEvent";
export * from "./lib/events/functional/ImageToVideoGenerationCompleteEvent";
export * from "./lib/events/functional/RefreshAccountStateEvent";
export * from "./lib/events/functional/ShowProviderBillingModalEvent";
export * from "./lib/events/functional/ShowProviderLoginModalEvent";
export * from "./lib/events/functional/TextToImageGenerationCompleteEvent";
export * from "./lib/events/functional/ObjectGenerationCompleteEvent";
export * from "./lib/events/functional/VideoGenerationCompleteEvent";
export * from "./lib/events/functional/SubscriptionPlanChangedEvent";
export * from "./lib/events/generation/useGenerationCompleteEvent";
export * from "./lib/events/generation/useGenerationEnqueueFailureEvent";
@@ -1,45 +0,0 @@
import { listen, UnlistenFn } from "@tauri-apps/api/event";
import { BasicEventWrapper } from "../../common/BasicEventWrapper";
import { useEffect } from "react";
const EVENT_NAME: string = "image_to_video_generation_complete_event";
export interface ImageToVideoGenerationCompleteEvent {
generated_video: GeneratedVideo;
maybe_frontend_subscriber_id?: string;
maybe_frontend_subscriber_payload?: string;
}
export interface GeneratedVideo {
media_token: string;
cdn_url: string;
}
export const useImageToVideoGenerationCompleteEvent = (
asyncCallback: (event: ImageToVideoGenerationCompleteEvent) => Promise<void>
) => {
useEffect(() => {
let isUnmounted = false;
let unlisten: Promise<UnlistenFn>;
const setup = async () => {
unlisten = listen<BasicEventWrapper<ImageToVideoGenerationCompleteEvent>>(
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());
};
}, []);
};
@@ -0,0 +1,42 @@
import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { BasicEventWrapper } from '../../common/BasicEventWrapper';
import { useEffect } from 'react';
const EVENT_NAME : string = 'object_generation_complete_event';
export interface ObjectGenerationCompleteEvent {
generated_object?: GeneratedObject,
maybe_frontend_subscriber_id?: string,
maybe_frontend_subscriber_payload?: string,
}
export interface GeneratedObject {
media_token: string,
cdn_url: string,
maybe_thumbnail_template?: string,
}
export const useObjectGenerationCompleteEvent = (asyncCallback: (event: ObjectGenerationCompleteEvent) => Promise<void>) => {
useEffect(() => {
let isUnmounted = false;
let unlisten: Promise<UnlistenFn>;
const setup = async () => {
unlisten = listen<BasicEventWrapper<ObjectGenerationCompleteEvent>>(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());
};
}, []);
}
@@ -0,0 +1,42 @@
import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { BasicEventWrapper } from '../../common/BasicEventWrapper';
import { useEffect } from 'react';
const EVENT_NAME : string = 'video_generation_complete_event';
export interface VideoGenerationCompleteEvent {
generated_video?: GeneratedVideo,
maybe_frontend_subscriber_id?: string,
maybe_frontend_subscriber_payload?: string,
}
export interface GeneratedVideo {
media_token: string,
cdn_url: string,
maybe_thumbnail_template?: string,
}
export const useVideoGenerationCompleteEvent = (asyncCallback: (event: VideoGenerationCompleteEvent) => Promise<void>) => {
useEffect(() => {
let isUnmounted = false;
let unlisten: Promise<UnlistenFn>;
const setup = async () => {
unlisten = listen<BasicEventWrapper<VideoGenerationCompleteEvent>>(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());
};
}, []);
}