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:
1 parent
a63ca89eb7
commit
b8f5cf4eaa
8 files changed
+96
-38
No files matched your search
@@ -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',
|
||||
|
||||
@@ -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)=>{
|
||||
|
||||
Reference in new issue
Block a user