styling/sidebar layout fixes (#258)

- broken layout fixes
- every single tab is consistent now
- audio tab styling updates
This commit is contained in:
BFlat authored and GitHub committed 2024-04-27 00:28:10 -04:00
1 parent 2fc081e6ef
commit fd65e65290
21 files changed
+448 -408

No files matched your search

+1 -1
View File
@@ -54,7 +54,7 @@ export const Button = ({
disabled={disabled}
{...{ ...rest, htmlFor }}>
{icon && !iconFlip ? <FontAwesomeIcon icon={icon} /> : null}
<div>{children}</div>
{children}
{icon && iconFlip ? <FontAwesomeIcon icon={icon} /> : null}
</ButtonType>
);
@@ -1,10 +1,6 @@
.fy-file-wrapper {
border-color: transparent;
border-style: dashed;
border-width: 2px;
// flex-grow: 1;
height: calc(100% - 4px);
overflow: scroll;
.file-picker-button {
cursor: pointer;
+29 -26
View File
@@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
import { twMerge } from "tailwind-merge";
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -15,31 +15,34 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
icon?: IconDefinition;
}
export const Input = React.forwardRef(({
label, icon, className, id, ...rest
}: InputProps,
ref: React.ForwardedRef<HTMLInputElement>
) => {
return (
<div className={twMerge("flex flex-col", className)}>
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
export const Input = React.forwardRef(
(
{ label, icon, className, id, ...rest }: InputProps,
ref: React.ForwardedRef<HTMLInputElement>,
) => {
return (
<div className={twMerge("flex flex-col", className)}>
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
<div className="relative w-full">
{icon && (
<FontAwesomeIcon icon={icon} className="absolute h-5 pl-3 pt-2.5" />
)}
<input
ref={ref}
id={id ? id : label ? kebabCase(label) : undefined}
className={twMerge(
"h-10 w-full rounded-md bg-brand-secondary px-3 py-2.5 text-white outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary",
icon ? "pl-12" : "",
<div className="relative w-full">
{icon && (
<FontAwesomeIcon icon={icon} className="absolute h-5 pl-3 pt-2.5" />
)}
onFocus={() => disableHotkeyInput(DomLevels.INPUT)}
onBlur={() => enableHotkeyInput(DomLevels.INPUT)}
{...rest}
/>
<input
ref={ref}
id={id ? id : label ? kebabCase(label) : undefined}
className={twMerge(
"h-10 w-full rounded-md bg-brand-secondary px-3 py-2.5 text-white outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary",
icon ? "pl-12" : "",
)}
onFocus={() => disableHotkeyInput(DomLevels.INPUT)}
onBlur={() => enableHotkeyInput(DomLevels.INPUT)}
{...rest}
/>
</div>
</div>
</div>
);
});
);
},
);
Input.displayName = "Input";
+7 -2
View File
@@ -1,13 +1,15 @@
import React, { useState, useRef } from "react";
import { Transition } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
interface TooltipProps {
children: React.ReactElement;
content: React.ReactNode;
position: "top" | "bottom" | "left" | "right";
className?: string;
}
const Tooltip = ({ children, content, position }: TooltipProps) => {
const Tooltip = ({ children, content, position, className }: TooltipProps) => {
const [isShowing, setIsShowing] = useState(false);
const triggerRef = useRef<HTMLDivElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
@@ -64,7 +66,10 @@ const Tooltip = ({ children, content, position }: TooltipProps) => {
<div
ref={tooltipRef}
style={getStyleForPosition()}
className="absolute z-10 w-max rounded-lg bg-ui-controls px-2.5 py-1.5 text-sm font-medium text-white shadow-lg">
className={twMerge(
"absolute z-10 w-max rounded-lg bg-ui-controls px-2.5 py-1.5 text-sm font-medium text-white shadow-xl",
className ? className : "",
)}>
{content}
</div>
</Transition>
+2 -2
View File
@@ -38,7 +38,7 @@ export const Label = ({
...rest
}: LabelHTMLAttributes<HTMLLabelElement>) => (
<label
className={twMerge(baseTypo, "mb-2 text-sm font-medium", className)}
className={twMerge(baseTypo, "mb-2 text-[15px] font-medium", className)}
{...rest}>
{children}
</label>
@@ -51,7 +51,7 @@ export const H5 = ({ className, children }: TypoProps) => (
);
export const H6 = ({ className, children }: TypoProps) => (
<h6 className={twMerge(baseTypo, "text-sm font-light", className)}>
<h6 className={twMerge(baseTypo, "text-sm font-normal", className)}>
{children}
</h6>
);
@@ -8,22 +8,24 @@ import {
faFileAudio,
faSpinnerThird,
faTrash,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { UploadAudio, UploadAudioIsOk, UploadAudioRequest } from "./utilities";
import { Button } from "../Button";
import { P, WaveformPlayer } from "~/components";
import { ButtonIcon, P, WaveformPlayer } from "~/components";
import Tooltip from "../Tooltip";
const FILE_TYPES = ["MP3", "WAV", "FLAC", "OGG"];
interface Props {
sessionToken: string;
file?: File;
onFileStaged?: (file:File)=>void;
onClear?: ()=>void;
onFileUploaded: (token:string)=>void
onFileStaged?: (file: File) => void;
onClear?: () => void;
onFileUploaded: (token: string) => void;
}
function UploadComponent({
@@ -33,19 +35,18 @@ function UploadComponent({
onClear,
onFileUploaded,
}: Props) {
const [{file, uploadState, uploadToken}, setState] = useState<{
const [{ file, uploadState, uploadToken }, setState] = useState<{
file: File | undefined;
uploadState: "init"|"none"|"uploading"|"uploaded"|"error";
uploadToken ?: string;
uploadState: "init" | "none" | "uploading" | "uploaded" | "error";
uploadToken?: string;
}>({
file: propsFile,
uploadState: propsFile ? "none" : "init",
});
const audioUrl = file ? URL.createObjectURL(file) : "";
const handleChange = (file:File) => {
setState((curr)=>({
const handleChange = (file: File) => {
setState((curr) => ({
...curr,
file: file,
uploadState: "none",
@@ -88,9 +89,9 @@ function UploadComponent({
});
};
useEffect(()=>{
if(file && uploadState === "none" && onFileStaged) onFileStaged(file);
if(!file && uploadState === "none" && onClear) onClear();
useEffect(() => {
if (file && uploadState === "none" && onFileStaged) onFileStaged(file);
if (!file && uploadState === "none" && onClear) onClear();
}, [file, uploadState]);
useEffect(() => {
if (uploadState === "uploaded" && uploadToken) onFileUploaded(uploadToken);
@@ -109,12 +110,22 @@ function UploadComponent({
{file && (
<>
<div className="rounded-lg border border-brand-secondary-700 p-3">
<WaveformPlayer audio={audioUrl} />
<div className="flex items-center gap-3 rounded-lg bg-brand-secondary p-3">
<div className="grow">
<WaveformPlayer audio={audioUrl} />
</div>
<Tooltip content="Remove" position="top">
<ButtonIcon
icon={faXmark}
onClick={handleClear}
className="h-auto w-auto bg-transparent p-0 text-xl opacity-60 hover:bg-transparent hover:opacity-90"
/>
</Tooltip>
</div>
<div className="flex justify-evenly gap-3">
<div className="flex gap-2">
<Button
className="grow"
className="w-full py-2.5"
variant="action"
onClick={handleUploadFile}
disabled={uploadState !== "none" && uploadState !== "error"}
icon={
@@ -133,10 +144,6 @@ function UploadComponent({
<FontAwesomeIcon icon={faSpinnerThird} spin />
)}
</Button>
<Button className="grow" onClick={handleClear} icon={faTrash}>
Clear
</Button>
</div>
</>
)}
@@ -154,13 +161,13 @@ const DragAndDropZone = ({ file }: { file: any }) => {
if (!file) {
return (
<div className="flex cursor-pointer items-center gap-4 rounded-lg border-2 border-dashed border-brand-secondary-700 bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileArrowUp} size="3x" />
<div className="flex flex-col gap-1">
<div className="flex cursor-pointer items-center gap-3.5 rounded-lg border-2 border-dashed border-ui-controls-button/50 bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileArrowUp} className="text-4xl" />
<div className="flex flex-col gap-0">
<P className="font-medium">
<u>Upload a file</u> or drop it here
</P>
<P className="opacity-50">
<P className="flex items-center gap-2 text-sm font-normal opacity-50">
{FILE_TYPES.join(", ").toString()} supported
</P>
</div>
@@ -168,17 +175,17 @@ const DragAndDropZone = ({ file }: { file: any }) => {
);
} else {
return (
<div className="flex cursor-pointer items-center gap-4 rounded-lg border-2 border-dashed border-brand-secondary-700 bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileAudio} size="3x" />
<div className="flex flex-col gap-1">
<div className="flex cursor-pointer items-center gap-3.5 rounded-lg border-2 border-dashed border-ui-controls-button/50 bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileAudio} className="text-4xl" />
<div className="flex flex-col gap-0">
<P className="font-medium">
{file.name.slice(0, file.name.lastIndexOf("."))}
</P>
<P>
<P className="flex items-center gap-2 text-sm font-normal">
<span className="opacity-50">
{`${file.name.split(".").pop().toUpperCase()} file size: ${fileSize} `}
</span>
<u>Change File</u>
<u className="transition-all hover:text-white/80">Change File</u>
</P>
</div>
</div>
@@ -10,72 +10,82 @@ export const PageAudioGeneration = ({
changePage,
audioPanelState,
setAudioPanelState,
}:{
}: {
sessionToken: string;
changePage: (newPage: AudioTabPages)=>void;
audioPanelState: AudioPanelState
setAudioPanelState: React.Dispatch<React.SetStateAction<AudioPanelState>>
}) =>{
changePage: (newPage: AudioTabPages) => void;
audioPanelState: AudioPanelState;
setAudioPanelState: React.Dispatch<React.SetStateAction<AudioPanelState>>;
}) => {
const subpage = audioPanelState.lastWorkingAudioGeneration;
const changeSubpage = (newSubpage: AudioTabPages.TTS | AudioTabPages.V2V) => {
setAudioPanelState((curr)=>({
setAudioPanelState((curr) => ({
...curr,
lastWorkingAudioGeneration: newSubpage
lastWorkingAudioGeneration: newSubpage,
}));
};
const setTtsState = (newTtsState: TtsState)=>{
setAudioPanelState((curr)=>({
const setTtsState = (newTtsState: TtsState) => {
setAudioPanelState((curr) => ({
...curr,
ttsState : {...curr.ttsState, ...newTtsState}
ttsState: { ...curr.ttsState, ...newTtsState },
}));
};
const setV2VState = (newV2VState: V2VState)=>{
setAudioPanelState((curr)=>({
const setV2VState = (newV2VState: V2VState) => {
setAudioPanelState((curr) => ({
...curr,
v2vState : {...curr.v2vState, ...newV2VState}
v2vState: { ...curr.v2vState, ...newV2VState },
}));
};
return(
<div className="flex flex-col px-4 pt-2">
<TabTitle title="Generate Audio" onBack={() => changePage(AudioTabPages.LIBRARY)}/>
return (
<>
<TabTitle
title="Generate Audio"
onBack={() => changePage(AudioTabPages.LIBRARY)}
/>
<div className="flex flex-col px-4">
<div className="mb-4 flex h-10 w-full justify-evenly overflow-hidden rounded-lg">
<button
className={twMerge(
"grow cursor-pointer bg-brand-secondary p-2 text-sm font-medium transition-all",
subpage === AudioTabPages.TTS
? "bg-ui-controls-button"
: "hover:bg-ui-controls-button/50",
)}
disabled={subpage === AudioTabPages.TTS}
onClick={() => changeSubpage(AudioTabPages.TTS)}>
Text to Speech
</button>
<button
className={twMerge(
"grow cursor-pointer bg-brand-secondary p-2 text-sm font-medium transition-all",
subpage === AudioTabPages.V2V
? "bg-ui-controls-button"
: "hover:bg-ui-controls-button/50",
)}
disabled={subpage === AudioTabPages.V2V}
onClick={() => {
changeSubpage(AudioTabPages.V2V);
}}>
Voice to Voice
</button>
</div>
<div className="w-full rounded-lg overflow-hidden flex justify-evenly mb-4">
<button
className={twMerge(
"bg-brand-secondary p-2 grow",
subpage === AudioTabPages.TTS ? "bg-brand-secondary-800": "",)}
disabled={subpage === AudioTabPages.TTS}
onClick={()=>changeSubpage(AudioTabPages.TTS)}
>
Text to Speech
</button>
<button
className={twMerge(
"bg-brand-secondary p-2 grow",
subpage === AudioTabPages.V2V ? "bg-brand-secondary-800": "",)}
disabled={subpage === AudioTabPages.V2V}
onClick={()=>{changeSubpage(AudioTabPages.V2V)}}
>
Voice to Voice
</button>
{subpage === AudioTabPages.TTS && (
<PageTTS
changePage={changePage}
sessionToken={sessionToken}
ttsState={audioPanelState.ttsState}
setTtsState={setTtsState}
/>
)}
{subpage === AudioTabPages.V2V && (
<PageVoicetoVoice
changePage={changePage}
sessionToken={sessionToken}
v2vState={audioPanelState.v2vState}
setV2VState={setV2VState}
/>
)}
</div>
{subpage === AudioTabPages.TTS &&
<PageTTS
changePage={changePage}
sessionToken={sessionToken}
ttsState={audioPanelState.ttsState}
setTtsState={setTtsState}
/>
}
{subpage === AudioTabPages.V2V &&
<PageVoicetoVoice
changePage={changePage}
sessionToken={sessionToken}
v2vState={audioPanelState.v2vState}
setV2VState={setV2VState}
/>
}
</div>
</>
);
}
};
@@ -41,38 +41,40 @@ export const PageLibrary = ({
return (
<>
<TabTitle title="Audio" />
<div className="w-full h-48 overflow-x-auto px-4 flex items-center justify-start gap-2">
<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.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={!allAudioItems.value.some((item) => item.isBookmarked)}>
Bookmarked
</button>
<div>
<div className="flex gap-2 overflow-x-auto overflow-y-hidden 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.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={!allAudioItems.value.some((item) => item.isBookmarked)}>
Bookmarked
</button>
</div>
</div>
<div className="w-full px-4">
@@ -85,7 +87,7 @@ export const PageLibrary = ({
</Button>
</div>
<div className="grow w-full overflow-y-auto px-4 pb-4">
<div className="w-full grow overflow-y-auto px-4 pb-4">
{audioInferenceJobs.value.length > 0 && (
<div className="mb-4 grid grid-cols-1 gap-2">
{audioInferenceJobs.value.map((job) => {
@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react'
import { useCallback, useState } from "react";
import { Input } from "~/components";
import { AudioTabPages } from "./types";
import { TtsModelListItem } from "~/pages/PageEnigma/models/tts";
@@ -9,54 +8,56 @@ import { VoiceModelElement } from "./voiceModelElement";
export const PageSelectTtsModel = ({
changePage,
ttsModels,
onSelect
}:{
changePage: (newPage:AudioTabPages)=>void;
onSelect,
}: {
changePage: (newPage: AudioTabPages) => void;
ttsModels: Array<TtsModelListItem>;
onSelect: (item:TtsModelListItem)=>void;
})=>{
const [query, setQuery] = useState('');
const filteredListOfModels = query === ''
? ttsModels
: ttsModels.filter((model) =>{
return model.title
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''));
})
onSelect: (item: TtsModelListItem) => void;
}) => {
const [query, setQuery] = useState("");
const filteredListOfModels =
query === ""
? ttsModels
: ttsModels.filter((model) => {
return model.title
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, ""));
});
const slicedArray = filteredListOfModels.slice(0, 20);
const refCallback = useCallback((node:HTMLInputElement)=>{
if(node) node.focus();
//auto focus on the mounting on the input component
},[]);
return(
<div className="flex flex-col px-4 pt-2">
const refCallback = useCallback((node: HTMLInputElement) => {
if (node) node.focus();
//auto focus on the mounting on the input component
}, []);
return (
<>
<TabTitle
title="Search TTS Voices"
onBack={() => changePage(AudioTabPages.GENERATE_AUDIO)}
/>
<div className="flex flex-col gap-4 overflow-hidden">
<Input
ref={refCallback}
className="mt-1 px-4"
placeholder="Search Voice by Name"
onChange={(e) => setQuery(e.target.value)}
/>
<Input
ref={refCallback}
className="px-4"
placeholder="Search Voice by Name"
onChange={(e)=>setQuery(e.target.value)}
/>
<div className="grow w-full flex flex-col overflow-y-auto px-4 pb-4 gap-4">
{slicedArray.map((item)=>{
return(
<VoiceModelElement
key={item.model_token}
model={item}
onSelect={(item)=>onSelect(item as TtsModelListItem)}
/>
);
})}
<div className="flex w-full grow flex-col gap-3 overflow-y-auto px-4 pb-4">
{slicedArray.map((item) => {
return (
<VoiceModelElement
key={item.model_token}
model={item}
onSelect={(item) => onSelect(item as TtsModelListItem)}
/>
);
})}
</div>
</div>
</div>
</>
);
}
};
@@ -1,40 +1,40 @@
import { useCallback, useState } from 'react'
import { useCallback, useState } from "react";
import { Input } from "~/components";
import { AudioTabPages } from "./types";
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
import { VoiceModelElement } from "./voiceModelElement";
import { VoiceConversionModelListItem } from './typesImported';
import { VoiceConversionModelListItem } from "./typesImported";
export const PageSelectV2VModel = ({
changePage,
v2vModels,
onSelect
}:{
changePage: (newPage:AudioTabPages)=>void;
onSelect,
}: {
changePage: (newPage: AudioTabPages) => void;
v2vModels: Array<VoiceConversionModelListItem>;
onSelect: (item:VoiceConversionModelListItem)=>void;
})=>{
const [query, setQuery] = useState('');
const filteredListOfModels = query === ''
? v2vModels
: v2vModels.filter((model) =>{
return model.title
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''));
})
onSelect: (item: VoiceConversionModelListItem) => void;
}) => {
const [query, setQuery] = useState("");
const filteredListOfModels =
query === ""
? v2vModels
: v2vModels.filter((model) => {
return model.title
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, ""));
});
const slicedArray = filteredListOfModels.slice(0, 20);
const refCallback = useCallback((node:HTMLInputElement)=>{
if(node) node.focus();
const refCallback = useCallback((node: HTMLInputElement) => {
if (node) node.focus();
//auto focus on the mounting on the input component
},[]);
}, []);
return(
<div className="flex flex-col px-4 pt-2">
return (
<div className="flex flex-col gap-4 overflow-hidden">
<TabTitle
title="Search Convertible Voices"
onBack={() => changePage(AudioTabPages.GENERATE_AUDIO)}
@@ -42,21 +42,23 @@ export const PageSelectV2VModel = ({
<Input
ref={refCallback}
className="px-4"
className="mt-1 px-4"
placeholder="Search Voice by Name"
onChange={(e)=>setQuery(e.target.value)}
onChange={(e) => setQuery(e.target.value)}
/>
<div className="grow w-full flex flex-col overflow-y-auto px-4 pb-4 gap-4">
{slicedArray.map((item)=>{
return(
<div className="flex w-full grow flex-col gap-3 overflow-y-auto px-4 pb-4">
{slicedArray.map((item) => {
return (
<VoiceModelElement
key={item.token}
model={item}
onSelect={(item)=>onSelect(item as VoiceConversionModelListItem)}
onSelect={(item) =>
onSelect(item as VoiceConversionModelListItem)
}
/>
);
})}
</div>
</div>
);
}
};
@@ -1,20 +1,14 @@
import { useCallback, } from "react";
import { useCallback } from "react";
import { v4 as uuidv4 } from "uuid";
import { faVolumeHigh, faChevronRight} from "@fortawesome/pro-solid-svg-icons";
import { faVolumeHigh, faChevronRight } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
H4, H6,
Button,
Label,
Textarea,
} from "~/components";
import { H4, H6, Button, Label, Textarea } from "~/components";
import { GenerateTtsAudioResponse, } from "~/pages/PageEnigma/models/tts";
import { GenerateTtsAudioResponse } from "~/pages/PageEnigma/models/tts";
import { GenerateTtsAudio, } from "./utilities";
import { GenerateTtsAudio } from "./utilities";
import { AudioTabPages, TtsState } from "./types";
export const PageTTS = ({
@@ -22,13 +16,12 @@ export const PageTTS = ({
sessionToken,
ttsState,
setTtsState,
}:{
changePage: (newPage:AudioTabPages) => void;
}: {
changePage: (newPage: AudioTabPages) => void;
sessionToken: string;
ttsState: TtsState,
setTtsState: (newState:TtsState)=>void,
})=>{
ttsState: TtsState;
setTtsState: (newState: TtsState) => void;
}) => {
const requestTts = useCallback(
(sessionToken: string) => {
const modelToken = ttsState.voice
@@ -36,7 +29,6 @@ export const PageTTS = ({
: undefined;
if (modelToken) {
const request = {
uuid_idempotency_token: uuidv4(),
tts_model_token: modelToken,
@@ -50,7 +42,7 @@ export const PageTTS = ({
...ttsState,
inferenceTokens: [
...ttsState.inferenceTokens,
res.inference_job_token
res.inference_job_token,
],
});
@@ -74,21 +66,24 @@ export const PageTTS = ({
return (
<>
<Label className="mb-1">Select a Voice</Label>
<div
className="p-3 bg-brand-secondary rounded-lg flex justify-between items-center gap-3 cursor-pointer"
onClick={()=>changePage(AudioTabPages.SELECT_TTS_MODEL)}
>
<span className="bg-brand-secondary-600 rounded-lg w-12 h-12"/>
<Label>Select a Voice</Label>
<button
className="flex cursor-pointer items-center justify-between gap-3 rounded-lg bg-brand-secondary p-3 text-start transition-all hover:bg-ui-controls-button/40"
onClick={() => changePage(AudioTabPages.SELECT_TTS_MODEL)}>
<span className="h-12 w-12 rounded-lg bg-ui-controls-button/100" />
<div className="grow">
{!ttsState.voice && <H4>None Selected</H4>}
{ttsState.voice && <>
<H4>{ttsState.voice.title}</H4>
<H6>by {ttsState.voice.creator_display_name}</H6>
</>}
{ttsState.voice && (
<>
<H4>{ttsState.voice.title}</H4>
<H6 className="text-white/70">
by {ttsState.voice.creator_display_name}
</H6>
</>
)}
</div>
<FontAwesomeIcon icon={faChevronRight} size="2x"/>
</div>
<FontAwesomeIcon icon={faChevronRight} className="text-xl opacity-60" />
</button>
<div className="mt-4 w-full">
<Textarea
@@ -101,7 +96,7 @@ export const PageTTS = ({
</div>
<Button
className="h-11 w-full text-sm mt-4"
className="mt-4 h-11 w-full text-sm"
variant="primary"
disabled={ttsState.text === ""}
icon={faVolumeHigh}
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback } from "react";
import { v4 as uuidv4 } from "uuid";
import { AudioTabPages, V2VState } from "./types";
@@ -9,9 +9,12 @@ import {
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EnqueueVoiceConversionRequest, EnqueueVoiceConversionResponse } from './typesImported';
import { GenerateVoiceConversion } from './utilities';
import { H4, H6, Button } from "~/components";
import {
EnqueueVoiceConversionRequest,
EnqueueVoiceConversionResponse,
} from "./typesImported";
import { GenerateVoiceConversion } from "./utilities";
import { H4, H6, Button, Label } from "~/components";
import UploadComponent from "~/components/UploadComponent";
@@ -20,74 +23,85 @@ export const PageVoicetoVoice = ({
sessionToken,
v2vState,
setV2VState,
}:{
changePage: (newPage:AudioTabPages) => void;
}: {
changePage: (newPage: AudioTabPages) => void;
sessionToken: string;
v2vState : V2VState;
setV2VState : (newState:V2VState)=>void,
v2vState: V2VState;
setV2VState: (newState: V2VState) => void;
}) => {
const requestV2V = useCallback((sessionToken: string)=>{
const modelToken = v2vState.voice
? v2vState.voice.token
: undefined;
if(modelToken && v2vState.inputFileToken){
let request: EnqueueVoiceConversionRequest = {
uuid_idempotency_token: uuidv4(),
voice_conversion_model_token: modelToken,
source_media_upload_token: v2vState.inputFileToken,
};
const requestV2V = useCallback(
(sessionToken: string) => {
const modelToken = v2vState.voice ? v2vState.voice.token : undefined;
if (modelToken && v2vState.inputFileToken) {
let request: EnqueueVoiceConversionRequest = {
uuid_idempotency_token: uuidv4(),
voice_conversion_model_token: modelToken,
source_media_upload_token: v2vState.inputFileToken,
};
GenerateVoiceConversion(request, sessionToken).then(
(res:EnqueueVoiceConversionResponse)=>{
if (res && res.inference_job_token) {
setV2VState({
...v2vState,
inferenceTokens: [
...v2vState.inferenceTokens,
res.inference_job_token
],
});
changePage(AudioTabPages.LIBRARY);
}
}
);
}
}, [v2vState]);
GenerateVoiceConversion(request, sessionToken).then(
(res: EnqueueVoiceConversionResponse) => {
if (res && res.inference_job_token) {
setV2VState({
...v2vState,
inferenceTokens: [
...v2vState.inferenceTokens,
res.inference_job_token,
],
});
changePage(AudioTabPages.LIBRARY);
}
},
);
}
},
[v2vState],
);
return (
<>
<H4 className="mb-1">Select a Voice</H4>
<div
className="p-3 bg-brand-secondary rounded-lg flex justify-between items-center gap-3 cursor-pointer"
onClick={()=>changePage(AudioTabPages.SELECT_V2V_MODEL)}
>
<span className="bg-brand-secondary-600 rounded-lg w-12 h-12"/>
<div className="grow">
{!v2vState.voice && <H4>None Selected</H4>}
{v2vState.voice && <>
<H4>{v2vState.voice.title}</H4>
<H6>by {v2vState.voice.creator.display_name}</H6>
</>}
</div>
<FontAwesomeIcon icon={faChevronRight} size="2x"/>
<div className="flex flex-col gap-4">
<div className="flex flex-col">
<Label>Select a Voice</Label>
<button
className="flex cursor-pointer items-center justify-between gap-3 rounded-lg bg-brand-secondary p-3 text-start transition-all hover:bg-ui-controls-button/40"
onClick={() => changePage(AudioTabPages.SELECT_V2V_MODEL)}>
<span className="h-12 w-12 rounded-lg bg-ui-controls-button/100" />
<div className="grow">
{!v2vState.voice && <H4>None Selected</H4>}
{v2vState.voice && (
<>
<H4>{v2vState.voice.title}</H4>
<H6 className="text-white/70">
by {v2vState.voice.creator.display_name}
</H6>
</>
)}
</div>
<FontAwesomeIcon
icon={faChevronRight}
className="text-xl opacity-60"
/>
</button>
</div>
<H4 className="mt-4 mb-2">Upload Audio</H4>
<UploadComponent
sessionToken={sessionToken}
file={v2vState.file}
onFileStaged={(file:File)=>{
setV2VState({...v2vState, file: file});
}}
onClear={()=>{
setV2VState({...v2vState, file: undefined});
}}
onFileUploaded={(fileToken:string)=>{
setV2VState({
...v2vState,
inputFileToken: fileToken
});
}}
/>
<div className="flex flex-col">
<Label>Upload Audio</Label>
<UploadComponent
sessionToken={sessionToken}
file={v2vState.file}
onFileStaged={(file: File) => {
setV2VState({ ...v2vState, file: file });
}}
onClear={() => {
setV2VState({ ...v2vState, file: undefined });
}}
onFileUploaded={(fileToken: string) => {
setV2VState({
...v2vState,
inputFileToken: fileToken,
});
}}
/>
</div>
{/* <div className="my-4 flex w-full gap-4 justify-between items-center">
<span className="bg-brand-secondary grow h-1"/>
<H4>OR</H4>
@@ -103,13 +117,13 @@ export const PageVoicetoVoice = ({
Record Audio
</Button> */}
<Button
className="h-11 w-full text-sm mt-4"
className="w-full py-3 text-sm"
variant="primary"
disabled={!v2vState.voice || !v2vState.inputFileToken}
icon={faRightLeft}
onClick={()=>requestV2V(sessionToken)}>
onClick={() => requestV2V(sessionToken)}>
Convert
</Button>
</>
</div>
);
}
};
@@ -3,31 +3,30 @@ import { H4, H6 } from "~/components";
import { VoiceConversionModelListItem } from "./typesImported";
export const VoiceModelElement = ({
model,
onSelect
}:{
onSelect,
}: {
model: TtsModelListItem | VoiceConversionModelListItem;
onSelect:(item:TtsModelListItem | VoiceConversionModelListItem)=>void
})=>{
let creatorName: string|undefined;
if ("creator_display_name" in model){
onSelect: (item: TtsModelListItem | VoiceConversionModelListItem) => void;
}) => {
let creatorName: string | undefined;
if ("creator_display_name" in model) {
//case of TTS Models
creatorName = model.creator_display_name;
}else if("creator" in model){
} else if ("creator" in model) {
//case of V2V Models
creatorName = model.creator.display_name
}else {
creatorName = model.creator.display_name;
} else {
creatorName = undefined;
}
return(
<div
className="p-3 bg-brand-secondary rounded-lg flex justify-between items-center gap-3 cursor-pointer border-2 border-brand-secondary hover:border-brand-secondary-700"
onClick={()=>onSelect(model)}
>
<span className="bg-brand-secondary-600 rounded-lg w-12 h-12"/>
<div className="grow">
<H4>{model.title}</H4>
{creatorName && <H6>{creatorName}</H6>}
</div>
</div>
return (
<button
className="flex cursor-pointer items-center justify-between gap-3 rounded-lg border-2 border-transparent bg-brand-secondary p-3 text-start transition-all hover:border-ui-controls-button hover:bg-ui-controls-button/40"
onClick={() => onSelect(model)}>
<span className="h-12 w-12 rounded-lg bg-ui-controls-button/100" />
<div className="grow">
<H4>{model.title}</H4>
{creatorName && <H6 className="text-white/70">{creatorName}</H6>}
</div>
</button>
);
}
};
@@ -14,9 +14,10 @@ export const SidePanelTabs = () => {
<div
key={tab.value}
className={
tab.value === selectedTab.value?.value ? "h-full flex flex-col gap-4" : "hidden"
}
>
tab.value === selectedTab.value?.value
? "flex h-full flex-col gap-3.5 overflow-y-auto"
: "hidden"
}>
{tab.component}
</div>
))}
@@ -1,11 +1,10 @@
import { faAngleLeft, faClose } from "@fortawesome/pro-solid-svg-icons";
import { faChevronLeft, faClose } from "@fortawesome/pro-solid-svg-icons";
import { ButtonIcon } from "~/components";
import {
lastSelectedTab,
selectedTab,
sidePanelVisible,
} from "~/pages/PageEnigma/store";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
interface Props {
title: string;
@@ -20,12 +19,14 @@ export function TabTitle({ title, onBack }: Props) {
};
return (
<div className="flex items-center justify-between px-4">
<div className="flex items-center justify-between px-4 pt-4">
{onBack ? (
<div className="flex items-center gap-2">
<button onClick={onBack}>
<FontAwesomeIcon icon={faAngleLeft} />
</button>
<div className="flex items-center gap-3">
<ButtonIcon
onClick={onBack}
icon={faChevronLeft}
className="h-auto w-auto text-xl opacity-50 hover:opacity-90"
/>
<div className="align-middle text-base font-bold">{title}</div>
</div>
) : (
@@ -34,8 +35,7 @@ export function TabTitle({ title, onBack }: Props) {
<ButtonIcon
onClick={onClose}
icon={faClose}
size="lg"
className="h-auto w-auto opacity-75 hover:opacity-50"
className="h-auto w-auto text-xl opacity-50 hover:opacity-90"
/>
</div>
);
@@ -13,38 +13,42 @@ export const AnimationTab = () => {
return (
<>
<TabTitle title="Animation" />
<div className="w-full h-48 overflow-x-auto px-4 flex items-center justify-start gap-2">
<button
className={twMerge(
"filter-tab",
animationFilter.value === AssetFilterOption.ALL ? "active" : "",
)}
onClick={() => (animationFilter.value = AssetFilterOption.ALL)}>
All
</button>
<button
className={twMerge(
"filter-tab",
animationFilter.value === AssetFilterOption.MINE ? "active" : "",
)}
onClick={() => (animationFilter.value = AssetFilterOption.MINE)}
disabled={!animationItems.value.some((item) => item.isMine)}>
My Animations
</button>
<button
className={twMerge(
"filter-tab",
animationFilter.value === AssetFilterOption.BOOKMARKED
? "active"
: "",
)}
onClick={() =>
(animationFilter.value = AssetFilterOption.BOOKMARKED)
}
disabled={!animationItems.value.some((item) => item.isBookmarked)}>
Bookmarked
</button>
<div>
<div className="flex gap-2 overflow-x-auto overflow-y-hidden px-4">
<button
className={twMerge(
"filter-tab",
animationFilter.value === AssetFilterOption.ALL ? "active" : "",
)}
onClick={() => (animationFilter.value = AssetFilterOption.ALL)}>
All
</button>
<button
className={twMerge(
"filter-tab",
animationFilter.value === AssetFilterOption.MINE ? "active" : "",
)}
onClick={() => (animationFilter.value = AssetFilterOption.MINE)}
disabled={!animationItems.value.some((item) => item.isMine)}>
My Animations
</button>
<button
className={twMerge(
"filter-tab",
animationFilter.value === AssetFilterOption.BOOKMARKED
? "active"
: "",
)}
onClick={() =>
(animationFilter.value = AssetFilterOption.BOOKMARKED)
}
disabled={!animationItems.value.some((item) => item.isBookmarked)}>
Bookmarked
</button>
</div>
</div>
<div className="w-full px-4">
<Button
icon={faCirclePlus}
@@ -53,7 +57,7 @@ export const AnimationTab = () => {
Upload Animation
</Button>
</div>
<div className="grow flex w-full overflow-y-auto px-4 pb-4">
<div className="w-full grow overflow-y-auto px-4 pb-4">
<ItemElements
items={animationItems.value}
assetFilter={animationFilter.value}
@@ -110,9 +110,12 @@ export const ObjectsTab = ({ type }: Props) => {
{...{
onSuccess: reFetchList,
render: ({ parentId }: { parentId: string }) => (
<>
<div className="w-full overflow-x-auto">
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
<div className="flex h-full flex-col gap-3.5">
<TabTitle
title={`${type === AssetType.CHARACTER ? "Characters" : "Objects"}`}
/>
<div>
<div className="flex gap-2 overflow-x-auto overflow-y-hidden px-4">
<button
className={twMerge(
"filter-tab",
@@ -152,10 +155,10 @@ export const ObjectsTab = ({ type }: Props) => {
</button>
</div>
</div>
<div {...{ className: "w-full px-4 pb-4" }}>
<div {...{ className: "w-full px-4" }}>
<Button
{...{
className: "file-picker-button",
className: "file-picker-button py-3",
htmlFor: parentId,
icon: faCirclePlus,
variant: "action",
@@ -163,7 +166,7 @@ export const ObjectsTab = ({ type }: Props) => {
Upload {type === AssetType.CHARACTER ? "Character" : "Object"}
</Button>
</div>
<div className="w-full px-4">
<div className="w-full grow overflow-y-auto px-4 pb-4">
<ItemElements
{...{
...(type !== AssetType.CHARACTER
@@ -178,7 +181,7 @@ export const ObjectsTab = ({ type }: Props) => {
assetFilter={assetFilter.value}
/>
</div>
</>
</div>
),
}}
/>
@@ -43,7 +43,7 @@ export const Prompts = () => {
};
return (
<div className="flex flex-col gap-3 rounded-t-lg bg-ui-panel py-4">
<div className="flex flex-col gap-3 rounded-t-lg bg-ui-panel px-4">
<div className="relative w-full">
<Textarea
label="Enter a Prompt"
@@ -1,4 +1,4 @@
import { Button } from "~/components";
import { Button, Label } from "~/components";
import { faArrowsRotate, faFilm } from "@fortawesome/pro-solid-svg-icons";
import { editorState, EditorStates } from "~/pages/PageEnigma/store/engine";
import { useSignals } from "@preact/signals-react/runtime";
@@ -36,17 +36,15 @@ export function StyleButtons() {
console.log("state", editorState.value);
return (
<div className="flex w-full flex-col justify-center gap-4 rounded-b-lg bg-ui-panel">
<div className="flex w-full flex-col justify-center gap-4 rounded-b-lg bg-ui-panel px-4">
<div className="w-full">
{editorState.value === EditorStates.EDIT && (
<>
<div className="mb-2 text-sm font-medium">
Render the current camera view with AI
</div>
<Label>Render the current camera view with AI</Label>
<Button
icon={faArrowsRotate}
variant="primary"
className="w-full"
className="mt-1.5 w-full"
onClick={switchPreview}>
Preview Frame
</Button>
@@ -63,9 +61,9 @@ export function StyleButtons() {
)}
</div>
<div className="w-full">
<div className="text-sm font-medium">
<Label>
When you&apos;re done, render your entire animation with AI
</div>
</Label>
<div className="mb-2 text-xs text-white/70">
(This may take several minutes)
</div>
@@ -39,7 +39,7 @@ export const StyleSelection = () => {
};
return (
<div className="flex flex-col gap-4 rounded-t-lg bg-ui-panel">
<div className="flex flex-col gap-4 rounded-t-lg bg-ui-panel px-4">
<div className="flex flex-col">
<div className="relative">
<div
@@ -10,7 +10,7 @@ export function StylizeTab() {
return (
<div
className="overflow-y-auto overflow-x-hidden p-4"
className="flex flex-col gap-4"
style={{ height: sidePanelHeight.value }}>
<TabTitle title="Transform your animation with AI" />
<StyleSelection />