Merge pull request #141 from storytold/feature/tts-inference

Feature/tts inference
This commit is contained in:
ArEnSc authored and GitHub committed 2024-04-10 18:06:05 -04:00
commit 15fa4ecafd
44 files changed
+1724 -323

No files matched your search

+14
View File
@@ -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}`;
+11 -7
View File
@@ -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 (
<button className={className} onClick={onClick}>
<FontAwesomeIcon icon={icon} />
<FontAwesomeIcon icon={icon} size={size}/>
</button>
);
};
+69
View File
@@ -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 (
<Listbox value={selected} onChange={setSelected}>
<div className="relative mt-1">
<Listbox.Button className="relative w-full h-10 cursor-pointer rounded-md bg-brand-secondary py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary sm:text-sm">
<span className="block truncate">{selected.name}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<FontAwesomeIcon icon={faChevronDown} aria-hidden="true"/>
</span>
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-brand-secondary py-1 text-base shadow-lg focus:outline-none sm:text-sm z-10">
{list.map((item, itemIdx) => (
<Listbox.Option
key={itemIdx}
className={({ active }) =>
`relative cursor-pointer select-none py-2 pl-10 pr-4 text-white ${
active ? 'text-white' : 'text-gray-400'
}`
}
value={item}
>
{({ selected }) => (
<>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{Object.values(item)[0]}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3">
<FontAwesomeIcon icon={faCheck} aria-hidden="true"/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
)
}
+110
View File
@@ -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 (
<Combobox value={selected} onChange={setSelected}>
<div className="relative mt-1">
<div className="relative w-full cursor-default rounded-md text-left shadow-md sm:text-sm">
<Combobox.Input
className="w-full border-none rounded-md py-2 pl-3 pr-10 text-sm leading-5 bg-brand-secondary text-white outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary"
displayValue={(item: {[key: string] : string}) => {
if(listDisplayKey) return item[listDisplayKey];
else return Object.values(item)[0]
}}
onChange={(event) => setQuery(event.target.value)}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<FontAwesomeIcon icon={faChevronDown} aria-hidden="true"/>
</Combobox.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
afterLeave={() => setQuery('')}
>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-brand-secondary py-1 text-base shadow-lg focus:outline-none sm:text-sm z-10">
{filteredList.length === 0 && query !== '' ? (
<div className="relative cursor-default select-none px-4 py-2 text-white">
Nothing found.
</div>
) : (
filteredList.map((item, itemIdx) => {
if(itemIdx <= 10)
return (
<Combobox.Option
key={itemIdx}
className={({ active }) =>
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${
active ? 'text-white' : 'text-gray-400'
}`
}
value={item}
>
{({ selected, active }) => (
<>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{!listDisplayKey && Object.values(item)[0]}
{listDisplayKey && item[listDisplayKey]}
</span>
{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
active ? 'text-white' : 'text-teal-600'
}`}
>
<FontAwesomeIcon icon={faCheck} aria-hidden="true"/>
</span>
) : null}
</>
)}
</Combobox.Option>
);
return null;
})
)}
</Combobox.Options>
</Transition>
</div>
</Combobox>
)
}
+57
View File
@@ -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(
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10 " onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/40" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className={twMerge("w-full max-w-lg transform rounded-xl border border-ui-panel-border bg-ui-panel p-5 text-left align-middle shadow-xl transition-all", className)}>
{title && (
<Dialog.Title
as="h4"
className="mb-4 text-xl font-bold text-white"
>
{title}
</Dialog.Title>
)}
{children}
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
);
};
+6
View File
@@ -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,
@@ -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<AuthState>({});
const loginAndGetUserInfo = useCallback((
+1 -7
View File
@@ -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
}:{
+1
View File
@@ -0,0 +1 @@
export { useInferenceJobManager } from "./useInferenceJobManager";
@@ -0,0 +1 @@
export { useInferenceJobManager } from './useInferenceJobManager';
@@ -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<GetJobStatusResponse | null> {
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);
}
},[]);
};
+40 -70
View File
@@ -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}
</Button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10 " onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
<TransitionDialogue
title={title}
isOpen={isOpen}
onClose={closeModal}
className={dialogProps.className}
>
<div className="mt-2">{children}</div>
<div className="mt-6 flex justify-end gap-2">
<Button
type="button"
onClick={closeModal}
{...closeButtonProps}
variant="secondary"
>
<div className="fixed inset-0 bg-black/40" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-xl border border-ui-panel-border bg-ui-panel p-5 text-left align-middle shadow-xl transition-all">
{title && (
<Dialog.Title
as="h4"
className="mb-4 text-xl font-bold text-white"
>
{title}
</Dialog.Title>
)}
<div className="mt-2">{children}</div>
<div className="mt-6 flex justify-end gap-2">
<Button
type="button"
onClick={closeModal}
{...closeButtonProps}
variant="secondary"
>
{closeButtonLabel}
</Button>
{confirmButtonProps && (
<Button
type="button"
{...confirmButtonProps}
onClick={(e) => {
if (confirmButtonProps.onClick) {
confirmButtonProps.onClick(e);
}
closeModal();
}}
>
{confirmButtonProps.label
? confirmButtonProps.label
: "Confirm"}
</Button>
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
{closeButtonLabel}
</Button>
{confirmButtonProps && (
<Button
type="button"
{...confirmButtonProps}
onClick={(e) => {
if (confirmButtonProps.onClick) {
confirmButtonProps.onClick(e);
}
closeModal();
}}
>
{confirmButtonProps.label
? confirmButtonProps.label
: "Confirm"}
</Button>
)}
</div>
</TransitionDialogue>
</>
);
};
@@ -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(
<FontAwesomeIcon
className="col-span-1 animate-spin"
icon={faSpinner}
size="2xl"
/>
);
}
case JobState.STARTED:{
return(
<FontAwesomeIcon
className="col-span-1 animate-spin"
icon={faLoader}
size="2xl"
/>
);
}
case JobState.COMPLETE_SUCCESS:{
return(
<FontAwesomeIcon
className="col-span-1"
icon={faCircleCheck}
size="2xl"
/>
);
}
default:{
return(
<FontAwesomeIcon
className="col-span-1"
icon={faCircleExclamation}
size="2xl"
/>
);
}
}
};
const InferenceItem = ({job}:{job:InferenceJob}) => {
// console.log(job);
return(
<li
className="grid grid-cols-12 gap-2 items-center bg-brand-secondary rounded-md py-2 px-4"
>
<JobStatusIcon jobStatus={job.job_status} />
<div className="col-span-10 flex flex-col">
<H4>{printJobType(job.job_type)}</H4>
<H6>{printJobStatus(job.job_status)} </H6>
</div>
{job.job_status === JobState.COMPLETE_SUCCESS &&
<div className="col-span-1 flex justify-end">
<ButtonIcon
icon={faSquareXmark}
size="2xl"
onClick={()=>{
deleteInferenceJob(job);
}}
/>
</div>
}
</li>
);
};
export const DialogueInference = () =>{
useSignals();
const buttonIcon = inferenceJobs.value.length > 0
? faClipboardList : faClipboard;
return(
<ButtonDialogue
title="My Jobs"
dialogProps={{
className:"w-1/2 max-w-full"
}}
buttonProps={{
icon: buttonIcon,
variant: "secondary",
label: "My Jobs",
className:( inferenceJobs.value.length === 0 ? "invisible" : "visible")
}}
>
{inferenceJobs.value.length > 0 &&
<ul className="flex flex-col gap-2">
{inferenceJobs.value.map(job=>
<InferenceItem key={job.job_id} job={job} />
)}
</ul>
}
{
inferenceJobs.value.length === 0 &&
<P>You currently have no running proccesses.</P>
}
</ButtonDialogue>
);
}
+1
View File
@@ -0,0 +1 @@
export { DialogueInference } from './DialogueInference'
+9 -1
View File
@@ -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(
<Button onClick={handleLogout}>Log Out</Button>
<>
<DialogueInference />
<Button onClick={handleLogout}>Log Out</Button>
</>
);
}else{
return(
-1
View File
@@ -4,7 +4,6 @@ import { AuthenticationContext } from "~/contexts/Authentication";
export const withProtectionRoute = <P extends {}>(Component: ComponentType<P>) => (rest:P) => {
const { authState } = useContext(AuthenticationContext);
// console.log(authState);
if (authState.isLoggedIn === undefined ) {
return <>waiting</>;
}
+3
View File
@@ -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
@@ -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 = () => {
<PreviewEngineCamera />
<ControlsVideo />
<ControlPanelSceneObject />
<DialogueTTS />
</div>
</div>
{appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && (
+16 -10
View File
@@ -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) {
+1 -1
View File
@@ -1,5 +1,5 @@
export enum QueueNames {
FROM_ENGINE = "FROM_ENGINE",
TO_ENGINE = "TO_ENGINE",
TO_TIMELINE = "TO_TIMELINE"
TO_TIMELINE = "TO_TIMELINE",
}
@@ -0,0 +1,3 @@
export enum toInferenceActions {
ADD_TTS_JOB = "add_tts_job"
}
@@ -66,7 +66,7 @@ export const Controls3D = () => {
<div className="flex justify-center">
<div className="rounded-b-lg border-x border-b border-ui-panel-border bg-ui-controls p-2 text-white">
<div className="flex items-center justify-center gap-2">
<ButtonIcon icon={faPlus} onClick={handlePlus} fill={true} />
<ButtonIcon icon={faPlus} onClick={handlePlus} hoverFill={true} />
<span className="h-4 w-0 border-l border-white/[0.15]" />
@@ -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<string>("");
const [sceneToken, setSceneToken] = useState<string>("");
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 (
<div className="flex flex-col gap-2 pl-3 pt-3">
<div className="flex gap-2">
@@ -128,14 +112,7 @@ export const ControlsTopButtons = () => {
<p>Ask Wil about React</p>
</ButtonDialogue>
</div>
<div className="flex gap-2">
</div>
<div className="flex gap-2">
{/* <Button onClick={handleButtonSingleFrame}>Render Single Frame</Button>
<Button onClick={handleButtonTakeFrame}>Take Frame</Button> */}
</div>
<TestFeaturesButtons debug={false}/>
</div>
);
};
@@ -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(
<>
<div className="flex gap-1">
<Button variant="secondary" onClick={handleButtonCameraView} className={smallButtons}>
Toggle Camera View
</Button>
<Button
onClick={testObjectPanel}
className={smallButtons}
>
Object Panel
</Button>
<Button
onClick={testTTSPanel}
className={tertiaryColor + smallButtons}
>
TTS Panel
</Button>
</div>
<div className="flex gap-1">
<Button onClick={handleButtonSingleFrame} className={smallButtons}>Render Single Frame</Button>
<Button onClick={handleButtonTakeFrame} className={smallButtons}>Take Frame</Button>
<Button onClick={handleButtonRender} className={smallButtons}>Render</Button>
<Button
onClick={testStylizeRequest}
className={"bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary " +smallButtons}
style={{ zIndex: 9001 }}
>
Test Stylize
</Button>
</div>
</>
)
}
@@ -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<TtsState>({
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<Array<TtsModelListItem>>([]);
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<HTMLTextAreaElement>
)=>{
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(
<TransitionDialogue
title={
<>
<FontAwesomeIcon icon={faVolume} className="pr-2"/>
Generate TTS
</>
}
className="max-w-xl"
isOpen={appUiState.diagloueTts.isOpen}
onClose={handleClose}
>
<div className="flex flex-col">
<Label className="mb-1">Select a Voice</Label>
<ListSearchDropdown
list={ttsModels}
listDisplayKey="title"
onSelect={handleOnSelect}
/>
<div className="flex w-full justify-between mt-4">
<Label>What would you like to say?</Label>
<div className="flex gap-2 items-center">
<FontAwesomeIcon className="text-brand-primary" icon={faShuffle}/>
<H5 className="text-brand-primary">Randomized Text</H5>
</div>
</div>
<Textarea
placeholder="Enter what you want the voice to say here."
value={ttsState.text}
onChange={handleTextInput}
/>
<div className="mt-6 flex gap-2">
<div className="w-full h-12">
{!ttsState.hasAudio && !ttsState.hasEnqueued &&
<Button
className="w-36 h-full text-xl "
variant={ttsState.hasAudio ? "secondary" : "primary" }
disabled={ttsState.text === ""}
icon={faBrainCircuit}
onClick={requestTts}
>
Generate
</Button>
}
{!ttsState.hasAudio && ttsState.hasEnqueued &&
<LoadingDotsTyping className="bg-brand-secondary-500 rounded-lg"/>
}
{ ttsState.hasAudio &&
<div className="bg-success w-full h-full rounded-lg flex items-center justify-center">
<H2>Success!! Please Check "My Audio" in the Audio Panel.</H2>
</div>
}
</div>
</div>
<div className="mt-6 flex justify-between gap-2">
{ ttsState.hasEnqueued &&
<Button
type="button"
disabled={!ttsState.hasAudio}
onClick={requestTts}
icon={faBrainCircuit}
>
Generate Another
</Button>
}
<span />
<Button
type="button"
onClick={handleClose}
variant="secondary"
>
{ttsState.hasEnqueued ? 'Close':'Cancel'}
</Button>
</div>
</div>
</TransitionDialogue>
);
};
@@ -0,0 +1,85 @@
import { inferTts } from "~/api";
import {
GenerateTtsAudioRequest,
GenerateTtsAudioSuccess,
GenerateTtsAudioError,
GenerateTtsAudioResponse,
GenerateTtsAudioErrorType,
EndpointResponse
} from './types';
import {
maybeMapError
} from './utilities';
export function GenerateTtsAudioIsError(response: GenerateTtsAudioResponse): response is GenerateTtsAudioError {
return !('inference_job_token' in response);
}
export async function GenerateTtsAudio(request: GenerateTtsAudioRequest) : Promise<GenerateTtsAudioResponse | undefined>
{
return await fetch(inferTts, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (!('inference_job_token' in res)) {
return { error: GenerateTtsAudioErrorType.UnknownError };
}
const ret:GenerateTtsAudioSuccess = {
success: true,
inference_job_token: res.inference_job_token,
inference_job_token_type: res.inference_job_token_type,
}
return ret;
})
.catch(e => {
let maybeError = maybeMapError(e);
if (maybeError !== undefined) {
return { error: maybeError };
}
return { error: GenerateTtsAudioErrorType.UnknownError };
});
// return await fetch(inferTts, {
// method: 'POST',
// headers: {
// 'Accept': 'application/json',
// 'Content-Type': 'application/json',
// },
// credentials: 'include',
// body: JSON.stringify(request),
// })
// .then(res => res.json().then(data => ({ status: <number>res.status, body: <EndpointResponse>data })))
// .then((fullResponse) => {
// let maybeError = maybeMapError(fullResponse);
// if (maybeError !== undefined) {
// return { error: maybeError };
// }
// if (!fullResponse.body || !fullResponse.body.success) {
// return { error: GenerateTtsAudioErrorType.UnknownError };
// }
// if (!('inference_job_token' in fullResponse.body)) {
// return { error: GenerateTtsAudioErrorType.UnknownError };
// } else {
// return <GenerateTtsAudioSuccess> {
// inference_job_token: fullResponse.body.inference_job_token,
// inference_job_token_type: fullResponse.body.inference_job_token_type,
// };
// }
// })
// .catch(e => {
// let maybeError = maybeMapError(e);
// if (maybeError !== undefined) {
// return { error: maybeError };
// }
// return { error: GenerateTtsAudioErrorType.UnknownError };
// });
};
Whitespace-only changes.
@@ -0,0 +1,83 @@
/*
* Types for Listing TTS Models
*/
export interface TtsModelListResponsePayload {
success: boolean,
models: Array<TtsModelListItem>,
}
export interface UserRatings {
positive_count: number,
negative_count: number,
// Total count does not take into account "neutral" ratings.
total_count: number,
}
export interface TtsModelListItem {
model_token: string,
tts_model_type: string,
creator_user_token: string,
creator_username: string,
creator_display_name: string,
creator_gravatar_hash: string,
ietf_language_tag: string,
ietf_primary_language_subtag: string,
updatable_slug: string,
title: string,
is_front_page_featured: boolean,
is_twitch_featured: boolean,
user_ratings: UserRatings,
category_tokens: string[],
created_at: string,
updated_at: string,
}
/*
* Types for Making a TTS Generation Request
*/
export interface GenerateTtsAudioRequest {
uuid_idempotency_token: string,
tts_model_token: string,
inference_text: string,
// TODO(2022-03): TEMPORARY
is_storyteller_demo?: boolean,
}
export interface GenerateTtsAudioSuccess {
success: boolean,
inference_job_token: string,
// Which queue to poll
inference_job_token_type?: string,
}
export interface GenerateTtsAudioError {
error: GenerateTtsAudioErrorType,
}
export enum GenerateTtsAudioErrorType {
BadRequest,
NotFound,
TooManyRequests,
ServerError,
UnknownError,
}
export type GenerateTtsAudioResponse = GenerateTtsAudioSuccess | GenerateTtsAudioError;
export interface EndpointSuccessResponse {
success: boolean,
inference_job_token: string,
inference_job_token_type?: string,
}
export interface EndpointErrorResponse {
success: boolean,
}
export type EndpointResponse = EndpointSuccessResponse | EndpointErrorResponse;
export interface StatusLike {
status: number,
}
@@ -0,0 +1,41 @@
import { listTts } from '~/api';
import {
TtsModelListItem,
TtsModelListResponsePayload,
GenerateTtsAudioErrorType,
StatusLike
} from './types';
export async function ListTtsModels() : Promise<Array<TtsModelListItem>| undefined> {
return await fetch(listTts, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
.then(res => res.json())
.then(res => {
const response : TtsModelListResponsePayload = res;
if (!response.success) {
return;
}
return response?.models;
})
.catch(e => {
return undefined;
});
}
export function maybeMapError(statuslike: StatusLike) : GenerateTtsAudioErrorType | undefined {
switch (statuslike.status) {
case 400:
return GenerateTtsAudioErrorType.BadRequest;
case 404:
return GenerateTtsAudioErrorType.NotFound;
case 429:
return GenerateTtsAudioErrorType.TooManyRequests;
case 500:
return GenerateTtsAudioErrorType.ServerError;
}
}
@@ -0,0 +1,161 @@
import { useCallback, useContext, useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
import { faCirclePlus, faArrowsRotate } from "@fortawesome/pro-solid-svg-icons";
import { AppUiContext } from "~/pages/PageEnigma/contexts/AppUiContext";
import { AuthenticationContext } from "~/contexts/Authentication";
import { APPUI_ACTION_TYPES } from "~/pages/PageEnigma/reducers";
import { audioFilter, audioItems } from "~/pages/PageEnigma/store";
import { AssetFilterOption } from "~/pages/PageEnigma/models";
import { Button, ButtonIcon } from "~/components";
import { MediaItem, AssetType } from "~/pages/PageEnigma/models";
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
import { audioItemsFromServer } from "~/pages/PageEnigma/store/mediaFromServer";
import { ListAudioByUser } from "./listAudioByUser";
import { inferenceJobs } from "~/pages/PageEnigma/store/inferenceJobs";
import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager";
export const AudioTabComponent = () => {
useSignals();
const [ state, setState ] = useState({
firstLoad: false,
fetchingUserAudio: false,
});
const { authState } = useContext(AuthenticationContext);
const [, dispatchAppUiState] = useContext(AppUiContext);
const handleOpenTtsDialogue = ()=>{
dispatchAppUiState({
type: APPUI_ACTION_TYPES.OPEN_DIALOGUE_TTS
})
}
const allAudioItems = [...audioItems.value, ...audioItemsFromServer.value];
const handleListAudioByUser = useCallback((username:string)=>{
setState((curr)=>({...curr, fetchingUserAudio:true}));
ListAudioByUser(username).then((res:any[])=>{
console.log(res)
setState((curr)=>({...curr, fetchingUserAudio:false}));
audioItemsFromServer.value = res.map(item=>{
const morphedItem:MediaItem = {
version: 1,
type: AssetType.AUDIO,
media_id: item.token,
object_uuid: item.token,
name: item.maybe_title || item.origin.maybe_model.title,
length: 25,
thumbnail: "resources/placeholders/audio_placeholder.png",
isMine: true,
// isBookmarked?: boolean;
}
return morphedItem;
});
});
}, []);
useEffect(()=>{
if ( authState.userInfo ){
if(state.firstLoad === false && audioItemsFromServer.value.length === 0){
handleListAudioByUser(authState.userInfo.username);
}
}
}, [authState, state, handleListAudioByUser]);
useSignalEffect(()=>{
// flagging first load is done
if (state.firstLoad === false && audioItemsFromServer.value.length > 0){
setState(curr=>({...curr, firstLoad: true}));
}
// when inference changes, check if there's a new audio to refresh for
if (inferenceJobs.value.length > 0 && authState.userInfo){
const found = inferenceJobs.value.find((job)=>{
if(job.job_status === JobState.COMPLETE_SUCCESS){
console.log(job);
const foundItemOfJob = audioItemsFromServer.value.find((item)=>{
return item.media_id === job.result.entity_token
});
return foundItemOfJob !== undefined;
}
});
if(found === undefined){
handleListAudioByUser(authState.userInfo.username);
}
}
});
return (
<>
<div className="w-full overflow-x-auto">
<div className="mb-4 mt-4 flex justify-start items-center gap-2 px-4">
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.ALL ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}
>
All
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.MINE ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.MINE)}
disabled={!allAudioItems.some((item) => item.isMine)}
>
My Audios
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.BOOKMARKED
? "active"
: "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.BOOKMARKED)}
disabled={!allAudioItems.some((item) => item.isBookmarked)}
>
Bookmarked
</button>
<ButtonIcon
className={twMerge(
"absolute right-0 mr-4",
// state.fetchingUserAudio ? "animate-spin" : "",
)}
icon={faArrowsRotate}
onClick={()=>{
if(authState.userInfo?.username){
handleListAudioByUser(authState.userInfo?.username);
}
}}
/>
</div>
</div>
<div className="w-full px-4">
<Button
icon={faCirclePlus}
variant="action"
className="w-full py-3 text-sm font-medium"
onClick={handleOpenTtsDialogue}
>
Generate Audio
</Button>
</div>
<div className="h-full w-full overflow-y-auto px-4 pt-4">
<ItemElements
items={allAudioItems}
assetFilter={audioFilter.value}
/>
</div>
</>
);
};
@@ -0,0 +1 @@
export { AudioTabComponent } from "./AudioTabComponent";
@@ -0,0 +1,55 @@
import { listMediaByUser } from "~/api";
import {
MediaFileType,
MediaFileClass,
MediaFileSubtype,
UserDetailsLight,
WeightCategory,
WeightType
} from "./types";
export interface MediaFile {
token: string;
media_type: MediaFileType;
media_class: MediaFileClass | null;
maybe_media_subtype: MediaFileSubtype | null;
public_bucket_path: string;
maybe_engine_extension: string | null;
maybe_batch_token: string;
maybe_title: string | null;
maybe_original_filename: string | null;
maybe_creator_user: UserDetailsLight | null;
maybe_prompt_token: string | null;
creator_set_visibility: string;
created_at: Date;
updated_at: Date;
maybe_model_weight_info: {
title: string;
weight_token: string;
weight_category: WeightCategory;
weight_type: WeightType;
maybe_weight_creator: UserDetailsLight;
maybe_cover_image_public_bucket_path: string;
};
}
export const ListAudioByUser = async(username:string) => {
return await fetch(listMediaByUser(username),{
method: 'GET',
headers: {
"Accept": "application/json",
},
credentials: 'include'
})
.then(res => res.json())
.then(res => {
if(res.success && res.results){
return res.results.filter((item:MediaFile)=>item['media_type']==='audio');
}else{
Promise.reject();
}
})
.catch(e => ({ success : false }));
}
@@ -0,0 +1,124 @@
export enum WeightCategory {
TTS = "text_to_speech",
VOCODER = "vocoder",
VC = "voice_conversion",
ZS = "zero_shot",
SD = "image_generation",
WF = "workflow_config",
}
export enum MediaFileClass {
/// Unknown (default value)
/// This will be present until we migrate all old files.
Unknown = "unknown",
/// Audio files: wav, mp3, etc.
Audio = "audio",
/// Image files: png, jpeg, etc.
Image = "image",
/// Video files: mp4, etc.
Video = "video",
/// Engine "animations"
Animation = "animation",
/// Engine "characters"
Character = "character",
/// Engine "prop" items
Prop = "prop",
/// Engine scenes (internal and external scenes)
Scene = "scene",
}
export enum MediaFileType {
Audio = "audio",
Video = "video",
Image = "image",
// BVH is a very popular file format for motion capture.
// It is compatible with our engine.
BVH = "bvh",
// GLTF is the text-based format of GLB. Unforunately it requires a
// second file to include the visual data, so we strongly discourage
// the use of this format in favor of GLB.
GLTF = "gltf",
// GLB is the format we convert FBX files into. It's a very popular
// open source 3D file format.
GLB = "glb",
// FBX isn't supported by Storyteller Engine, but we can get
// uploads in this format. It's a very popular 3D graphics format.
FBX = "fbx",
// Full Storyteller Engine scenes (RON = Rusty Object Notation)
// This is a temporary format that will eventually go away.
SceneRon = "scene_ron",
None = "none",
}
export enum MediaFileSubtype {
/// Animation file from Mixamo
/// Primarily used for FBX and GLB.
Mixamo = "mixamo",
/// Animation file from MocapNet
/// Primarily used for BVH.
MocapNet = "mocap_net",
/// Generic animation case
/// Used for BVH files, but can also pertain to animation-only files of other types.
AnimationOnly = "animation_only",
// TODO(bt,2024-03-08): Migrate records and code, then remove
/// DEPRECATED: Use `SceneImport` instead.
Scene = "scene",
/// Generic 3D scene file.
/// Can pertain to glTF, glB, FBX, etc.
SceneImport = "scene_import",
/// Native Storyteller scene format.
/// Typically stored in a `.scn.ron` file.
StorytellerScene = "storyteller_scene",
}
/// A common type returned by several endpoints.
/// Basic information to display a user and their avatar.
export interface UserDetailsLight {
user_token: string,
/// Username (lowercase)
username: string,
/// Username with user-specified capitalization
display_name: string,
gravatar_hash: string,
default_avatar: DefaultAvatarInfo,
}
export interface DefaultAvatarInfo {
image_index: number,
color_index: number,
}
export enum WeightType {
TT2 = "tt2",
HIFIGAN_TT2 = "hifigan_tt2",
RVCv2 = "rvc_v2",
SD_15 = "sd_1.5",
SDXL = "sdxl",
SVC = "so_vits_svc",
LORA = "loRA",
VALL_E = "vall_e",
NONE = "none",
}
@@ -1,69 +1 @@
import { audioFilter, audioItems } from "~/pages/PageEnigma/store";
import { AssetFilterOption } from "~/pages/PageEnigma/models";
import { useSignals } from "@preact/signals-react/runtime";
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
import { Button } from "~/components";
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
export const AudioTab = () => {
useSignals();
return (
<>
<div className="w-full overflow-x-auto">
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.ALL ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}
>
All
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.MINE ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.MINE)}
disabled={!audioItems.value.some((item) => item.isMine)}
>
My Audios
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.BOOKMARKED
? "active"
: "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.BOOKMARKED)}
disabled={!audioItems.value.some((item) => item.isBookmarked)}
>
Bookmarked
</button>
</div>
</div>
<div className="w-full px-4 pb-4">
<Button
icon={faCirclePlus}
variant="action"
className="w-full py-3 text-sm font-medium"
>
Generate Audio
</Button>
</div>
<div className="h-full w-full overflow-y-auto px-4">
<ItemElements
items={audioItems.value}
assetFilter={audioFilter.value}
/>
</div>
</>
);
};
export { AudioTabComponent as AudioTab } from "../AudioTabComponent";
+1 -1
View File
@@ -570,4 +570,4 @@ export class TimeLine {
currentTime: this.current_time,
});
}
}
}
+1
View File
@@ -2,3 +2,4 @@ export * from "./assets";
export * from "./message";
export * from "./sidePanel";
export * from "./track";
export * from "./inferenceJobs";
@@ -0,0 +1,7 @@
export interface InferenceJob {
version: number;
job_id: string;
job_type: string;
job_status: string;
result?: any;
}
@@ -2,8 +2,24 @@ import { State, Action, ACTION_TYPES } from "./types";
export function reducer(state: State, action: Action): State {
switch (action.type) {
case ACTION_TYPES.OPEN_DIALOGUE_TTS:{
return{
...state,
diagloueTts:{
isOpen: true,
}
}
}
case ACTION_TYPES.CLOSE_DIALOGUE_TTS:{
return{
...state,
diagloueTts:{
isOpen: false,
}
}
}
case ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT: {
console.log("pay", action.payload);
// console.log("pay", action.payload);
return {
...state,
controlPanel: {
@@ -34,6 +34,9 @@ export type State = {
useFakeTimer?: number;
};
controlPanel: ControlPanel;
diagloueTts: {
isOpen: boolean;
}
};
export enum ACTION_TYPES {
@@ -47,13 +50,14 @@ export enum ACTION_TYPES {
SHOW_CONTROLPANELS_SCENEOBJECT = "show_controlpanels_sceneobject",
UPDATE_CONTROLPANELS_SCENEOBJECT = "update_controlpanels_sceneobject",
HIDE_CONTROLPANELS_SCENEOBJECT = "hide_controlpanels_sceneobject",
OPEN_DIALOGUE_TTS = "open_dialogue_tts",
CLOSE_DIALOGUE_TTS = "close_dialogue_tts",
}
export type Action =
| {
// Not sure why we need this but type script won't stop complaning fix wil.
type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT;
}
| { type: ACTION_TYPES.OPEN_DIALOGUE_TTS }
| { type: ACTION_TYPES.CLOSE_DIALOGUE_TTS }
| { type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT }
| {
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT;
payload: SceneObject;
@@ -27,4 +27,5 @@ export const initialStateValues: State = {
},
},
},
diagloueTts:{isOpen:false}
};
@@ -0,0 +1,45 @@
import { signal } from "@preact/signals-core";
import { InferenceJob } from "../models";
export const inferenceJobs = signal<InferenceJob[]>([]);
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')
let newList:InferenceJob[] = []
inferenceJobs.value.forEach(job=>{
if(job.job_id !== deletableJob.job_id) {
newList.push(job);
} else {
// do nothing
}
});
console.log(newList);
inferenceJobs.value = [...newList];
}
@@ -0,0 +1,3 @@
import { signal } from "@preact/signals-core";
import { MediaItem } from "~/pages/PageEnigma/models";
export const audioItemsFromServer = signal<MediaItem[]>([]);
+18 -1
View File
@@ -3,6 +3,8 @@ import {
Button,
ButtonLink,
H1,H2,H3,H4,H5,H6,P,Label,
ListDropdown,
ListSearchDropdown,
LoadingBar,
LoadingDotsTyping,
LoadingDotsBricks,
@@ -13,6 +15,15 @@ export default function ComponentLibrary () {
const [progress, setProgress] = useState(0);
const loopRef = useRef<NodeJS.Timeout | null>(null);
const testdata = [
{ name: 'Wade Cooper' },
{ name: 'Arlene Mccoy' },
{ name: 'Devon Webb' },
{ name: 'Tom Cook' },
{ name: 'Tanya Fox' },
{ name: 'Hellen Schmidt' },
]
const pushProgressBar: ()=>void = useCallback(()=>{
loopRef.current = setInterval(function timer(){
setProgress((curr)=>{
@@ -72,6 +83,12 @@ export default function ComponentLibrary () {
<ButtonLink to="/">Back to /</ButtonLink>
</div>
<div className="flex flex-col gap-2 mb-4 max-w-2xl">
<H4>List Dropdowns</H4>
<ListDropdown list={testdata}/>
<ListSearchDropdown list={testdata}/>
</div>
<div className="flex flex-col gap-2 mb-4">
<H4>Loading Dots</H4>
<div className="flex gap-2">
@@ -84,7 +101,7 @@ export default function ComponentLibrary () {
</div>
</div>
<div className="flex flex-col gap-2 mb-4">
<div className="flex flex-col gap-2 mb-4 max-w-2xl">
<H4>Loading Bar</H4>
<div className="flex gap-2 items-center">
<LoadingBar
+111 -119
View File
@@ -838,9 +838,9 @@
"dev": true
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"cpu": [
"ppc64"
],
@@ -5264,16 +5264,16 @@
}
},
"node_modules/express": {
"version": "4.18.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
"integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -5304,14 +5304,6 @@
"node": ">= 0.10.0"
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -9858,9 +9850,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.37",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.37.tgz",
"integrity": "sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==",
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"devOptional": true,
"funding": [
{
@@ -11888,9 +11880,9 @@
}
},
"node_modules/tar": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dev": true,
"dependencies": {
"chownr": "^2.0.0",
@@ -12708,14 +12700,14 @@
}
},
"node_modules/vite": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
"integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
"devOptional": true,
"dependencies": {
"esbuild": "^0.19.3",
"postcss": "^8.4.35",
"rollup": "^4.2.0"
"esbuild": "^0.20.1",
"postcss": "^8.4.38",
"rollup": "^4.13.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -12804,9 +12796,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/android-arm": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"cpu": [
"arm"
],
@@ -12820,9 +12812,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/android-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"cpu": [
"arm64"
],
@@ -12836,9 +12828,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/android-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"cpu": [
"x64"
],
@@ -12852,9 +12844,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"cpu": [
"arm64"
],
@@ -12868,9 +12860,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"cpu": [
"x64"
],
@@ -12884,9 +12876,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"cpu": [
"arm64"
],
@@ -12900,9 +12892,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"cpu": [
"x64"
],
@@ -12916,9 +12908,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-arm": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"cpu": [
"arm"
],
@@ -12932,9 +12924,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"cpu": [
"arm64"
],
@@ -12948,9 +12940,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"cpu": [
"ia32"
],
@@ -12964,9 +12956,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"cpu": [
"loong64"
],
@@ -12980,9 +12972,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"cpu": [
"mips64el"
],
@@ -12996,9 +12988,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"cpu": [
"ppc64"
],
@@ -13012,9 +13004,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"cpu": [
"riscv64"
],
@@ -13028,9 +13020,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"cpu": [
"s390x"
],
@@ -13044,9 +13036,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/linux-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"cpu": [
"x64"
],
@@ -13060,9 +13052,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"cpu": [
"x64"
],
@@ -13076,9 +13068,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"cpu": [
"x64"
],
@@ -13092,9 +13084,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"cpu": [
"x64"
],
@@ -13108,9 +13100,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"cpu": [
"arm64"
],
@@ -13124,9 +13116,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"cpu": [
"ia32"
],
@@ -13140,9 +13132,9 @@
}
},
"node_modules/vite/node_modules/@esbuild/win32-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"cpu": [
"x64"
],
@@ -13156,9 +13148,9 @@
}
},
"node_modules/vite/node_modules/esbuild": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"devOptional": true,
"hasInstallScript": true,
"bin": {
@@ -13168,29 +13160,29 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.19.12",
"@esbuild/android-arm": "0.19.12",
"@esbuild/android-arm64": "0.19.12",
"@esbuild/android-x64": "0.19.12",
"@esbuild/darwin-arm64": "0.19.12",
"@esbuild/darwin-x64": "0.19.12",
"@esbuild/freebsd-arm64": "0.19.12",
"@esbuild/freebsd-x64": "0.19.12",
"@esbuild/linux-arm": "0.19.12",
"@esbuild/linux-arm64": "0.19.12",
"@esbuild/linux-ia32": "0.19.12",
"@esbuild/linux-loong64": "0.19.12",
"@esbuild/linux-mips64el": "0.19.12",
"@esbuild/linux-ppc64": "0.19.12",
"@esbuild/linux-riscv64": "0.19.12",
"@esbuild/linux-s390x": "0.19.12",
"@esbuild/linux-x64": "0.19.12",
"@esbuild/netbsd-x64": "0.19.12",
"@esbuild/openbsd-x64": "0.19.12",
"@esbuild/sunos-x64": "0.19.12",
"@esbuild/win32-arm64": "0.19.12",
"@esbuild/win32-ia32": "0.19.12",
"@esbuild/win32-x64": "0.19.12"
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
}
},
"node_modules/vite/node_modules/rollup": {