mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 08:57:54 +00:00
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:
1 parent
7516ec9c05
commit
7cc6c019ce
11 files changed
+170
-152
No files matched your search
@@ -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">
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
Reference in new issue
Block a user