mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
back merge bflat's style updates
This commit is contained in:
commit
e21d718fc0
6 files changed
+179
-170
No files matched your search
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user