diff --git a/app/components/signaled/TopBar/MyMoviesButton.tsx b/app/components/signaled/TopBar/MyMoviesButton.tsx index 643a67ed38..678767d77a 100644 --- a/app/components/signaled/TopBar/MyMoviesButton.tsx +++ b/app/components/signaled/TopBar/MyMoviesButton.tsx @@ -9,7 +9,9 @@ import { activeWorkflowJobs } from "~/signals"; export const MyMoviesButton = () => { useSignals(); - const activeCount = activeWorkflowJobs.value.length; + const activeCount = activeWorkflowJobs.value + ? activeWorkflowJobs.value.length + : 0; return (
diff --git a/app/constants.ts b/app/constants.ts index 9389eab36b..11d9e00d54 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -6,3 +6,4 @@ export const ONE_MINUTE = 60000; export const TWO_SECONDS = 2000; export const FIVE_SECONDS = 5000; export const SEVEN_SECONDS = 7000; +export const TEN_SECONDS = 10000; diff --git a/app/enums/JobStatus.ts b/app/enums/JobStatus.ts index 353a93dd7e..7b1099de11 100644 --- a/app/enums/JobStatus.ts +++ b/app/enums/JobStatus.ts @@ -8,3 +8,9 @@ export enum JobStatus { CANCELLED_BY_USER = "cancelled_by_user", CANCCELLED_BY_SYSTEM = "cancelled_by_system", } + +export enum JobType { + TextToSpeech = "text_to_speech", + VoiceConversion = "voice_conversion", + VideoStyleTransfer = "workflow", +} diff --git a/app/hooks/index.tsx b/app/hooks/index.tsx index f962410657..b5cc5590a2 100644 --- a/app/hooks/index.tsx +++ b/app/hooks/index.tsx @@ -1 +1 @@ -export * from "./useInferenceJobManager"; +export * from "./useActiveJobs"; diff --git a/app/hooks/useActiveJobs/index.tsx b/app/hooks/useActiveJobs/index.tsx new file mode 100644 index 0000000000..59e67688ff --- /dev/null +++ b/app/hooks/useActiveJobs/index.tsx @@ -0,0 +1 @@ +export { useActiveJobs } from "./useActiveJobs"; diff --git a/app/hooks/useInferenceJobManager/types.ts b/app/hooks/useActiveJobs/types.ts similarity index 59% rename from app/hooks/useInferenceJobManager/types.ts rename to app/hooks/useActiveJobs/types.ts index 3001bbee7a..182d2c8e06 100644 --- a/app/hooks/useInferenceJobManager/types.ts +++ b/app/hooks/useActiveJobs/types.ts @@ -1,5 +1,9 @@ import { Job } from "~/models"; +export type WindowExtended = typeof window & { + stopPollingActiveJobs: () => void; +}; + export type GetJobsResponse = { success: boolean; error_message?: string; diff --git a/app/hooks/useActiveJobs/useActiveJobs.ts b/app/hooks/useActiveJobs/useActiveJobs.ts new file mode 100644 index 0000000000..b667c7179d --- /dev/null +++ b/app/hooks/useActiveJobs/useActiveJobs.ts @@ -0,0 +1,130 @@ +import { useCallback, useEffect, useLayoutEffect, useState } from "react"; +import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; + +import { JobType } from "~/enums"; + +import { + activeJobs, + shouldPollActiveJobs, + startPollingActiveJobs, + stopPollingActiveJobs, +} from "~/signals"; + +import { PollRecentJobs } from "./utilities"; +import { TEN_SECONDS, TWO_SECONDS } from "~/constants"; +import { WindowExtended } from "./types"; + +export const useActiveJobs = () => { + useSignals(); + + const [timerState, setTimerState] = useState<{ + timer: NodeJS.Timeout | undefined; + interval: number; + }>({ + timer: undefined, + interval: TWO_SECONDS, + }); + + const resetTimer = useCallback((newTimeInterval: number) => { + setTimerState((curr) => { + if (!curr.timer) { + // if no timer, just change interval + return { + timer: curr.timer, + interval: newTimeInterval, + }; + } + // if timer already exist, first clear + // then set new one with new interval + clearInterval(curr.timer); + const newTimer = setInterval(() => { + PollRecentJobs(); + }, newTimeInterval); + return { + timer: newTimer, + interval: newTimeInterval, + }; + }); + }, []); + + const clearTimer = useCallback(() => { + if (timerState.timer) { + clearInterval(timerState.timer); + setTimerState((curr) => ({ + ...curr, + timer: undefined, + })); + } + }, [timerState.timer]); + + useEffect(() => { + return () => { + //make sure we run clear timer as well on dismount; + clearInterval(timerState.timer); + }; + }, [timerState.timer]); + + useLayoutEffect(() => { + //TODO: add dev account guard + (window as WindowExtended).stopPollingActiveJobs = stopPollingActiveJobs; + }, []); + + useSignalEffect(() => { + // if active jobs is not initiated, do do a first pull; + if (!activeJobs.value) { + PollRecentJobs(); + return; + } + // if there are no active jobs, stop making polls + if (activeJobs.value.length === 0) { + stopPollingActiveJobs(); + return; + } + + if (activeJobs.value.length > 0) { + // if there are active jobs, first, manage polling interval + const hasShortJobs = + activeJobs.value.find((job) => { + return ( + job.request.inference_category === JobType.TextToSpeech || + job.request.inference_category === JobType.VoiceConversion + ); + }) !== undefined; + if (!hasShortJobs && timerState.interval < TEN_SECONDS) { + resetTimer(TEN_SECONDS); + } + if (hasShortJobs && timerState.interval > TWO_SECONDS) { + resetTimer(TWO_SECONDS); + } + + // if there are active jobs, and is not polling, start polling + if (!shouldPollActiveJobs.value) { + startPollingActiveJobs(); + } + } + }); + + useSignalEffect(() => { + //trigger the pull via a timer + if (shouldPollActiveJobs.value) { + setTimerState((curr) => { + if (curr.timer) { + //do nothing if timer is already running; + return curr; + } + + //else make and set timer + const newTimer = setInterval(() => { + PollRecentJobs(); + }, curr.interval); + return { + ...curr, + timer: newTimer, + }; + }); + return; + } + // should not poll, then clear timer + clearTimer(); + }); +}; diff --git a/app/hooks/useActiveJobs/utilities.ts b/app/hooks/useActiveJobs/utilities.ts new file mode 100644 index 0000000000..05f0561335 --- /dev/null +++ b/app/hooks/useActiveJobs/utilities.ts @@ -0,0 +1,27 @@ +import { getRecentJobs } from "~/api"; +import { ToastTypes } from "~/enums"; +import { authentication, addToast, setJobs } from "~/signals"; + +export function PollRecentJobs() { + console.log("Poll recent jobs"); + const endpoint = getRecentJobs; + + fetch(endpoint, { + method: "GET", + headers: { + Accept: "application/json", + session: authentication.sessionToken.value || "", + }, + credentials: "include", + }) + .then((res) => res.json()) + .then((res) => { + if (res.jobs && res.jobs.length > 0) { + setJobs(res.jobs); + // just no jobs, not an error, do nothing + } + }) + .catch(() => { + addToast(ToastTypes.ERROR, "Unknown Error in Getting Recent Jobs"); + }); +} diff --git a/app/hooks/useBackgroundLoadingMedia/useBackgroundLoadingMedia.tsx b/app/hooks/useBackgroundLoadingMedia/useBackgroundLoadingMedia.tsx index ae66676099..f122638e35 100644 --- a/app/hooks/useBackgroundLoadingMedia/useBackgroundLoadingMedia.tsx +++ b/app/hooks/useBackgroundLoadingMedia/useBackgroundLoadingMedia.tsx @@ -1,47 +1,18 @@ -import { useCallback } from "react"; import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; -import { GetUserMovies, instanceOfMediaListResponse } from "./utilities"; -import { addToast, authentication, completedJobs } from "~/signals"; +import { PollUserMovies, PollUserAudioItems } from "./utilities"; -import { - myMovies, - setMyMovies, - shouldPollMyMovies, -} from "~/pages/PageEnigma/signals"; -import { ToastTypes } from "~/enums"; +import { completedJobs, userMovies, userAudioItems } from "~/signals"; + +import { JobType } from "~/enums"; export const useBackgroundLoadingMedia = () => { useSignals(); - const PollMyMovies = useCallback(() => { - // console.log("polling my movies"); - if (authentication.userInfo.value?.username) { - GetUserMovies(authentication.userInfo.value.username).then((res) => { - // console.log("GetUerMovies has response:", res); - if (instanceOfMediaListResponse(res)) { - setMyMovies(res.results); - } else { - addToast(ToastTypes.ERROR, res.error_reason); - } - }); - } - }, []); - - useSignalEffect(() => { - if (shouldPollMyMovies.value) { - // shouldPollMyMovies is initiated as true to guaruntee first pull on load - PollMyMovies(); - } - }); - useSignalEffect(() => { //CASE 1: first load - // if myMovies undefined, first load is not done - if (!myMovies.value) { - // turn on polling if it is not already on - if (!shouldPollMyMovies.value) { - shouldPollMyMovies.value = true; - } + // if myMovies undefined, poll for the first time + if (!userMovies.value) { + PollUserMovies(); return; } @@ -53,7 +24,7 @@ export const useBackgroundLoadingMedia = () => { const workflowJobsTokens = completedJobs.value .filter((job) => { if ( - job.request.inference_category === "workflow" && + job.request.inference_category === JobType.VideoStyleTransfer && job.maybe_result.entity_token ) { return true; @@ -67,21 +38,61 @@ export const useBackgroundLoadingMedia = () => { return; } - const myMoviesTokens = myMovies.value.map((movie) => movie.token); + const userMoviesTokens = userMovies.value.map((movie) => movie.token); const isEveryTokenIncludedInPolled = workflowJobsTokens.every((token) => - myMoviesTokens.includes(token), + userMoviesTokens.includes(token), ); if (!isEveryTokenIncludedInPolled) { //there are videos newly completed, poll - shouldPollMyMovies.value = true; + PollUserMovies(); return; } - // at this point, make sure we stop polling because: - // - myMovies first load is done - // - completed jobs that contain new videos are all polled - if (shouldPollMyMovies.value) { - shouldPollMyMovies.value = false; + //else, no new movies, no need to poll; + }); + + useSignalEffect(() => { + //CASE 1: first load + // if audioItems undefined, poll for the first time + if (!userAudioItems.value) { + PollUserAudioItems(); + return; } + + //CASE 2: pull after jobs completion + if (!completedJobs.value || completedJobs.value.length === 0) { + return; // nothing to do if there's no complete jobs + } + + const audioJobsTokens = completedJobs.value + .filter((job) => { + if ( + (job.request.inference_category === JobType.TextToSpeech || + job.request.inference_category === JobType.VoiceConversion) && + job.maybe_result.entity_token + ) { + return true; + } + return false; + }) + .map((job) => job.maybe_result.entity_token); + + if (audioJobsTokens.length === 0) { + //nothing to do if no complete jobs involves audio + return; + } + + const userAudioItemsTokens = userAudioItems.value.map( + (audio) => audio.media_id, + ); + const isEveryTokenIncludedInPolled = audioJobsTokens.every((token) => + userAudioItemsTokens.includes(token), + ); + if (!isEveryTokenIncludedInPolled) { + //there are videos newly completed, poll + PollUserAudioItems(); + return; + } + //else, no new movies, no need to poll; }); }; diff --git a/app/hooks/useBackgroundLoadingMedia/utilities.ts b/app/hooks/useBackgroundLoadingMedia/utilities.ts index 3eccc1d97a..048afea2dc 100644 --- a/app/hooks/useBackgroundLoadingMedia/utilities.ts +++ b/app/hooks/useBackgroundLoadingMedia/utilities.ts @@ -1,30 +1,56 @@ import { MediaFileType } from "~/pages/PageEnigma/enums"; +import { ToastTypes } from "~/enums"; + import { GetMediaByUser, GetMediaListResponse, } from "~/api/media_files/GetMediaByUser"; -export function instanceOfMediaListResponse( - object: GetMediaListResponse | { success: boolean; error_reason: string }, -): object is GetMediaListResponse { - return "results" in object; -} -export function GetUserMovies(username: string) { +import { + addToast, + authentication, + setUserAudioItems, + setUserMovies, +} from "~/signals"; +const { userInfo } = authentication; + +export function PollUserMovies() { + if (!userInfo.value) { + //do nothing return if login info does not exist + return; + } return GetMediaByUser( - username, + userInfo.value.username, {}, { filter_media_type: MediaFileType.Video, }, ) .then((res: GetMediaListResponse) => { - return res; + setUserMovies(res.results); }) .catch(() => { - return { - success: false, - error_reason: "Unknown Error in Loading My Movies", - }; + addToast(ToastTypes.ERROR, "Unknown Error in Loading My Movies"); + }); +} + +export function PollUserAudioItems() { + if (!userInfo.value) { + //do nothing return if login info does not exist + return; + } + return GetMediaByUser( + userInfo.value.username, + {}, + { + filter_media_type: MediaFileType.Audio, + }, + ) + .then((res: GetMediaListResponse) => { + setUserAudioItems(res.results); + }) + .catch(() => { + addToast(ToastTypes.ERROR, "Unknown Error in Loading My Audio Items"); }); } diff --git a/app/hooks/useInferenceJobManager/index.tsx b/app/hooks/useInferenceJobManager/index.tsx deleted file mode 100644 index 1e25310d00..0000000000 --- a/app/hooks/useInferenceJobManager/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { useInferenceJobManager } from "./useInferenceJobManager"; -export { useActiveJobs } from "./useActiveJobs"; diff --git a/app/hooks/useInferenceJobManager/useActiveJobs.ts b/app/hooks/useInferenceJobManager/useActiveJobs.ts deleted file mode 100644 index 8372f2490f..0000000000 --- a/app/hooks/useInferenceJobManager/useActiveJobs.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useEffect } from "react"; -import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; - -import { GetJobsResponse } from "./types"; -import { ToastTypes } from "~/enums"; -import { SEVEN_SECONDS } from "~/constants"; - -import { - activeJobs, - addToast, - setJobs, - shouldPollActiveJobs, - startPollingActiveJobs, - stopPollingActiveJobs, -} from "~/signals"; - -import { GetRecentJobs } from "./utilities"; - -export const useActiveJobs = () => { - useSignals(); - - useEffect(() => { - const PollRecentJobs = () => { - GetRecentJobs().then((res: GetJobsResponse) => { - if (!res.success) { - addToast( - ToastTypes.ERROR, - res.error_message || "Unknown Error in Getting Recent Jobs", - ); - } - if (res.jobs && res.jobs.length > 0) { - setJobs(res.jobs); - } - // just no jobs, not an error, do nothing - }); - }; - - // Initial Poll - PollRecentJobs(); - - //trigger the pull every two seconds - const intervalTimer = setInterval(() => { - if (shouldPollActiveJobs.value) { - //but only do it if necessary - PollRecentJobs(); - } - }, SEVEN_SECONDS); - return () => { - clearInterval(intervalTimer); - }; - }, []); - - useSignalEffect(() => { - // if active jobs is not initiated, do nothing - if (!activeJobs.value) { - return; - } - // if there are no active jobs, stop making polls - if (activeJobs.value.length === 0) { - stopPollingActiveJobs(); - return; - } - // if there are active jobs, and is not polling, start polling - if (activeJobs.value.length > 0 && !shouldPollActiveJobs.value) { - startPollingActiveJobs(); - } - }); -}; diff --git a/app/hooks/useInferenceJobManager/useInferenceJobManager.tsx b/app/hooks/useInferenceJobManager/useInferenceJobManager.tsx deleted file mode 100644 index 05eaf06c22..0000000000 --- a/app/hooks/useInferenceJobManager/useInferenceJobManager.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useCallback, useEffect } from "react"; -import { useSignals } from "@preact/signals-react/runtime"; - -import { ErrorResponse, GetJobStatusResponse } from "~/pages/PageEnigma/models"; -import { JobState } from "~/pages/PageEnigma/enums"; -import { ToastTypes } from "~/enums"; - -import { addToast } from "~/signals"; -import { - inferenceJobs, - updateInferenceJob, -} from "~/pages/PageEnigma/signals/inferenceJobs"; - -import { GetInferenceJobStatus } from "./utilities"; - -function shouldKeepPolling(currStatus: string) { - return currStatus === JobState.PENDING || currStatus === JobState.STARTED; -} - -export const useInferenceJobManager = () => { - useSignals(); - - const pollInferenceJobs = useCallback(() => { - if (inferenceJobs.value.length > 0) { - inferenceJobs.value.forEach((job) => { - if (shouldKeepPolling(job.job_status)) { - GetInferenceJobStatus(job.job_id).then( - (res: GetJobStatusResponse | ErrorResponse) => { - if (res?.success) { - console.log("inference", res); - const response = res as GetJobStatusResponse; - updateInferenceJob({ - ...job, - job_status: response.state.status.status, - result: response.state.maybe_result, - }); - return; - } - addToast(ToastTypes.ERROR, (res as ErrorResponse).error_reason); - }, - ); - } - }); - } - }, []); - - useEffect(() => { - const intervalTimer = setInterval(() => { - pollInferenceJobs(); - }, 2000); - return () => { - clearInterval(intervalTimer); - }; - }, [pollInferenceJobs]); -}; diff --git a/app/hooks/useInferenceJobManager/utilities.ts b/app/hooks/useInferenceJobManager/utilities.ts deleted file mode 100644 index 95729821cc..0000000000 --- a/app/hooks/useInferenceJobManager/utilities.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { ErrorResponse, GetJobStatusResponse } from "~/pages/PageEnigma/models"; - -import { getActiveJobs, getJobStatus, getRecentJobs } from "~/api"; -import { authentication } from "~/signals"; - -export function GetInferenceJobStatus( - jobToken: string, -): Promise { - const endpoint = getJobStatus(jobToken); - - return fetch(endpoint, { - method: "GET", - headers: { - Accept: "application/json", - session: authentication.sessionToken.value || "", - }, - credentials: "include", - }) - .then((res) => res.json()) - .then((res) => { - const response: GetJobStatusResponse | ErrorResponse = res; - return response; - }) - .catch(() => { - return { - success: false, - error_reason: "Unknown error", - }; - }); -} - -export function GetRecentJobs() { - const endpoint = getRecentJobs; - - return fetch(endpoint, { - method: "GET", - headers: { - Accept: "application/json", - session: authentication.sessionToken.value || "", - }, - credentials: "include", - }) - .then((res) => res.json()) - .then((res) => { - return res; - }) - .catch(() => { - return { - success: false, - error_reason: "Unknown Error in Getting Recent Jobs", - }; - }); -} - -export function GetActiveJobs() { - const endpoint = getActiveJobs; - - return fetch(endpoint, { - method: "GET", - headers: { - Accept: "application/json", - session: authentication.sessionToken.value || "", - }, - credentials: "include", - }) - .then((res) => res.json()) - .then((res) => { - return res; - }) - .catch(() => { - return { - success: false, - error_reason: "Unknown error", - }; - }); -} diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 5e5f61e27a..0f7bda0869 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -2,14 +2,13 @@ import { PageEditor } from "~/pages/PageEnigma/PageEditor"; import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackProvider"; import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent"; import { EngineProvider } from "./contexts/EngineContext"; -import { useInferenceJobManager, useActiveJobs } from "~/hooks"; +import { useActiveJobs } from "~/hooks"; import { useQueueHandler } from "./hooks/useQueueHandler"; import { ErrorDialog, LoadingBar } from "~/components"; import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals"; import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia"; export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => { - useInferenceJobManager(); useActiveJobs(); useBackgroundLoadingMedia(); diff --git a/app/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx b/app/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx index d61ee9839d..971dcecfbc 100644 --- a/app/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx +++ b/app/pages/PageEnigma/comps/GenerateModals/MyMovies.tsx @@ -1,15 +1,12 @@ -import { - // generateMovieId, - myMovies, - viewMyMovies, -} from "~/pages/PageEnigma/signals"; +import { viewMyMovies } from "~/pages/PageEnigma/signals"; import { CompletedCard } from "~/pages/PageEnigma/comps/GenerateModals/CompletedCard"; import { InProgressCard } from "~/pages/PageEnigma/comps/GenerateModals/InProgressCard"; import { useSignals } from "@preact/signals-react/runtime"; import { faFilm } from "@fortawesome/pro-solid-svg-icons"; import { TransitionDialogue } from "~/components"; -import { activeWorkflowJobs } from "~/signals"; +import { activeWorkflowJobs, userMovies } from "~/signals"; + interface Props { setMovieId: (page: string) => void; } @@ -41,7 +38,7 @@ export function MyMovies({ setMovieId }: Props) {
Completed
- {myMovies.value?.map((movie) => ( + {userMovies.value?.map((movie) => ( { useSignals(); @@ -54,71 +35,9 @@ export const AudioTab = () => { Array >([]); - const handleListAudioByUser = useCallback(() => { - function getTitle(item: MediaInfo) { - // console.log(item); - if (item.maybe_title) return item.maybe_title; - if ( - item.origin && - item.origin.maybe_model && - item.origin.maybe_model.title - ) - return item.origin.maybe_model.title; - return "Media Audio"; - } - function getCategory(item: MediaInfo) { - if ( - item.origin && - item.origin.product_category && - item.origin.product_category !== "unknown" - ) - return item.origin.product_category; - if (item.origin_category) return item.origin_category; - return "unknown"; - } - function checkIsNew(previousItems: AudioMediaItem[], token: string) { - const foundItem = previousItems.find((item) => { - return token === item.media_id && item.isNew !== true; - }); - return foundItem === undefined; - } - function getLength(item: MediaInfo) { - return item.maybe_duration_millis - ? (item.maybe_duration_millis / 1000) * 60 - : undefined; - } - isRetreivingAudioItems.value = true; - ListAudioByUser().then((res: MediaInfo[]) => { - isRetreivingAudioItems.value = false; - const previousItems = [...audioItemsFromServer.value]; - audioItemsFromServer.value = res.map((item) => { - const morphedItem: AudioMediaItem = { - version: 1, - type: AssetType.AUDIO, - category: getCategory(item), - media_id: item.token, - object_uuid: item.token, - name: getTitle(item), - description: item.maybe_text_transcript || "", - publicBucketPath: item.public_bucket_path, - length: getLength(item), - thumbnail: "/resources/placeholders/audio_placeholder.png", - isMine: true, - isNew: - previousItems.length > 0 - ? checkIsNew(previousItems, item.token) - : false, - // isBookmarked?: boolean; - }; - return morphedItem; - }); - }); - }, []); - useEffect(() => { if (!state.firstLoad) { //fetch all the data on first load once, after securing auth info - handleListAudioByUser(); ListTtsModels().then((res) => { if (res) setTtsModels(res); }); @@ -128,79 +47,7 @@ export const AudioTab = () => { setState((curr) => ({ ...curr, firstLoad: true })); // completed the first load } - }, [state, handleListAudioByUser]); - - useEffect(() => { - //this listens to ttsState and push its new inference jobs - setState((curr) => { - const { ttsState: currTtsState } = curr; - if (currTtsState.hasEnqueued < currTtsState.inferenceTokens.length) { - addInferenceJob({ - version: 1, - job_id: - currTtsState.inferenceTokens[ - currTtsState.inferenceTokens.length - 1 - ], - job_type: InferenceJobType.TextToSpeech, - job_status: JobState.PENDING, - }); - return { - ...curr, - ttsState: { - ...currTtsState, - hasEnqueued: currTtsState.hasEnqueued + 1, - }, - }; - } - return curr; //case of no new jobs, do nothing - }); - }, [state.ttsState]); - - useEffect(() => { - //this listens to v2vState and push its new inference jobs - setState((curr) => { - const { v2vState: currV2VState } = curr; - if (currV2VState.hasEnqueued < currV2VState.inferenceTokens.length) { - addInferenceJob({ - version: 1, - job_id: - currV2VState.inferenceTokens[ - currV2VState.inferenceTokens.length - 1 - ], - job_type: InferenceJobType.VoiceConversion, - job_status: JobState.PENDING, - }); - return { - ...curr, - v2vState: { - ...currV2VState, - hasEnqueued: currV2VState.hasEnqueued + 1, - }, - }; - } - return curr; //case of no new jobs, do nothing - }); - }, [state.v2vState]); - - useSignalEffect(() => { - // when inference changes, check if there's a new audio to refresh for - let hasNewCompletedJob = false; - inferenceJobs.value.forEach((job) => { - if ( - job.job_status === JobState.COMPLETE_SUCCESS && - (job.job_type === InferenceJobType.TextToSpeech || - job.job_type === InferenceJobType.VoiceConversion) - ) { - const foundItemOfJob = audioItemsFromServer.value.find((item) => { - return item.media_id === job.result.entity_token; - }); - hasNewCompletedJob = foundItemOfJob === undefined; - } - }); - if (hasNewCompletedJob) { - handleListAudioByUser(); - } - }); + }, [state]); const changePage = useCallback((newPage: AudioTabPages) => { setState((curr) => ({ @@ -212,9 +59,9 @@ export const AudioTab = () => { switch (state.page) { case AudioTabPages.LIBRARY: { return ( - ); } diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/inferenceElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/inferenceElement.tsx index 87ee7bd979..b4aa3599d7 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/inferenceElement.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/inferenceElement.tsx @@ -6,33 +6,33 @@ import { } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { InferenceJob } from "~/pages/PageEnigma/models"; +import { JobStatus } from "~/enums"; +import { Job } from "~/models"; import { ButtonIcon, H5 } from "~/components"; -import { JobState } from "~/pages/PageEnigma/enums"; -export const InferenceElement = ({ job }: { job: InferenceJob }) => { +export const InferenceElement = ({ job }: { job: Job }) => { const className = twMerge( "rounded-md w-full flex justify-between items-center p-2 gap-2", - job.job_status === JobState.PENDING ? "bg-inference-pending" : "", - job.job_status === JobState.STARTED ? "bg-inference-generating" : "", - job.job_status === JobState.DEAD ? "bg-inference-error" : "", + job.status.status === JobStatus.PENDING ? "bg-inference-pending" : "", + job.status.status === JobStatus.STARTED ? "bg-inference-generating" : "", + job.status.status === JobStatus.DEAD ? "bg-inference-error" : "", ); const statusText = "".concat( - job.job_status === JobState.PENDING ? "Pending..." : "", - job.job_status === JobState.STARTED ? "Generating..." : "", - job.job_status === JobState.DEAD ? "Error" : "", + job.status.status === JobStatus.PENDING ? "Pending..." : "", + job.status.status === JobStatus.STARTED ? "Generating..." : "", + job.status.status === JobStatus.DEAD ? "Error" : "", ); return (
- {job.job_status === JobState.DEAD && ( + {job.status.status === JobStatus.DEAD && ( )} - {job.job_status !== JobState.DEAD && ( + {job.status.status !== JobStatus.DEAD && ( )}
{statusText}
- {job.job_status === JobState.DEAD && ( + {job.status.status === JobStatus.DEAD && ( { diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageLibrary.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioLibrary.tsx similarity index 81% rename from app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageLibrary.tsx rename to app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioLibrary.tsx index 433939cfba..dfa181f11e 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageLibrary.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageAudioLibrary.tsx @@ -3,8 +3,6 @@ import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; import { twMerge } from "tailwind-merge"; import { useSignals, useComputed } from "@preact/signals-react/runtime"; import { audioFilter, audioItems } from "~/pages/PageEnigma/signals"; -import { audioItemsFromServer } from "~/pages/PageEnigma/signals/mediaFromServer"; -import { inferenceJobs } from "~/pages/PageEnigma/signals/inferenceJobs"; import { Button, Pagination, UploadAudioButtonDialogue } from "~/components"; @@ -12,13 +10,11 @@ import { AudioItemElements } from "./audioItemElements"; import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; import { InferenceElement } from "./inferenceElement"; import { AssetFilterOption } from "~/enums"; -import { - AudioTabPages, - InferenceJobType, - JobState, -} from "~/pages/PageEnigma/enums"; +import { AudioTabPages } from "~/pages/PageEnigma/enums"; -export const PageLibrary = ({ +import { activeAudioJobs, userAudioItems } from "~/signals"; + +export const PageAudioLibrary = ({ changePage, reloadLibrary, }: { @@ -26,9 +22,10 @@ export const PageLibrary = ({ reloadLibrary: () => void; }) => { useSignals(); + const loadUserAudioItems = userAudioItems.value ? userAudioItems.value : []; const allAudioItems = useComputed(() => [ ...audioItems.value, - ...audioItemsFromServer.value, + ...loadUserAudioItems, ]); const displayedItems = allAudioItems.value.filter((item) => { if (audioFilter.value === AssetFilterOption.ALL) { @@ -39,17 +36,7 @@ export const PageLibrary = ({ } return item.isBookmarked; }); - const audioInferenceJobs = useComputed(() => - inferenceJobs.value.filter((job) => { - if ( - job.job_status !== JobState.COMPLETE_SUCCESS && - (job.job_type === InferenceJobType.TextToSpeech || - job.job_type === InferenceJobType.VoiceConversion) - ) { - return job; - } - }), - ); + const pageSize = 20; const totalPages = Math.ceil(allAudioItems.value.length / pageSize); const [currentPage, setCurrentPage] = useState(0); @@ -109,10 +96,10 @@ export const PageLibrary = ({
- {audioInferenceJobs.value.length > 0 && ( + {activeAudioJobs.value && activeAudioJobs.value.length > 0 && (
- {audioInferenceJobs.value.map((job) => { - return ; + {activeAudioJobs.value.map((job) => { + return ; })}
)} diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageTTS.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageTTS.tsx index 5c041ac421..fbf586da13 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageTTS.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageTTS.tsx @@ -11,6 +11,7 @@ import { GenerateTtsAudioResponse } from "~/pages/PageEnigma/models/tts"; import { GenerateTtsAudio } from "./utilities"; import { TtsState } from "../../../../models/voice"; import { AudioTabPages } from "~/pages/PageEnigma/enums"; +import { startPollingActiveJobs } from "~/signals"; export const PageTTS = ({ changePage, @@ -33,21 +34,14 @@ export const PageTTS = ({ GenerateTtsAudio(request).then((res: GenerateTtsAudioResponse) => { if (res && res.inference_job_token) { - setTtsState({ - ...ttsState, - inferenceTokens: [ - ...ttsState.inferenceTokens, - res.inference_job_token, - ], - }); - + startPollingActiveJobs(); changePage(AudioTabPages.LIBRARY); } }); } else { console.log("no voice model selected"); } - }, [ttsState, changePage, setTtsState]); + }, [ttsState, changePage]); const handleTextInput = (e: React.ChangeEvent) => { setTtsState({ diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageVoiceToVoice.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageVoiceToVoice.tsx index 49e049b6ad..9e6937b57f 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageVoiceToVoice.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/pageVoiceToVoice.tsx @@ -13,6 +13,7 @@ import { GenerateVoiceConversion } from "./utilities"; import { H4, H6, Button, Label, UploadAudioComponent } from "~/components"; import { AudioTabPages } from "~/pages/PageEnigma/enums"; +import { startPollingActiveJobs } from "~/signals"; export const PageVoicetoVoice = ({ changePage, @@ -35,19 +36,14 @@ export const PageVoicetoVoice = ({ GenerateVoiceConversion(request).then( (res: EnqueueVoiceConversionResponse) => { if (res && res.inference_job_token) { - setV2VState({ - ...v2vState, - inferenceTokens: [ - ...v2vState.inferenceTokens, - res.inference_job_token, - ], - }); + startPollingActiveJobs(); changePage(AudioTabPages.LIBRARY); } }, ); } - }, [v2vState, changePage, setV2VState]); + }, [v2vState, changePage]); + return (
diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/values.ts b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/values.ts index 6bfb4b800e..fe941a64b9 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/values.ts +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab/values.ts @@ -3,14 +3,10 @@ import { TtsState, V2VState } from "../../../../models/voice"; export const initialTtsState: TtsState = { voice: undefined, text: "", - hasEnqueued: 0, - inferenceTokens: [], }; export const initialV2VState: V2VState = { voice: undefined, file: undefined, inputFileToken: undefined, - hasEnqueued: 0, - inferenceTokens: [], }; diff --git a/app/pages/PageEnigma/enums/InferenceJobType.ts b/app/pages/PageEnigma/enums/InferenceJobType.ts deleted file mode 100644 index 08415541ef..0000000000 --- a/app/pages/PageEnigma/enums/InferenceJobType.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum InferenceJobType { - FaceAnimation, - TextToSpeech, - VoiceConversion, - VoiceDesignerCreateVoice, - VoiceDesignerTts, - ImageGeneration, - VideoMotionCapture, - ConvertFbxtoGltf, - EngineComposition, - VideoWorkflow, - VideoStyleTransfer, -} diff --git a/app/pages/PageEnigma/enums/index.ts b/app/pages/PageEnigma/enums/index.ts index 6b6c2f1b1e..6b6c3f738d 100644 --- a/app/pages/PageEnigma/enums/index.ts +++ b/app/pages/PageEnigma/enums/index.ts @@ -5,7 +5,6 @@ export * from "./ClipType"; export * from "./EditorState"; export * from "./FetchStatus"; export * from "./GenerateTtsAudioErrorType"; -export * from "./InferenceJobType"; export * from "./JobState"; export * from "./MediaFileClass"; export * from "./MediaFileType"; diff --git a/app/pages/PageEnigma/models/voice.ts b/app/pages/PageEnigma/models/voice.ts index 247695fced..029244b643 100644 --- a/app/pages/PageEnigma/models/voice.ts +++ b/app/pages/PageEnigma/models/voice.ts @@ -12,14 +12,10 @@ export type AudioPanelState = { export type TtsState = { voice: TtsModelListItem | undefined; text: string; - hasEnqueued: number; - inferenceTokens: string[]; }; export type V2VState = { voice: VoiceConversionModelListItem | undefined; file: File | undefined; inputFileToken: string | undefined; - hasEnqueued: number; - inferenceTokens: string[]; }; diff --git a/app/pages/PageEnigma/signals/inferenceJobs.ts b/app/pages/PageEnigma/signals/inferenceJobs.ts deleted file mode 100644 index a6e2ae9b07..0000000000 --- a/app/pages/PageEnigma/signals/inferenceJobs.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { signal } from "@preact/signals-core"; - -import { InferenceJob } from "../models"; - -export const inferenceJobs = signal([]); - -export function addInferenceJob(newJob: InferenceJob) { - const existingInferenceJobs = inferenceJobs.value; - const jobExist = existingInferenceJobs.find( - (job) => job.job_id == newJob.job_id, - ); - if (!jobExist) { - inferenceJobs.value = [...existingInferenceJobs, newJob]; - } - // else do nothing -} - -export function updateInferenceJob(updatableJob: InferenceJob) { - const existingInferenceJobs = inferenceJobs.value; - existingInferenceJobs.forEach((job) => { - if (job.job_id === updatableJob.job_id) { - job.job_status = updatableJob.job_status; - } - if (updatableJob.result) { - job.result = updatableJob.result; - } - }); - inferenceJobs.value = [...existingInferenceJobs]; -} - -export function deleteInferenceJob(deletableJob: InferenceJob) { - // console.log("delete"); - const newList: InferenceJob[] = []; - inferenceJobs.value.forEach((job) => { - if (job.job_id !== deletableJob.job_id) { - newList.push(job); - } - // or else do nothing - }); - // console.log(newList); - inferenceJobs.value = [...newList]; -} diff --git a/app/pages/PageEnigma/signals/myMovies.ts b/app/pages/PageEnigma/signals/myMovies.ts index 61e8a67091..eece573172 100644 --- a/app/pages/PageEnigma/signals/myMovies.ts +++ b/app/pages/PageEnigma/signals/myMovies.ts @@ -1,14 +1,4 @@ import { signal } from "@preact/signals-core"; -import { MediaInfo } from "~/pages/PageEnigma/models/movies"; - export const viewMyMovies = signal(false); export const generateMovieId = signal(""); - -export const myMovies = signal(undefined); - -export const setMyMovies = (newSet: MediaInfo[]) => { - myMovies.value = newSet; -}; - -export const shouldPollMyMovies = signal(true); diff --git a/app/signals/activeJobs.ts b/app/signals/activeJobs.ts index 8797752ee7..13609dc1d4 100644 --- a/app/signals/activeJobs.ts +++ b/app/signals/activeJobs.ts @@ -1,10 +1,11 @@ import { signal, computed } from "@preact/signals-core"; -import { JobStatus } from "~/enums"; +import { JobStatus, JobType } from "~/enums"; +import { PollRecentJobs } from "~/hooks/useActiveJobs/utilities"; import { Job } from "~/models"; import { isJobStatusTerminal } from "~/utilities"; -export const recentJobs = signal([]); +export const recentJobs = signal(undefined); export const setJobs = (newJobList: Job[]) => { recentJobs.value = [...newJobList]; }; @@ -24,9 +25,22 @@ export const activeWorkflowJobs = computed(() => { return undefined; } return activeJobs.value.filter((job) => { - return job.request.inference_category === "workflow"; + return job.request.inference_category === JobType.VideoStyleTransfer; }); }); + +export const activeAudioJobs = computed(() => { + if (!activeJobs.value) { + return undefined; + } + return activeJobs.value.filter((job) => { + return ( + job.request.inference_category === JobType.TextToSpeech || + job.request.inference_category === JobType.VoiceConversion + ); + }); +}); + export const completedJobs = computed(() => { if (!recentJobs.value) { return undefined; @@ -36,13 +50,13 @@ export const completedJobs = computed(() => { }); }); -export const shouldPollActiveJobs = signal(true); +export const shouldPollActiveJobs = signal(false); export const startPollingActiveJobs = () => { - console.info("start polling Active Jobs"); + PollRecentJobs(); //poll once regardless of polling intervals shouldPollActiveJobs.value = true; }; + export const stopPollingActiveJobs = () => { - console.info("stop polling Active Jobs"); shouldPollActiveJobs.value = false; }; diff --git a/app/signals/backgroundLoadedMedia.ts b/app/signals/backgroundLoadedMedia.ts new file mode 100644 index 0000000000..d24acf8a7b --- /dev/null +++ b/app/signals/backgroundLoadedMedia.ts @@ -0,0 +1,92 @@ +import { signal } from "@preact/signals-core"; +import { AssetType } from "~/enums"; +import { MediaInfo } from "~/pages/PageEnigma/models/movies"; +import { AudioMediaItem } from "~/pages/PageEnigma/models"; + +// Signal for background loading User's Movies +export const userMovies = signal(undefined); + +export const setUserMovies = (newSet: MediaInfo[]) => { + userMovies.value = newSet; +}; + +//Signal for background loading User's Audio Items. +export const userAudioItems = signal(undefined); +export const setUserAudioItems = (newSet: MediaInfo[]) => { + const previousItems = userAudioItems.value ? [...userAudioItems.value] : []; + const morphedNewSet = newSet.map((item) => { + const morphedItem: AudioMediaItem = { + version: 1, + type: AssetType.AUDIO, + category: getCategory(item), + media_id: item.token, + object_uuid: item.token, + name: getTitle(item), + description: item.maybe_text_transcript || "", + publicBucketPath: item.public_bucket_path, + length: getLength(item), + thumbnail: "/resources/placeholders/audio_placeholder.png", + isMine: true, + isNew: + previousItems.length > 0 + ? checkIsNew(previousItems, item.token) + : false, + // isBookmarked?: boolean; + }; + return morphedItem; + }); + userAudioItems.value = morphedNewSet; +}; + +export const cancelNewFromAudioItem = (mediaId: string) => { + if (!userAudioItems.value || userAudioItems.value.length === 0) { + return; + } + const newList = userAudioItems.value.map((item) => { + if (item.media_id === mediaId) item.isNew = false; + return item; + }); + userAudioItems.value = [...newList]; +}; + +export const updateAudioItemLength = (mediaId: string, duration: number) => { + if (!userAudioItems.value || userAudioItems.value.length === 0) { + return; + } + const newList = userAudioItems.value.map((item) => { + if (item.media_id === mediaId) item.length = duration; + return item; + }); + userAudioItems.value = [...newList]; +}; + +// Helper functions to massage Media Info to AudioMediaItem +// that's usable as a signal. +function getTitle(item: MediaInfo) { + // console.log(item); + if (item.maybe_title) return item.maybe_title; + if (item.origin && item.origin.maybe_model && item.origin.maybe_model.title) + return item.origin.maybe_model.title; + return "Media Audio"; +} +function getCategory(item: MediaInfo) { + if ( + item.origin && + item.origin.product_category && + item.origin.product_category !== "unknown" + ) + return item.origin.product_category; + if (item.origin_category) return item.origin_category; + return "unknown"; +} +function checkIsNew(previousItems: AudioMediaItem[], token: string) { + const foundItem = previousItems.find((item) => { + return token === item.media_id && item.isNew !== true; + }); + return foundItem === undefined; +} +function getLength(item: MediaInfo) { + return item.maybe_duration_millis + ? (item.maybe_duration_millis / 1000) * 60 + : undefined; +} diff --git a/app/signals/index.ts b/app/signals/index.ts index 3765e96bfc..7d0ef283ed 100644 --- a/app/signals/index.ts +++ b/app/signals/index.ts @@ -1,5 +1,6 @@ export * from "./authentication"; export * from "./activeJobs"; +export * from "./backgroundLoadedMedia"; export * from "./global"; export * from "./loadingBar"; export * from "./scene"; // THIS IS SHOULE BE PAGE LEVEL