back merge bflat's style updates

This commit is contained in:
wil committed 2024-04-23 11:32:14 -04:00
commit e21d718fc0
6 files changed
+179 -170

No files matched your search

+8 -7
View File
@@ -16,12 +16,8 @@ export const Button = ({
disabled,
...rest
}: ButtonPropsI) => {
const variant = disabled ? "disabled" : propsVariant;
function getVariantClassNames(variant: string) {
switch (variant) {
case "disabled": {
return "bg-brand-secondary-500 text-brand-secondary-300";
}
case "secondary": {
return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
}
@@ -30,20 +26,25 @@ export const Button = ({
}
case "primary":
default: {
return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
return "bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
}
}
}
const disabledClass = twMerge(
disabled ? "opacity-40 pointer-events-none" : "",
);
const className = twMerge(
"text-sm font-medium rounded-lg px-3 py-2 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-all duration-150 flex gap-2 items-center justify-center",
getVariantClassNames(variant),
getVariantClassNames(propsVariant),
propsClassName,
disabledClass,
);
return (
<button className={className} disabled={disabled} {...rest}>
{icon && <FontAwesomeIcon icon={icon} size="sm"/>}
{icon && <FontAwesomeIcon icon={icon} size="sm" />}
{children}
</button>
);
+23 -22
View File
@@ -1,54 +1,55 @@
import { useCallback, useEffect, useState, useRef } from 'react';
import WaveSurfer from 'wavesurfer.js';
import { faCirclePlay, faCirclePause, } from "@fortawesome/pro-solid-svg-icons"
import { ButtonIcon } from '~/components';
import { useCallback, useEffect, useState, useRef } from "react";
import WaveSurfer from "wavesurfer.js";
import { faCirclePlay, faCirclePause } from "@fortawesome/pro-solid-svg-icons";
import { ButtonIcon } from "~/components";
export const WaveformPlayer = ({
audio
}:{
audio: string
}) => {
export const WaveformPlayer = ({ audio }: { audio: string }) => {
const waveSurferRef = useRef<WaveSurfer | undefined>(undefined);
const [isPlaying, toggleIsPlaying] = useState(false);
const containerRef = useCallback((node:HTMLDivElement)=>{
if(node){
const containerRef = useCallback((node: HTMLDivElement) => {
if (node) {
const waveSurfer = WaveSurfer.create({
container: node,
barWidth: 2,
height: 30,
height: 24,
cursorWidth: 0,
waveColor: "#D7C8C8",
progressColor: "#FB8381",
});
waveSurfer.load(audio);
waveSurfer.on('ready', () => {
waveSurfer.on("ready", () => {
waveSurferRef.current = waveSurfer;
});
waveSurfer.on('play', () => {
waveSurfer.on("play", () => {
toggleIsPlaying(true);
});
waveSurfer.on('pause', () => {
waveSurfer.on("pause", () => {
toggleIsPlaying(false);
});
waveSurfer.on("finish", () => {
waveSurfer.seekTo(0);
});
}
}, []);
useEffect(()=>{
return()=>{
useEffect(() => {
return () => {
console.log("OK?");
waveSurferRef.current?.destroy();
}
},[]);
};
}, []);
return (
<div className="flex py-1 gap-2">
<div className="flex items-center gap-2 py-1">
<ButtonIcon
icon={isPlaying ? faCirclePause : faCirclePlay}
size="2x"
className="w-auto bg-transparent p-0 text-2xl hover:bg-transparent hover:opacity-75"
onClick={() => {
waveSurferRef.current?.playPause();
}}
/>
<div ref={containerRef} className="w-full h-10"/>
<div ref={containerRef} className="w-full" />
</div>
);
};
@@ -11,7 +11,7 @@ import {
initPosition,
} from "~/pages/PageEnigma/store";
import { H4, H6, Pill } from "~/components";
import { H5, H6, Pill } from "~/components";
function getGcsUrl(bucketRelativePath: string | undefined | null): string {
let bucket = "vocodes-public";
@@ -79,20 +79,22 @@ export const AudioItemElement = ({ item }: Props) => {
return (
<div
className="relative w-full cursor-pointer rounded-lg transition-all duration-200"
onPointerDown={onPointerDown}
>
<div
className="flex flex-col p-2 gap-1 w-full rounded-lg bg-assets-background"
>
<div className="flex justify-between pb-1">
onPointerDown={onPointerDown}>
<div className="flex w-full flex-col gap-0.5 rounded-lg bg-assets-background p-2.5">
<div className="flex justify-between">
<Pill>Audio</Pill>
</div>
{item.publicBucketPath && <WaveformPlayer audio={getGcsUrl(item.publicBucketPath)} />}
<H4 className="text-overflow-ellipsis">{item.name}</H4>
{item.description && <H6 className="text-overflow-ellipsis">{item.description}</H6>}
{item.publicBucketPath && (
<WaveformPlayer audio={getGcsUrl(item.publicBucketPath)} />
)}
<H5 className="text-overflow-ellipsis">{item.name}</H5>
{item.description && (
<H6 className="text-overflow-ellipsis text-xs text-white/90">
{item.description}
</H6>
)}
</div>
</div>
);
@@ -7,7 +7,6 @@ interface Props {
}
export const AudioItemElements = ({ items, assetFilter }: Props) => {
const displayItems = items.filter((item) => {
if (assetFilter === AssetFilterOption.ALL) {
return true;
@@ -19,7 +18,7 @@ export const AudioItemElements = ({ items, assetFilter }: Props) => {
});
return (
<div className={`grid grid-cols-1 gap-3`}>
<div className={`grid grid-cols-1 gap-2.5`}>
{displayItems.map((item) => (
<AudioItemElement key={item.media_id} item={item} />
))}
@@ -10,25 +10,24 @@ import { AudioItemElements } from "./audioItemElements";
import { AudioTabPages } from "./types";
export const PageLibrary = ({
changePage
}:{
changePage: (newPage:AudioTabPages) => void
})=>{
changePage,
}: {
changePage: (newPage: AudioTabPages) => void;
}) => {
useSignals();
const allAudioItems = [...audioItems.value, ...audioItemsFromServer.value];
return (
<>
<div className="w-full overflow-x-auto">
<div className="mb-4 mt-4 flex justify-start items-center gap-2 px-4">
<div className="mb-4 mt-4 flex items-center justify-start gap-2 px-4">
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.ALL ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}
>
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}>
All
</button>
<button
@@ -38,8 +37,7 @@ export const PageLibrary = ({
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.MINE)}
disabled={!allAudioItems.some((item) => item.isMine)}
>
disabled={!allAudioItems.some((item) => item.isMine)}>
My Audios
</button>
<button
@@ -51,8 +49,7 @@ export const PageLibrary = ({
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.BOOKMARKED)}
disabled={!allAudioItems.some((item) => item.isBookmarked)}
>
disabled={!allAudioItems.some((item) => item.isBookmarked)}>
Bookmarked
</button>
</div>
@@ -62,13 +59,12 @@ export const PageLibrary = ({
icon={faCirclePlus}
variant="action"
className="w-full py-3 text-sm font-medium"
onClick={()=>changePage(AudioTabPages.TTS)}
>
onClick={() => changePage(AudioTabPages.TTS)}>
Generate Audio
</Button>
</div>
<div className="h-full w-full overflow-y-auto px-4 pt-4">
<div className="mt-4 h-full w-full overflow-y-auto px-4">
<AudioItemElements
items={allAudioItems}
assetFilter={audioFilter.value}
@@ -76,4 +72,4 @@ export const PageLibrary = ({
</div>
</>
);
}
};
@@ -4,22 +4,31 @@ import { v4 as uuidv4 } from "uuid";
import {
faChevronLeft,
faBrainCircuit,
faVolumeHigh,
} from "@fortawesome/pro-solid-svg-icons";
import {
Button, ButtonIcon, H2, Label,
import {
Button,
ButtonIcon,
H2,
Label,
LoadingDotsTyping,
ListSearchDropdown,
Textarea
Textarea,
} from "~/components";
import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager";
import { TtsModelListItem, GenerateTtsAudioResponse } from "~/pages/PageEnigma/models/tts";
import {
TtsModelListItem,
GenerateTtsAudioResponse,
} from "~/pages/PageEnigma/models/tts";
import { addInferenceJob, inferenceJobs} from "~/pages/PageEnigma/store/inferenceJobs";
import { addInferenceJob, inferenceJobs } from "~/pages/PageEnigma/store/inferenceJobs";
import { GenerateTtsAudio, GetMediaFileByToken } from "./utilities";
import {
GenerateTtsAudio,
GetMediaFileByToken,
} from "./utilities";
import { AudioTabPages } from "./types";
import { AudioItemElement } from "./audioItemElement";
import { MediaItem, AssetType } from "~/pages/PageEnigma/models";
@@ -27,22 +36,21 @@ import { MediaItem, AssetType } from "~/pages/PageEnigma/models";
type TtsState = {
voice: TtsModelListItem | undefined;
text: string;
hasEnqueued :boolean;
hasEnqueued: boolean;
inferenceToken?: string;
inferenceJobType?: string;
hasTtsResult: boolean;
}
const initialState:TtsState =
{
voice:undefined,
text:"",
hasEnqueued:false,
inferenceJobType: undefined,
inferenceToken: undefined,
hasTtsResult:false,
};
};
const initialState: TtsState = {
voice: undefined,
text: "",
hasEnqueued: false,
inferenceJobType: undefined,
inferenceToken: undefined,
hasTtsResult: false,
};
export const PageTTS =({
export const PageTTS = ({
changePage,
sessionToken,
voiceModels
@@ -53,9 +61,9 @@ export const PageTTS =({
})=>{
const [ttsState, setTtsState] = useState<TtsState>(initialState);
const [ttsResultFile, setTtsResultFile] = useState<MediaItem|undefined>();
const [ttsResultFile, setTtsResultFile] = useState<MediaItem | undefined>();
useSignalEffect(()=>{
useSignalEffect(() => {
console.log(inferenceJobs.value);
if(ttsState.hasEnqueued && ttsState.inferenceToken){
const found = inferenceJobs.value.find((job)=>job.job_id===ttsState.inferenceToken);
@@ -65,25 +73,26 @@ export const PageTTS =({
setTtsState((curr)=>({
...curr,
hasTtsResult: true,
}))
GetMediaFileByToken(found.result.entity_token, sessionToken)
.then(res=>{
console.log(res);
const morphedItem:MediaItem = {
version: 1,
type: AssetType.AUDIO,
media_id: res.media_file.token,
object_uuid: res.media_file.token,
name: ttsState.voice?.title || '',
description: ttsState.text,
publicBucketPath: res.media_file.public_bucket_path,
length: 25,
thumbnail: "/resources/placeholders/audio_placeholder.png",
isMine: true,
// isBookmarked?: boolean;
}
setTtsResultFile(morphedItem);
})
}));
GetMediaFileByToken(found.result.entity_token, sessionToken).then(
(res) => {
console.log(res);
const morphedItem: MediaItem = {
version: 1,
type: AssetType.AUDIO,
media_id: res.media_file.token,
object_uuid: res.media_file.token,
name: ttsState.voice?.title || "",
description: ttsState.text,
publicBucketPath: res.media_file.public_bucket_path,
length: 25,
thumbnail: "/resources/placeholders/audio_placeholder.png",
isMine: true,
// isBookmarked?: boolean;
};
setTtsResultFile(morphedItem);
},
);
}
}
});
@@ -92,52 +101,56 @@ export const PageTTS =({
if(ttsState.hasEnqueued && ttsState.inferenceToken && ttsState.inferenceJobType){
console.log(`tts has Enqueued`);
addInferenceJob({
version:1,
version: 1,
job_id: ttsState.inferenceToken,
job_type: ttsState.inferenceJobType,
job_status: JobState.PENDING,
})
}
},[ttsState]);
const requestTts = useCallback( (sessionToken:string)=>{
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, sessionToken).then((
res:GenerateTtsAudioResponse)=>{
if(res && 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]);
}, [ttsState]);
const handleTextInput = (
e: React.ChangeEvent<HTMLTextAreaElement>
)=>{
setTtsState((curr)=>({
const requestTts = useCallback(
(sessionToken: string) => {
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, sessionToken).then(
(res: GenerateTtsAudioResponse) => {
if (res && 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 handleTextInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTtsState((curr) => ({
...curr,
text: e.target.value,
}));
@@ -151,16 +164,17 @@ export const PageTTS =({
...curr,
voice: currVoiceModel,
}));
}
};
return(
return (
<div className="flex flex-col p-4">
<div className="flex items-center">
<div className="mb-5 flex items-center gap-3">
<ButtonIcon
className="w-auto p-0 text-xl opacity-60 hover:opacity-40"
icon={faChevronLeft}
onClick={()=>changePage(AudioTabPages.LIBRARY)}
onClick={() => changePage(AudioTabPages.LIBRARY)}
/>
<H2>Generate TTS</H2>
<H2 className="font-semibold">Generate TTS</H2>
</div>
<Label className="mb-1">Select a Voice</Label>
{voiceModels.length > 0 && <ListSearchDropdown
@@ -168,51 +182,47 @@ export const PageTTS =({
listDisplayKey="title"
onSelect={handleOnSelect}
/> }
<div className="flex w-full justify-between mt-4">
<div className="mt-4 flex w-full justify-between">
<Label>What would you like to say?</Label>
<div className="flex gap-2 items-center">
</div>
<div className="flex items-center gap-2"></div>
</div>
<Textarea
placeholder="Enter what you want the voice to say here."
value={ttsState.text}
onChange={handleTextInput}
rows={8}
/>
<div className="mt-6 flex gap-2">
<div className="w-full h-12">
{!ttsState.hasTtsResult && !ttsState.hasEnqueued &&
<div className="mt-4 flex gap-2">
<div className="h-auto w-full">
{!ttsState.hasTtsResult && !ttsState.hasEnqueued && (
<Button
className="w-36 h-full text-xl "
variant={ttsState.hasTtsResult ? "secondary" : "primary" }
className="h-11 w-full text-sm"
variant={ttsState.hasTtsResult ? "secondary" : "primary"}
disabled={ttsState.text === ""}
icon={faBrainCircuit}
onClick={()=>requestTts(sessionToken)}
>
icon={faVolumeHigh}
onClick={() => requestTts(sessionToken)}>
Generate
</Button>
}
{!ttsState.hasTtsResult && ttsState.hasEnqueued &&
<LoadingDotsTyping className="bg-brand-secondary-500 rounded-lg"/>
}
{ ttsResultFile &&
<AudioItemElement item={ttsResultFile}/>
}
)}
{!ttsState.hasTtsResult && ttsState.hasEnqueued && (
<LoadingDotsTyping className="rounded-lg bg-brand-secondary-500" />
)}
{ttsResultFile && <AudioItemElement item={ttsResultFile} />}
</div>
</div>
<div className="mt-6 flex justify-between gap-2">
{ ttsState.hasEnqueued &&
<div className="mt-4 flex justify-between gap-2">
{ttsState.hasEnqueued && (
<Button
className="h-11 w-full text-sm"
type="button"
disabled={!ttsState.hasTtsResult}
onClick={()=>requestTts(sessionToken)}
icon={faBrainCircuit}
>
disabled={!ttsState.hasTtsResult || ttsState.text === ""}
onClick={() => requestTts(sessionToken)}
icon={faVolumeHigh}>
Generate Another
</Button>
}
)}
</div>
</div>
);
}
};