Api2.5 / Upload Audio (#502)

implement ApiManager ver of UploadAudio
- refactor UploadAudioDialogue to use endpoint
- cherrypicked MediaUploadApi from api-manager-5
- code cleanup as we tread through the files
- delete old request
- patched ApiManager to have a multipart fetch

Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
wilandwil authored and GitHub committed 2024-06-20 12:37:46 -04:00
1 parent 7516ec9c05
commit 7cc6c019ce
11 files changed
+170 -152

No files matched your search

+26 -1
View File
@@ -64,6 +64,27 @@ export class ApiManager {
return response.json();
}
public async fetchMultipartFormData<T>(
endpoint: string,
{
method,
body,
}: {
method: string;
body: FormData;
},
): Promise<T> {
const response = await fetch(endpoint, {
method,
headers: {
Accept: "application/json",
},
credentials: "include",
body: body,
});
return response.json();
}
protected get<T>({
endpoint,
query,
@@ -129,7 +150,11 @@ export class ApiManager {
} else if (blob) {
formData.append("file", blob);
}
return this.fetch<FormData, T>(endpoint, {
// return this.fetch<FormData, T>(endpoint, {
// method: "POST",
// body: formData,
// });
return this.fetchMultipartFormData<T>(endpoint, {
method: "POST",
body: formData,
});
@@ -0,0 +1,52 @@
import { faFileArrowUp, faFileAudio } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { P } from "~/components";
export const DragAndDropZone = ({
file,
fileTypes,
}: {
file?: File;
fileTypes: string[];
}) => {
if (!file) {
return (
<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="flex items-center gap-2 text-sm font-normal opacity-50">
{fileTypes.join(", ").toString()} supported
</P>
</div>
</div>
);
}
const fileName = file.name.split(".")[0].toUpperCase();
const fileSize =
file.size >= 1024 * 1024
? (file.size / 1024 / 1024).toFixed(2) + " MB"
: `${Math.floor(file.size / 1024)} KB`;
return (
<div className="flex cursor-pointer items-center justify-between 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 grow flex-col gap-0">
<P className="font-medium">
{file.name.slice(0, file.name.lastIndexOf("."))}
</P>
<P className="flex items-center gap-2 text-sm font-normal">
<span className="opacity-50">
{`${fileName} file size: ${fileSize} `}
</span>
<u className="transition-all hover:text-white/80">Change File</u>
</P>
</div>
</div>
);
};
@@ -0,0 +1,23 @@
import { FileUploader as DragDropFileUploader } from "react-drag-drop-files";
// Usage refer to https://github.com/KarimMokhtar/react-drag-drop-files
import { DragAndDropZone } from "./DragAndDropZone";
export const FileUploader = ({
file,
fileTypes,
handleChange,
}: {
file: File | undefined;
handleChange: (file: File) => void;
fileTypes: string[];
}) => (
<DragDropFileUploader
handleChange={handleChange}
name="file"
maxSize={50}
types={fileTypes}
>
<DragAndDropZone file={file} fileTypes={fileTypes} />
</DragDropFileUploader>
);
@@ -0,0 +1 @@
export * from "./FileUploader";
@@ -63,7 +63,7 @@ export const TransitionDialogue = ({
}) => {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50 " onClose={onClose}>
<Dialog as="div" className="relative z-40 " onClose={onClose}>
<DialogBackdrop />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
+1
View File
@@ -6,6 +6,7 @@ export * from "./ButtonIcon";
export * from "./ButtonIconSelect";
export * from "./ButtonLink";
export * from "./ConfirmationModal";
export * from "./FileUploader";
export * from "./FileWrapper"; //need to rid of scss
export * from "./FilterButtons"; //this relies on a list of filters locally, could be more flexible
export * from "./Gravatar";
@@ -1,45 +1,28 @@
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import {
faCirclePlus,
faFileArrowUp,
faFileAudio,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { faCirclePlus, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FileUploader } from "react-drag-drop-files";
import {
Button,
FileUploader,
H2,
P,
TransitionDialogue,
WaveformPlayer,
} from "~/components";
import { UploadMedia, UploadMediaResponse } from "./utilities";
import { MediaUploadApi } from "~/Classes/ApiManager/MediaUploadApi";
import { AUDIO_FILE_TYPE, ToastTypes } from "~/enums";
export const FILE_TYPES = Object.keys(AUDIO_FILE_TYPE);
import { AUDIO_FILE_TYPE } from "~/enums";
const FILE_TYPES = Object.keys(AUDIO_FILE_TYPE);
// import { ListDropdown } from "../ListDropdown";
// const visiblityOptions = [
// {
// name: "private",
// value: "private",
// },
// {
// name: "public",
// value: "public",
// },
// ];
import { getFileName } from "~/utilities";
import { addToast } from "~/signals";
type ComponentState = {
isOpen: boolean;
file: File | undefined;
audioUrl: string;
uploadState: "init" | "staged" | "uploading" | "uploaded" | "error";
// visibility: string;
fileToken: string | undefined;
};
const initialValues: ComponentState = {
@@ -47,14 +30,9 @@ const initialValues: ComponentState = {
file: undefined,
audioUrl: "",
uploadState: "init",
// visibility: "private",
fileToken: undefined,
};
const getFileName = (file: File) => {
return file.name.split(".")[0] || "";
};
export const UploadAudioButtonDialogue = ({
onUploaded,
}: {
@@ -65,9 +43,7 @@ export const UploadAudioButtonDialogue = ({
const closeModal = () => setState(initialValues);
const openModal = () => setState((curr) => ({ ...curr, isOpen: true }));
// const selectVisibility = (value:string) => {
// setState((curr)=>({...curr, visibility:value}))
// };
const handleFileChange = (file: File) => {
setState((curr) => ({
...curr,
@@ -82,39 +58,52 @@ export const UploadAudioButtonDialogue = ({
isOpen: true,
});
};
const submitUpload = () => {
if (file) {
const submitUpload = async () => {
if (!file) {
//TODO: Maybe more verbose on not having file
return;
}
setState((curr) => ({
...curr,
uploadState: "uploading",
}));
const mediaApi = new MediaUploadApi();
const request = {
blob: file,
fileName: getFileName(file),
uuid: uuidv4(),
maybe_title: getFileName(file),
};
const response = await mediaApi.UploadAudio(request);
if (response.success && response.data) {
setState((curr) => ({
...curr,
uploadState: "uploading",
uploadState: "uploaded",
fileToken: response.data,
}));
UploadMedia({
uuid_idempotency_token: uuidv4(),
file: file,
source: "file",
title: getFileName(file),
}).then((res: UploadMediaResponse) => {
// console.log(res);
if ("media_file_token" in res) {
setState((curr) => ({
...curr,
uploadState: "uploaded",
fileToken: res.media_file_token,
}));
} else {
setState((curr) => ({
...curr,
uploadState: "error",
}));
}
});
return;
}
//handle errors
console.log(response.errorMessage);
addToast(
ToastTypes.ERROR,
response.errorMessage || "Unknown Error: Upload Audio",
);
setState((curr) => ({
...curr,
uploadState: "staged",
}));
};
useEffect(() => {
if (fileToken) {
if (fileToken && uploadState === "uploaded") {
//calling the callback on a successful upload
onUploaded(fileToken);
}
}, [fileToken]);
}, [fileToken, uploadState, onUploaded]);
return (
<>
<Button
@@ -133,12 +122,11 @@ export const UploadAudioButtonDialogue = ({
>
<div className="flex w-full flex-col gap-3">
<FileUploader
file={file}
fileTypes={FILE_TYPES}
handleChange={handleFileChange}
name="file"
types={FILE_TYPES}
maxSize={50}
children={<DragAndDropZone file={file} />}
/>
{file && (
<div className="flex items-center gap-3 rounded-lg bg-brand-secondary p-3">
<div className="grow">
@@ -146,7 +134,7 @@ export const UploadAudioButtonDialogue = ({
</div>
</div>
)}
{/* <ListDropdown list={visiblityOptions} onSelect={selectVisibility}/> */}
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={closeModal}>
{uploadState === "uploaded" ? "Close" : "Cancel"}
@@ -171,47 +159,3 @@ export const UploadAudioButtonDialogue = ({
</>
);
};
const DragAndDropZone = ({ file }: { file: File | undefined }) => {
const fileSize =
file && file.size >= 1024 * 1024
? (file.size / 1024 / 1024).toFixed(2) + " MB"
: file
? `${Math.floor(file.size / 1024)} KB`
: null;
const fileName = file && getFileName(file).toUpperCase();
if (!file) {
return (
<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="flex items-center gap-2 text-sm font-normal opacity-50">
{FILE_TYPES.join(", ").toString()} supported
</P>
</div>
</div>
);
} else {
return (
<div className="flex cursor-pointer items-center justify-between 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 grow flex-col gap-0">
<P className="font-medium">
{file.name.slice(0, file.name.lastIndexOf("."))}
</P>
<P className="flex items-center gap-2 text-sm font-normal">
<span className="opacity-50">
{`${fileName} file size: ${fileSize} `}
</span>
<u className="transition-all hover:text-white/80">Change File</u>
</P>
</div>
</div>
);
}
};
@@ -1,41 +0,0 @@
import { uploadMedia } from "~/api";
export interface UploadMediaRequest {
uuid_idempotency_token: string;
file: File;
source: string;
title: string;
}
export interface UploadMediaResponse {
media_file_token: string;
success: boolean;
}
export const UploadMedia = (request: UploadMediaRequest) => {
const formData = new FormData();
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",
headers: {
Accept: "application/json",
},
credentials: "include",
body: formData,
})
.then((res) => res.json())
.then((res) => {
if (res && res.success) {
return res;
}
return { success: false };
})
.catch((e) => {
return { success: false };
});
};
@@ -101,8 +101,9 @@ export const UploadAudioComponent = ({
name="file"
types={FILE_TYPES}
maxSize={50}
children={<DragAndDropZone file={file} />}
/>
>
<DragAndDropZone file={file} />
</FileUploader>
{file && (
<>
@@ -1,5 +1,9 @@
import { twMerge } from "tailwind-merge";
import { AudioPanelState, TtsState, V2VState } from "../../../../models/voice";
import {
AudioPanelState,
TtsState,
V2VState,
} from "~/pages/PageEnigma/models/voice";
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
import { PageTTS } from "./pageTTS";
+8
View File
@@ -29,3 +29,11 @@ export const isJobStatusTerminal = (curr: JobStatus) => {
}
return true;
};
export const getFileName = (file: File) => {
return file.name.split(".")[0] || "";
};
export const getFileTypesFromEnum = (enumOfTypes: object) => {
return Object.keys(enumOfTypes);
};