diff --git a/app/api.tsx b/app/api.tsx new file mode 100644 index 0000000000..0e0bcc77a1 --- /dev/null +++ b/app/api.tsx @@ -0,0 +1,14 @@ +export const apiHost = 'https://api.fakeyou.com'; + +// Authentications +// const createAccount = `${apiHost}}/create_account`; +export const login = `${apiHost}/v1/login`; +export const getSession = `${apiHost}/v1/session`; +export const logout = `${apiHost}/v1/logout`; + +// TTS +export const inferTts = `${apiHost}/tts/inference`; +export const listTts = `${apiHost}/tts/list`; + +// User Media +export const listMediaByUser = (username:string) => `${apiHost}/v1/media_files/list/user/${username}`; \ No newline at end of file diff --git a/app/components/ButtonIcon.tsx b/app/components/ButtonIcon.tsx index 2db55e172f..124ca12943 100644 --- a/app/components/ButtonIcon.tsx +++ b/app/components/ButtonIcon.tsx @@ -1,28 +1,32 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; -interface ButtonIconProps { - icon: IconDefinition; - fill?: boolean; +interface ButtonIconProps extends FontAwesomeIconProps { + // icon: IconDefinition; onClick: () => void; + className?: string; + hoverFill?: boolean; } export const ButtonIcon = ({ icon, + size, onClick, - fill = false, + className : propsClassName, + hoverFill = false, }: ButtonIconProps) => { const className = twMerge( "flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150", - fill + hoverFill ? "bg-ui-controls-button hover:bg-ui-controls-button/[0.75]" : "bg-transparent hover:bg-ui-panel/[0.4]", + propsClassName, ); return ( ); }; diff --git a/app/components/ListDropdown.tsx b/app/components/ListDropdown.tsx new file mode 100644 index 0000000000..aeb04875fc --- /dev/null +++ b/app/components/ListDropdown.tsx @@ -0,0 +1,69 @@ +import { Fragment, useEffect, useState } from 'react' +import { Listbox, Transition } from '@headlessui/react' +import { faCheck, faChevronDown } from '@fortawesome/pro-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + + +interface ListDropdownProps { + list: {[key: string] : string}[]; + onSelect: (val:string)=>void; +} +export const ListDropdown = ({ + list, onSelect +}:ListDropdownProps) => { + const [selected, setSelected] = useState(list[0]); + + useEffect(()=>{ + onSelect(Object.values(selected)[0]); + }, [selected]); + + return ( + +
+ + {selected.name} + + + + + + {list.map((item, itemIdx) => ( + + `relative cursor-pointer select-none py-2 pl-10 pr-4 text-white ${ + active ? 'text-white' : 'text-gray-400' + }` + } + value={item} + > + {({ selected }) => ( + <> + + {Object.values(item)[0]} + + {selected ? ( + + + ) : null} + + )} + + ))} + + +
+
+ ) +} \ No newline at end of file diff --git a/app/components/ListSearchDropdown.tsx b/app/components/ListSearchDropdown.tsx new file mode 100644 index 0000000000..cd0dd6dffd --- /dev/null +++ b/app/components/ListSearchDropdown.tsx @@ -0,0 +1,110 @@ +import { Fragment, useState, useEffect } from 'react' +import { Combobox, Transition } from '@headlessui/react' +import { faCheck, faChevronDown } from '@fortawesome/pro-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +interface ListDropdownProps { + list: {[key: string] : string}[]; + listDisplayKey?: string; + onSelect: (val:string)=>void +} +export const ListSearchDropdown = ({ + list, listDisplayKey, onSelect +}:ListDropdownProps) => { + const [selected, setSelected] = useState(list[0]) + const [query, setQuery] = useState('') + + const filteredList = + query === '' + ? list + : list.filter((item) =>{ + if(listDisplayKey) + return item[listDisplayKey] + .toLowerCase() + .replace(/\s+/g, '') + .includes(query.toLowerCase().replace(/\s+/g, '')); + + return Object.values(item)[0] + .toLowerCase() + .replace(/\s+/g, '') + .includes(query.toLowerCase().replace(/\s+/g, '')); + }) + + useEffect(()=>{ + if (listDisplayKey) onSelect(selected[listDisplayKey]); + else onSelect(Object.values(selected)[0]); + }, [selected]); + + return ( + +
+
+ { + if(listDisplayKey) return item[listDisplayKey]; + else return Object.values(item)[0] + }} + onChange={(event) => setQuery(event.target.value)} + /> + + +
+ setQuery('')} + > + + {filteredList.length === 0 && query !== '' ? ( +
+ Nothing found. +
+ ) : ( + filteredList.map((item, itemIdx) => { + if(itemIdx <= 10) + return ( + + `relative cursor-pointer select-none py-2 pl-10 pr-4 ${ + active ? 'text-white' : 'text-gray-400' + }` + } + value={item} + > + {({ selected, active }) => ( + <> + + {!listDisplayKey && Object.values(item)[0]} + {listDisplayKey && item[listDisplayKey]} + + {selected ? ( + + + ) : null} + + )} + + ); + return null; + }) + )} +
+
+
+
+ ) +} diff --git a/app/components/TransitionDialogue.tsx b/app/components/TransitionDialogue.tsx new file mode 100644 index 0000000000..73003bd2d4 --- /dev/null +++ b/app/components/TransitionDialogue.tsx @@ -0,0 +1,57 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { Fragment, ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; +export const TransitionDialogue = ({ + isOpen, title, onClose, className, children, +}:{ + isOpen: boolean; + title?: ReactNode; + onClose:()=>void; + className?: string; + children: ReactNode; + +})=>{ + return( + + + +
+ + +
+
+ + + {title && ( + + {title} + + )} + {children} + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/app/components/index.tsx b/app/components/index.tsx index 8b3be492ec..28dfc10661 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -7,6 +7,9 @@ import { InputVector } from "./InputVector"; import { H1, H2, H3, H4, H5, H6, P, Label, Link} from "./Typography"; import { ItemPicker } from "./ItemPicker"; import { Textarea } from "./Textarea"; +import { TransitionDialogue } from "./TransitionDialogue"; +import { ListDropdown } from "./ListDropdown" +import { ListSearchDropdown } from "./ListSearchDropdown" import { LoadingBar } from "./LoadingBar"; import { LoadingDots, LoadingDotsBricks, LoadingDotsTyping } from "./LoadingDots"; @@ -18,8 +21,11 @@ export { H1,H2,H3,H4,H5,H6,P,Label,Link, ItemPicker, Textarea, + TransitionDialogue, Input, InputVector, + ListDropdown, + ListSearchDropdown, LoadingBar, LoadingDots, LoadingDotsBricks, diff --git a/app/contexts/Authentication/Authentication.tsx b/app/contexts/Authentication/Authentication.tsx index 13681a75be..2d294007e1 100644 --- a/app/contexts/Authentication/Authentication.tsx +++ b/app/contexts/Authentication/Authentication.tsx @@ -32,7 +32,7 @@ export const AuthenticationContext = createContext<{ export const AuthenticationProvider = ({children}:{children:ReactNode})=>{ // console.log('auth provider rerender'); - const [authCookies, setAuthCookie, removeAuthCookie] = useCookies([STORAGE_KEYS.USER_INFO]); + const [, setAuthCookie, removeAuthCookie] = useCookies([STORAGE_KEYS.USER_INFO]); const [authState, setAuthState] = useState({}); const loginAndGetUserInfo = useCallback(( diff --git a/app/contexts/Authentication/utilities.tsx b/app/contexts/Authentication/utilities.tsx index ccd7915e30..0cc54b8f46 100644 --- a/app/contexts/Authentication/utilities.tsx +++ b/app/contexts/Authentication/utilities.tsx @@ -1,14 +1,8 @@ - +import { login, getSession, logout } from '~/api'; import { SessionResponse, } from "./types"; -const apiHost = "https://api.fakeyou.com"; -// const createAccount = `${apiHost}}/create_account`; -const login = `${apiHost}/v1/login`; -const getSession = `${apiHost}/v1/session`; -const logout = `${apiHost}/v1/logout`; - export const CreateSession = async ({ usernameOrEmail, password }:{ diff --git a/app/hooks/index.tsx b/app/hooks/index.tsx new file mode 100644 index 0000000000..5cc28acb16 --- /dev/null +++ b/app/hooks/index.tsx @@ -0,0 +1 @@ +export { useInferenceJobManager } from "./useInferenceJobManager"; \ No newline at end of file diff --git a/app/hooks/useInferenceJobManager/index.tsx b/app/hooks/useInferenceJobManager/index.tsx new file mode 100644 index 0000000000..e048c44906 --- /dev/null +++ b/app/hooks/useInferenceJobManager/index.tsx @@ -0,0 +1 @@ +export { useInferenceJobManager } from './useInferenceJobManager'; diff --git a/app/hooks/useInferenceJobManager/useInferenceJobManager.tsx b/app/hooks/useInferenceJobManager/useInferenceJobManager.tsx new file mode 100644 index 0000000000..89c5206542 --- /dev/null +++ b/app/hooks/useInferenceJobManager/useInferenceJobManager.tsx @@ -0,0 +1,122 @@ +import { useEffect, useCallback } from "react"; +import { useSignals } from "@preact/signals-react/runtime"; +import { inferenceJobs, updateInferenceJob } from '~/pages/PageEnigma/store/inferenceJobs'; + +export enum JobState { + UNKNOWN = "unknown", // Only on frontend. + PENDING = "pending", + STARTED = "started", + COMPLETE_SUCCESS = "complete_success", + COMPLETE_FAILURE = "complete_failure", + ATTEMPT_FAILED = "attempt_failed", + DEAD = "dead", + CANCELED_BY_USER = "canceled_by_user", +} +export interface RequestDetails { + inference_category: string, + maybe_model_type?: string, + maybe_model_token?: string, + maybe_model_title?: string, + maybe_raw_inference_text?: string, +} + +export interface StatusDetails { + status: string, + maybe_extra_status_description?: string, + maybe_failure_category?: string, + attempt_count: number, +} + +export interface ResultDetails { + entity_type: string, + entity_token: string, + maybe_public_bucket_media_path?: string, +} + +export interface JobStatus { + job_token: string, + request: RequestDetails, + status: StatusDetails, + maybe_result?: ResultDetails, + created_at: Date, + updated_at: Date, +} + +export interface GetJobStatusResponse { + success: boolean; + state: JobStatus +} +type ErrorResponse = { + success: boolean; + error_reason: string; +} +export async function GetInferenceJobStatus(jobToken:string) : Promise { + + const endpoint = `https://api.fakeyou.com/v1/model_inference/job_status/${jobToken}`; + + return await fetch(endpoint, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include', + }) + .then(res => res.json()) + .then(res => { + const response : GetJobStatusResponse = res; + return response; + }) + .catch(e => { + return null; + }); +} + +function shouldKeepPolling(currStatus:string){ + if(currStatus === JobState.PENDING + || currStatus === JobState.STARTED + ) return true; + return false +} + +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|null)=>{ + if(res!==null){ + if(res.state.status.status !== job.job_status){ + console.log(`${res.state.job_token} has new state: ${res.state.status.status}`) + if(res.state.status.status === JobState.COMPLETE_SUCCESS){ + console.log(res.state); + updateInferenceJob({ + ...job, + job_status: res.state.status.status, + result: res.state.maybe_result + }); + }else{ + updateInferenceJob({ + ...job, + job_status: res.state.status.status + }); + } + } + } + }); + } + }); + } + }, []); + + useEffect(()=>{ + console.log('useInferenceJobManager Mounted'); + const intervalTimer = setInterval(pollInferenceJobs,2000); + return()=>{ + console.log('useInferenceJobManager Dismounted'); + clearInterval(intervalTimer); + } + },[]); +}; + diff --git a/app/modules/ButtonDialogue/ButtonDialogue.tsx b/app/modules/ButtonDialogue/ButtonDialogue.tsx index 2cf1ceb539..e83bf0c149 100644 --- a/app/modules/ButtonDialogue/ButtonDialogue.tsx +++ b/app/modules/ButtonDialogue/ButtonDialogue.tsx @@ -1,6 +1,5 @@ -import { Dialog, Transition } from "@headlessui/react"; -import { Fragment, useState } from "react"; - +import { useState } from "react"; +import { TransitionDialogue } from "~/components/TransitionDialogue"; import { Button, ButtonPropsI } from "~/components/Button"; type UnionedButtonProps = { label?: string } & ButtonPropsI; @@ -9,11 +8,15 @@ interface ButtonDialoguePropsI { buttonProps?: UnionedButtonProps; confirmButtonProps?: UnionedButtonProps; closeButtonProps?: UnionedButtonProps; + dialogProps?:{ + className?: string; + }; title?: string; children: React.ReactNode; } export const ButtonDialogue = ({ + dialogProps = {}, buttonProps: unionedButtonProps, confirmButtonProps, closeButtonProps: unionedCloseButtonProps, @@ -36,74 +39,41 @@ export const ButtonDialogue = ({ {buttonLabel} - - - +
{children}
+ +
+ - {confirmButtonProps && ( - - )} -
- -
- - -
-
+ {closeButtonLabel} + + {confirmButtonProps && ( + + )} + + ); }; diff --git a/app/modules/DialogueInference/DialogueInference.tsx b/app/modules/DialogueInference/DialogueInference.tsx new file mode 100644 index 0000000000..1439dd4d37 --- /dev/null +++ b/app/modules/DialogueInference/DialogueInference.tsx @@ -0,0 +1,141 @@ +import { useSignals } from "@preact/signals-react/runtime"; +import { + faClipboard, + faClipboardList, + faCircleCheck, + faCircleExclamation, + faLoader, + faSpinner, + faSquareXmark, +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { deleteInferenceJob, inferenceJobs } from "~/pages/PageEnigma/store/inferenceJobs"; +import { InferenceJob } from "~/pages/PageEnigma/models"; + +import { ButtonDialogue } from "../ButtonDialogue"; +import { ButtonIcon, H4,H6,P } from "~/components"; +import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager"; + +const printJobType = (jobType : string)=>{ + switch (jobType){ + case "generic": + return "Text to Speech"; + default: + return "Unknown"; + } +}; + +const printJobStatus = (jobStatus : string)=>{ + switch (jobStatus){ + case JobState.PENDING: + return "Adding to the queue."; + case JobState.STARTED: + return "Processing, please wait..."; + case JobState.COMPLETE_SUCCESS: + return "Completed! See Audio Panel for the result. file." + default: + return "Unknown Status"; + } +}; + +const JobStatusIcon = ({jobStatus} : {jobStatus:string})=>{ + switch (jobStatus){ + case JobState.PENDING:{ + return( + + ); + } + case JobState.STARTED:{ + return( + + ); + } + case JobState.COMPLETE_SUCCESS:{ + return( + + ); + } + default:{ + return( + + ); + } + } +}; + +const InferenceItem = ({job}:{job:InferenceJob}) => { + // console.log(job); + return( +
  • + +
    +

    {printJobType(job.job_type)}

    +
    {printJobStatus(job.job_status)}
    +
    + {job.job_status === JobState.COMPLETE_SUCCESS && +
    + { + deleteInferenceJob(job); + }} + /> +
    + } +
  • + ); +}; + +export const DialogueInference = () =>{ + useSignals(); + + const buttonIcon = inferenceJobs.value.length > 0 + ? faClipboardList : faClipboard; + return( + + {inferenceJobs.value.length > 0 && +
      + {inferenceJobs.value.map(job=> + + )} +
    + } + { + inferenceJobs.value.length === 0 && +

    You currently have no running proccesses.

    + } +
    + ); +} + diff --git a/app/modules/DialogueInference/index.tsx b/app/modules/DialogueInference/index.tsx new file mode 100644 index 0000000000..df44438efa --- /dev/null +++ b/app/modules/DialogueInference/index.tsx @@ -0,0 +1 @@ +export { DialogueInference } from './DialogueInference' \ No newline at end of file diff --git a/app/modules/TopBar/AuthButtons.tsx b/app/modules/TopBar/AuthButtons.tsx index 972cc9e2df..1f7474667a 100644 --- a/app/modules/TopBar/AuthButtons.tsx +++ b/app/modules/TopBar/AuthButtons.tsx @@ -1,16 +1,24 @@ import { useContext, } from "react"; +import { useSignals } from "@preact/signals-react/runtime"; import { AuthenticationContext } from "~/contexts/Authentication"; import { Button, ButtonLink } from "~/components"; +import { DialogueInference } from "../DialogueInference"; +import { inferenceJobs } from "~/pages/PageEnigma/store/inferenceJobs"; export const AuthButtons = ()=>{ + useSignals(); + const {authState, logout} = useContext(AuthenticationContext); const handleLogout = ()=>{ if(logout)logout(); } if ( authState && authState.isLoggedIn ) { return( - + <> + + + ); }else{ return( diff --git a/app/modules/withProtectedRoute/index.tsx b/app/modules/withProtectedRoute/index.tsx index 3bf606ffc3..1278e11b6f 100644 --- a/app/modules/withProtectedRoute/index.tsx +++ b/app/modules/withProtectedRoute/index.tsx @@ -4,7 +4,6 @@ import { AuthenticationContext } from "~/contexts/Authentication"; export const withProtectionRoute =

    (Component: ComponentType

    ) => (rest:P) => { const { authState } = useContext(AuthenticationContext); - // console.log(authState); if (authState.isLoggedIn === undefined ) { return <>waiting; } diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 613becceb5..6b9038c970 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -3,6 +3,8 @@ import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackPro import { AppUIProvider } from "~/pages/PageEnigma/contexts/AppUiContext"; import { EngineProvider } from "~/pages/PageEnigma/contexts/EngineProvider"; import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent"; +import { useInferenceJobManager } from "~/hooks"; + import { useEffect } from "react"; import { useSignals } from "@preact/signals-react/runtime"; import { @@ -14,6 +16,7 @@ import { export const PageEnigma = () => { useSignals(); + useInferenceJobManager(); useEffect(() => { function setPage() { // TODO address this issue with zooming diff --git a/app/pages/PageEnigma/PageEnigmaComponent.tsx b/app/pages/PageEnigma/PageEnigmaComponent.tsx index e635b36557..ef57ce95cd 100644 --- a/app/pages/PageEnigma/PageEnigmaComponent.tsx +++ b/app/pages/PageEnigma/PageEnigmaComponent.tsx @@ -9,6 +9,7 @@ import { Controls3D } from "./comps/Controls3D"; import { ControlsTopButtons } from "./comps/ControlsTopButtons"; import { ControlsVideo } from "./comps/ControlsVideo"; import { ControlPanelSceneObject } from "./comps/ControlPanelSceneObject"; +import { DialogueTTS } from "./comps/DialogueTTS/DialogueTTS"; import { PreviewEngineCamera } from "./comps/PreviewEngineCamera"; import { ViewSideBySide } from "./comps/ViewSideBySide"; import { Timeline } from "./comps/Timeline"; @@ -113,6 +114,7 @@ export const PageEnigmaComponent = () => { + {appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && ( diff --git a/app/pages/PageEnigma/Queue/Queue.ts b/app/pages/PageEnigma/Queue/Queue.ts index 2fbfcd789e..5b989dbdff 100644 --- a/app/pages/PageEnigma/Queue/Queue.ts +++ b/app/pages/PageEnigma/Queue/Queue.ts @@ -1,26 +1,35 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; + import { QueueClip, QueueKeyframe, UpdateTime, } from "~/pages/PageEnigma/models"; import { toTimelineActions } from "./toTimelineActions"; + +type UnionedActionTypes = fromEngineActions | toEngineActions | toTimelineActions; +type UnionedDataTypes = QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem; + +export type QueueSubscribeType = { + action: UnionedActionTypes; + data: UnionedDataTypes; +} import { ClipUI } from "../datastructures/clips/clip_ui"; import { MediaItem } from "~/pages/PageEnigma/models"; class Queue { private _queue: Record< string, { - action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem; + action: UnionedActionTypes; + data: UnionedDataTypes; }[] > = {}; private _subscribers: Record< string, (entry: { - action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem; + action: UnionedActionTypes; + data: UnionedDataTypes; }) => void > = {}; @@ -30,8 +39,8 @@ class Queue { data, }: { queueName: string; - action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem ; + action: UnionedActionTypes; + data: UnionedDataTypes ; }) { if (!this._queue[queueName]) { this._queue[queueName] = []; @@ -46,10 +55,7 @@ class Queue { public subscribe( queueName: string, - onMessage: (entry: { - action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem; - }) => void, + onMessage: (entry: QueueSubscribeType) => void, ) { this._subscribers[queueName] = onMessage; while (this._queue[queueName]?.length) { diff --git a/app/pages/PageEnigma/Queue/QueueNames.ts b/app/pages/PageEnigma/Queue/QueueNames.ts index 9a046d7c85..813a726847 100644 --- a/app/pages/PageEnigma/Queue/QueueNames.ts +++ b/app/pages/PageEnigma/Queue/QueueNames.ts @@ -1,5 +1,5 @@ export enum QueueNames { FROM_ENGINE = "FROM_ENGINE", TO_ENGINE = "TO_ENGINE", - TO_TIMELINE = "TO_TIMELINE" + TO_TIMELINE = "TO_TIMELINE", } diff --git a/app/pages/PageEnigma/Queue/toInferenceActions.ts b/app/pages/PageEnigma/Queue/toInferenceActions.ts new file mode 100644 index 0000000000..b3ed965a6b --- /dev/null +++ b/app/pages/PageEnigma/Queue/toInferenceActions.ts @@ -0,0 +1,3 @@ +export enum toInferenceActions { + ADD_TTS_JOB = "add_tts_job" +} diff --git a/app/pages/PageEnigma/comps/Controls3D/Controls3D.tsx b/app/pages/PageEnigma/comps/Controls3D/Controls3D.tsx index a5ad366e1f..fed4fa9829 100644 --- a/app/pages/PageEnigma/comps/Controls3D/Controls3D.tsx +++ b/app/pages/PageEnigma/comps/Controls3D/Controls3D.tsx @@ -66,7 +66,7 @@ export const Controls3D = () => {

    - + diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx index 84b3edf4d2..dcc148adcb 100644 --- a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx @@ -1,12 +1,15 @@ import { useContext, useState } from "react"; -import { Button, Input } from "~/components"; +import { Input } from "~/components"; import { ButtonDialogue } from "~/modules/ButtonDialogue"; import { EngineContext } from "../../contexts/EngineContext"; import { ToasterContext } from "~/pages/PageEnigma/contexts/ToasterContext"; -import { APPUI_ACTION_TYPES } from "~/pages/PageEnigma/reducers"; +// import { APPUI_ACTION_TYPES } from "~/pages/PageEnigma/reducers"; + +import { TestFeaturesButtons } from "./TestFeaturesButtons"; export const ControlsTopButtons = () => { const editorEngine = useContext(EngineContext); + const [sceneName, setSceneName] = useState(""); const [sceneToken, setSceneToken] = useState(""); const { addToast } = useContext(ToasterContext); @@ -33,25 +36,6 @@ export const ControlsTopButtons = () => { }); }; - const handleButtonTest = () => {}; - - const handleTestButton2 = () => { - console.log("Test Button 2"); - }; - - const handleButtonCameraView = () => { - editorEngine?.switchCameraView(); - }; - // const handleButtonPlayBack = () => { - // editorEngine?.startPlayback(); - // }; - const handleButtonTakeFrame = () => { - // editorEngine?.take_timeline_cam_clip(); - }; - const handleButtonSingleFrame = () => { - editorEngine?.generateFrame(); - }; - return (
    @@ -128,14 +112,7 @@ export const ControlsTopButtons = () => {

    Ask Wil about React

    - -
    - -
    -
    - {/* - */} -
    +
    ); }; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/TestFeaturesButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/TestFeaturesButtons.tsx new file mode 100644 index 0000000000..763a6b662a --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/TestFeaturesButtons.tsx @@ -0,0 +1,102 @@ +import { useContext } from "react"; +import { Button, } from "~/components"; +import { ClipGroup } from "~/pages/PageEnigma/models/track"; +import { APPUI_ACTION_TYPES } from "../../reducers"; +import { AppUiContext } from "../../contexts/AppUiContext"; +import { EngineContext } from "../../contexts/EngineContext"; + +export const TestFeaturesButtons = ({debug}:{debug:boolean})=>{ + if (!debug) return null; + + const [appUiState, dispatchAppUiState] = useContext(AppUiContext); + const editorEngine = useContext(EngineContext); + + const testObjectPanel = () => { + if (appUiState?.controlPanel.isShowing) { + dispatchAppUiState({ + type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT, + }); + } else { + dispatchAppUiState({ + type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT, + payload: { + group: ClipGroup.OBJECT, + object_name: "TEST BUTTON", + object_uuid: "", + version: "1", + objectVectors: { + position: { x: 1, y: 2, z: 3 }, + rotation: { x: 4, y: 5, z: 6 }, + scale: { x: 7, y: 8, z: 9 }, + }, + }, + }); + } + }; + + const testTTSPanel = () =>{ + dispatchAppUiState({ + type: APPUI_ACTION_TYPES.OPEN_DIALOGUE_TTS + }) + }; + + const handleButtonCameraView = () => { + editorEngine?.switchCameraView(); + }; + const testStylizeRequest = () => { + // editorEngine?.testStylizeRequest(); + console.log('editorEnging does not have testStylizeRequest') + }; + const handleButtonRender = () => { + editorEngine?.generateVideo(); + }; + + const handleButtonTakeFrame = () => { + // editorEngine?.take_timeline_cam_clip(); + console.log('editorEnging does not have take_timeline_cam_clip') + }; + + const handleButtonSingleFrame = () => { + editorEngine?.generateFrame(); + }; + const handleButtonPlayBack = () => { + editorEngine?.startPlayback(); + }; + + const smallButtons = "text-xs p-2 h-6 "; + const tertiaryColor = "bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary "; + + return( + <> +
    + + + +
    +
    + + + + +
    + + ) +} \ No newline at end of file diff --git a/app/pages/PageEnigma/comps/DialogueTTS/DialogueTTS.tsx b/app/pages/PageEnigma/comps/DialogueTTS/DialogueTTS.tsx new file mode 100644 index 0000000000..7d674e2882 --- /dev/null +++ b/app/pages/PageEnigma/comps/DialogueTTS/DialogueTTS.tsx @@ -0,0 +1,243 @@ +import { useCallback, useContext, useEffect, useState } from "react"; +import { useSignalEffect } from "@preact/signals-react/runtime"; +import { v4 as uuidv4 } from "uuid"; +import { + faVolume, + faShuffle, + faPlay, + faBrainCircuit, +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AppUiContext } from "../../contexts/AppUiContext"; +import { APPUI_ACTION_TYPES } from "../../reducers"; +import { + Button, + H2, + H5, + Label, + LoadingDotsTyping, + ListSearchDropdown, + TransitionDialogue, + Textarea +} from "~/components"; +import { ListTtsModels } from "./utilities"; +import { TtsModelListItem } from "./types"; +import { GenerateTtsAudio } from "./generateTts"; +import { addInferenceJob, inferenceJobs} from "../../store/inferenceJobs"; +import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager"; + +type TtsState = { + voice: TtsModelListItem | undefined; + text: string; + hasEnqueued :boolean; + inferenceToken?: string; + inferenceJobType?: string; + hasAudio: boolean; + audioFile?: any; +} + +export const DialogueTTS = ()=>{ + + const [appUiState, dispatchAppUiState] = useContext(AppUiContext); + const [ttsState, setTtsState] = useState({ + voice:undefined, + text:"", + hasEnqueued:false, + hasAudio:false, + }); + + useSignalEffect(()=>{ + console.log(inferenceJobs.value); + if(ttsState.hasEnqueued && ttsState.inferenceToken){ + const found = inferenceJobs.value.find((job)=>job.job_id===ttsState.inferenceToken); + console.log(`finding: ${ttsState.inferenceToken}`); + console.log(found); + if(found?.job_status === JobState.COMPLETE_SUCCESS){ + setTtsState((curr)=>({ + ...curr, + hasAudio: true, + audioFile: found.result + })) + } + } + }); + + const [ttsModels, setTtsModels] = useState>([]); + + const listModels = useCallback(async () => { + const ttsModelsLoaded = ttsModels.length > 0; + if (ttsModelsLoaded) { + return; // Already queried. + } + const models = await ListTtsModels(); + if (models) { + setTtsModels(models); + } + }, []); + + useEffect(() => { + listModels(); + }, [listModels]); + + useEffect(()=> { + if(ttsState.hasEnqueued && ttsState.inferenceToken && ttsState.inferenceJobType){ + console.log(`tts has Enqueued`); + addInferenceJob({ + version:1, + job_id: ttsState.inferenceToken, + job_type: ttsState.inferenceJobType, + job_status: JobState.PENDING, + }) + } + },[ttsState]); + + const requestTts = useCallback( ()=>{ + const modelToken = ttsState.voice ? ttsState.voice.model_token : undefined; + + if(modelToken){ + setTtsState((curr)=>({ + ...curr, + hasEnqueued: true, + inferenceToken: undefined, + inferenceJobType: undefined, + hasAudio: false, + result: undefined, + })); + + const request = { + uuid_idempotency_token: uuidv4(), + tts_model_token: modelToken, + inference_text: ttsState.text, + }; + + GenerateTtsAudio(request).then(res=>{ + if(res.inference_job_token){ + setTtsState((curr)=>({ + ...curr, + inferenceToken: res.inference_job_token, + inferenceJobType: res.inference_job_token_type + })); + } + }); + }else{ + console.log("no voice model selected"); + } + },[ttsState]); + + const handleClose = ()=> { + if(ttsState.hasAudio){ + setTtsState((curr)=>({ + ...curr, + hasEnqueued: false, + inferenceToken: undefined, + inferenceJobType: undefined, + hasAudio: false, + result: undefined, + })); + } + dispatchAppUiState({ + type: APPUI_ACTION_TYPES.CLOSE_DIALOGUE_TTS + }) + }; + + const handleTextInput = ( + e: React.ChangeEvent + )=>{ + setTtsState((curr)=>({ + ...curr, + text: e.target.value, + })); + }; + + const handleOnSelect = (val:string)=>{ + const voiceModel = ttsModels.find((item)=>{ + if (item.title === val) return item + }) + // console.log(val); + // console.log(voiceModel); + setTtsState((curr)=>({ + ...curr, + voice: voiceModel, + })); + } + + return( + + + Generate TTS + + } + className="max-w-xl" + isOpen={appUiState.diagloueTts.isOpen} + onClose={handleClose} + > +
    + + +
    + +
    + +
    Randomized Text
    +
    +
    +