Audio Sidepanel v2.6 (#279)

- waveform now has a loading state
- audio library now has loading new items state
- fix so that the "New" state works on both inference created audio
items and uploaded audio items
- fix so that uploaded audio items takes the file name as its title

Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
wilandwil authored and GitHub committed 2024-04-30 14:04:20 -04:00
1 parent a63ca89eb7
commit b8f5cf4eaa
8 files changed
+96 -38

No files matched your search

+2 -1
View File
@@ -20,6 +20,7 @@ export const ButtonIcon = ({
className: propsClassName,
hoverFill = false,
disabled,
...rest
}: ButtonIconProps) => {
const className = twMerge(
"flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150",
@@ -31,7 +32,7 @@ export const ButtonIcon = ({
return (
<button className={className} onClick={onClick} disabled={disabled}>
<FontAwesomeIcon icon={icon} size={size} />
<FontAwesomeIcon icon={icon} size={size} {...rest}/>
</button>
);
};
@@ -19,7 +19,7 @@ import { AuthenticationContext } from "~/contexts/Authentication";
import { UploadMedia, UploadMediaResponse } from "./utilities";
// import { ListDropdown } from "../ListDropdown";
// const visiblityOpts = [
// const visiblityOptions = [
// {
// name: "private",
// value: "private",
@@ -36,7 +36,7 @@ type ComponentState = {
file: File | undefined;
audioUrl: string;
uploadState: "init" | "staged" | "uploading" | "uploaded" | "error";
visibility: string|undefined;
// visibility: string;
fileToken: string|undefined;
}
const initialValues:ComponentState = {
@@ -44,9 +44,14 @@ const initialValues:ComponentState = {
file: undefined,
audioUrl: "",
uploadState: "init",
visibility: undefined,
// visibility: "private",
fileToken: undefined,
}
const getFileName = (file:File)=>{
return file.name.split(".")[0] || "";
}
export const UploadAudioButtonDiagloue = ({
onUploaded,
}:{
@@ -57,9 +62,9 @@ export const UploadAudioButtonDiagloue = ({
const closeModal = () => setState(initialValues);
const openModal = () => setState((curr)=>({...curr, isOpen:true}));
const selectVisibility = (value:string) => {
setState((curr)=>({...curr, visibility:value}))
};
// const selectVisibility = (value:string) => {
// setState((curr)=>({...curr, visibility:value}))
// };
const handleFileChange = (file: File) => {
setState((curr) => ({
...curr,
@@ -84,6 +89,7 @@ export const UploadAudioButtonDiagloue = ({
uuid_idempotency_token: uuidv4(),
file: file,
source: "file",
title: getFileName(file),
}, authState.sessionToken)
.then((res: UploadMediaResponse) => {
// console.log(res);
@@ -138,10 +144,10 @@ export const UploadAudioButtonDiagloue = ({
</div>
</div>
)}
{/* <ListDropdown list={visiblityOpts} onSelect={selectVisibility}/> */}
{/* <ListDropdown list={visiblityOptions} onSelect={selectVisibility}/> */}
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={closeModal}>
Cancel
{uploadState === "uploaded" ? "Close" : "Cancel"}
</Button>
{uploadState !== "uploaded" &&
<Button
@@ -178,10 +184,7 @@ const DragAndDropZone = ({
? `${Math.floor(file.size / 1024)} KB`
: null;
const fileName =
file && file.name
? file.name.split(".")[0].toUpperCase()
: "";
const fileName = file && getFileName(file).toUpperCase();
if (!file) {
return (
@@ -1,9 +1,10 @@
import { uploadMedia } from "~/api";
export interface UploadMediaRequest {
uuid_idempotency_token: string,
file: File,
source: string
uuid_idempotency_token: string;
file: File;
source: string;
title: string;
}
export interface UploadMediaResponse {
@@ -18,6 +19,7 @@ export const UploadMedia = (request: UploadMediaRequest, sessionToken:string) =>
formData.append('uuid_idempotency_token', request.uuid_idempotency_token);
formData.append('file', request.file);
formData.append('source', request.source);
formData.append('title', request.title);
return fetch(uploadMedia, {
method: 'POST',
+45 -8
View File
@@ -1,9 +1,12 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { twMerge } from "tailwind-merge";
import WaveSurfer from "wavesurfer.js";
import { faCirclePlay, faCirclePause } from "@fortawesome/pro-solid-svg-icons";
import { faCirclePlay, faCirclePause, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { ButtonIcon } from "~/components";
import { environmentVariables } from "~/store";
import { useSignals } from "@preact/signals-react/runtime";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export const WaveformPlayer = ({
hasPlayButton,
@@ -16,10 +19,27 @@ export const WaveformPlayer = ({
}) => {
useSignals();
const waveSurferRef = useRef<WaveSurfer | undefined>(undefined);
const [isPlaying, toggleIsPlaying] = useState<boolean>(false);
const [{isPlaying, isLoading}, setState] = useState<{
isLoading:boolean;
isPlaying:boolean;
}>({
isLoading: true,
isPlaying: false,
});
const containerRef = useCallback((node: HTMLDivElement) => {
const toggleIsPlaying = (newIsPlaying:boolean)=>{
setState((curr)=>({...curr, isPlaying:newIsPlaying}));
}
const toggleIsLoading = (newIsLoading:boolean)=>{
setState((curr)=>({...curr, isLoading:newIsLoading}));
}
if (node) {
if(waveSurferRef.current){
//need to destroy previous wavesurfer to not have doubles
waveSurferRef.current.destroy();
toggleIsLoading(true);
}
const waveSurfer = WaveSurfer.create({
container: node,
barWidth: 2,
@@ -35,10 +55,9 @@ export const WaveformPlayer = ({
waveSurfer.load(newUrl);
waveSurfer.on("ready", () => {
if(waveSurferRef.current){
waveSurferRef.current.destroy();
}
waveSurferRef.current = waveSurfer;
// console.log("here");
toggleIsLoading(false);
if(onLoad) onLoad({duration: waveSurfer.getDuration()});
});
waveSurfer.on("play", () => {
@@ -59,16 +78,34 @@ export const WaveformPlayer = ({
return (
<div className="flex items-center gap-2 py-1">
{hasPlayButton &&
<ButtonIcon
icon={isPlaying ? faCirclePause : faCirclePlay}
icon={
isLoading ? faSpinnerThird :
isPlaying ? faCirclePause : faCirclePlay
}
className="w-auto bg-transparent p-0 text-2xl hover:bg-transparent hover:opacity-75"
spin={isLoading}
onClick={() => {
waveSurferRef.current?.playPause();
if (!isLoading) waveSurferRef.current?.playPause();
}}
/>
}
<div ref={containerRef} className="grow" />
<div className="grow relative h-10 overflow-hidden">
<span className={ twMerge(
"absolute w-full border-t-white border-t border-dotted top-[18px] transition-opacity",
isLoading ? "opacity-100" : "opacity-0"
)}/>
<div
ref={containerRef}
className={twMerge(
"absolute w-full top-[7px] transition-opacity",
isLoading ? "opacity-0" : "opacity-100"
)}
/>
</div>
</div>
);
};
@@ -8,7 +8,7 @@ import {
JobState,
InferenceJobType,
} from "~/pages/PageEnigma/models";
import { audioItemsFromServer } from "~/pages/PageEnigma/store/mediaFromServer";
import { audioItemsFromServer, isRetreivingAudioItems } from "~/pages/PageEnigma/store/mediaFromServer";
import {
ListAudioByUser,
ListTtsModels,
@@ -50,6 +50,7 @@ export const AudioTab = () => {
const handleListAudioByUser = useCallback((username:string, sessionToken:string)=>{
function getTitle (item:any){
console.log(item);
if (item.maybe_title) return item.maybe_title;
if (item.origin && item.origin.maybe_model && item.origin.maybe_model.title) return item.origin.maybe_model.title;
return "Media Audio";
@@ -59,22 +60,22 @@ export const AudioTab = () => {
if(item.origin_category) return item.origin_category;
return "unknown";
}
function checkIsNew(token:string){
const findNewItem = inferenceJobs.value.find((job=>{
if(job.result && job.result.entity_token){
return token === job.result.entity_token;
}else {
return false;
}
}))
return findNewItem !== undefined;
function checkIsNew(previousItems:AudioMediaItem[],token:string){
const foundItem = previousItems.find((item=>{
return token === item.media_id && item.isNew !== true;
}));
const foundNewItem = foundItem === undefined;
return foundNewItem;
}
function getLength(item:any){
return item.maybe_duration_millis
? item.maybe_duration_millis / 1000 * 60
: undefined;
}
isRetreivingAudioItems.value = true;
ListAudioByUser(username, sessionToken).then((res:MediaFile[])=>{
isRetreivingAudioItems.value = false;
const previousItems = [...audioItemsFromServer.value];
audioItemsFromServer.value = res.map(item=>{
const morphedItem:AudioMediaItem = {
version: 1,
@@ -88,7 +89,9 @@ export const AudioTab = () => {
length: getLength(item),
thumbnail: "/resources/placeholders/audio_placeholder.png",
isMine: true,
isNew: checkIsNew(item.token),
isNew: previousItems.length > 0
? checkIsNew(previousItems,item.token)
: false,
// isBookmarked?: boolean;
}
return morphedItem;
@@ -1,6 +1,11 @@
import { useSignals } from "@preact/signals-react/runtime";
import { isRetreivingAudioItems } from "~/pages/PageEnigma/store";
import { AudioItemElement } from "./audioItemElement";
import { AssetFilterOption, AudioMediaItem } from "~/pages/PageEnigma/models";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AudioMediaItem } from "~/pages/PageEnigma/models";
import { H6 } from "~/components";
interface Props {
currentPage: number;
@@ -13,9 +18,15 @@ export const AudioItemElements = ({
pageSize,
items,
}: Props) => {
useSignals();
return (
<div className="grid grid-cols-1 gap-2.5">
{isRetreivingAudioItems.value &&
<div className="w-full flex gap-3 justify-center items-center">
<FontAwesomeIcon icon={faSpinnerThird} spin/>
<H6>Retreiving New Audio Items</H6>
</div>
}
{items
.slice(currentPage*pageSize, (currentPage+1)*pageSize)
.map((item) => (
@@ -7,7 +7,6 @@ import {
inferV2V,
} from "~/api";
import {
MediaFile,
GetMediaFileResponse,
VoiceConversionModelListItem,
VoiceConversionModelListResponse,
@@ -1,6 +1,8 @@
import { signal } from "@preact/signals-core";
import { AudioMediaItem } from "~/pages/PageEnigma/models";
export const audioItemsFromServer = signal<AudioMediaItem[]>([]);
export const isRetreivingAudioItems = signal<boolean>(false);
export const cancelNewFromAudioItem = (mediaId:string)=>{
const newList = audioItemsFromServer.value.map((item)=>{