mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
implement v2v with the new audio upload in Api Manager, and big cleanup (#515)
- simplify the upload component in v2v because the api manager made it easier now - comb through all the old api files - since nothing refers to them anymore, they are deleted Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
1 parent
2630baeeb6
commit
865f009ee2
14 files changed
+52
-409
No files matched your search
@@ -1,34 +0,0 @@
|
||||
// interface ApiDetails { hostname: string, useSsl: boolean, }
|
||||
|
||||
const domainWithoutPort = document.location.host.split(":")[0];
|
||||
|
||||
const hostConfig = (host: string, useSsl = true) => ({
|
||||
formatUrl: (endpoint = "") => `${ useSsl ? "https" : "http" }://${ host + endpoint }`,
|
||||
host,
|
||||
useSsl,
|
||||
});
|
||||
|
||||
export default function GetApiHost() {
|
||||
switch (domainWithoutPort) {
|
||||
case "fakeyou.com":
|
||||
case "staging.fakeyou.com":
|
||||
return hostConfig("api.fakeyou.com");
|
||||
case "storyteller.ai":
|
||||
case "staging.storyteller.ai":
|
||||
//return hostConfig("api.storyteller.ai");
|
||||
return hostConfig("api.fakeyou.com");
|
||||
case "storyteller.stream": // Storyteller.stream is deprecated and will be decommissioned in the future.
|
||||
return hostConfig("api.storyteller.stream");
|
||||
case "devproxy.fakeyou.com":
|
||||
return hostConfig("api.fakeyou.com");
|
||||
case "devproxy.storyteller.ai":
|
||||
//return hostConfig("api.storyteller.ai");
|
||||
return hostConfig("api.fakeyou.com");
|
||||
case "dev.fakeyou.com":
|
||||
return hostConfig("api.dev.fakeyou.com:12345",false); // false disables SSL
|
||||
default:
|
||||
return document.location.host.includes("localhost") ?
|
||||
hostConfig("localhost:12345",document.location.protocol === "https:") :
|
||||
hostConfig("api.fakeyou.com"); // default
|
||||
}
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
const MakeMultipartRequest = (endpoint = "", body: any) => {
|
||||
const formData = new FormData();
|
||||
|
||||
Object.keys(body).forEach((key) => formData.append(key, body[key]));
|
||||
|
||||
formData.append("source", "file");
|
||||
|
||||
return fetch(`https://api.storyteller.ai${endpoint}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (res && res.success) {
|
||||
return res;
|
||||
} else {
|
||||
return { success: false };
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return { success: false };
|
||||
});
|
||||
};
|
||||
|
||||
export default MakeMultipartRequest;
|
||||
@@ -1,66 +0,0 @@
|
||||
import { environmentVariables } from "~/signals";
|
||||
|
||||
type UrlRoutingFunction<UrlRouteArgs> = (urlRouteArgs: UrlRouteArgs) => string;
|
||||
|
||||
interface RouteSetup<UrlRouteArgs> {
|
||||
method: string;
|
||||
multipart?: boolean;
|
||||
routingFunction: UrlRoutingFunction<UrlRouteArgs>;
|
||||
}
|
||||
|
||||
const METHOD_OMITS_BODY: { [key: string]: boolean } = {
|
||||
DELETE: false,
|
||||
GET: true,
|
||||
OPTIONS: true,
|
||||
PATCH: false,
|
||||
POST: false,
|
||||
PUT: false,
|
||||
};
|
||||
|
||||
const MakeRequest = <UrlRouteArgs, Request, Response, UrlParams>(
|
||||
routeSetup: RouteSetup<UrlRouteArgs>,
|
||||
): ((
|
||||
urlRouteArgs: UrlRouteArgs,
|
||||
request: Request,
|
||||
queries?: UrlParams,
|
||||
) => Promise<Response>) => {
|
||||
return async function (
|
||||
urlRouteArgs: UrlRouteArgs,
|
||||
request: Request,
|
||||
queries?: any,
|
||||
): Promise<Response> {
|
||||
const newQueries = queries
|
||||
? Object.keys(queries)
|
||||
.map((key, i) => {
|
||||
return `${i ? "&" : ""}${key}=${Array.isArray(queries[key]) ? queries[key].join(`&${key}=`) : queries[key]}`;
|
||||
})
|
||||
.join("")
|
||||
: null;
|
||||
|
||||
const endpoint = `${routeSetup.routingFunction(urlRouteArgs)}${newQueries ? "?" + newQueries : ""}`;
|
||||
const method = routeSetup.method;
|
||||
const methodOmitsBody = METHOD_OMITS_BODY[method] || false;
|
||||
|
||||
// return fetch(formatUrl(endpoint), {
|
||||
return fetch(`${environmentVariables.value.BASE_API}${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
...(methodOmitsBody ? {} : { "Content-Type": "application/json" }),
|
||||
},
|
||||
credentials: "include",
|
||||
...(methodOmitsBody ? {} : { body: JSON.stringify(request) }),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (!res.success && res && (res.voice_token || res.dataset_token)) {
|
||||
return res;
|
||||
} else if (res && "success" in res) {
|
||||
return res;
|
||||
} else Promise.reject();
|
||||
})
|
||||
.catch(() => ({ success: false }));
|
||||
};
|
||||
};
|
||||
|
||||
export default MakeRequest;
|
||||
@@ -1,26 +0,0 @@
|
||||
import MakeRequest from "../MakeRequest";
|
||||
// import { Weight } from "../weights/GetWeight";
|
||||
|
||||
export interface UserBookmarksListRequest {}
|
||||
|
||||
export interface UserBookmarksListResponse {
|
||||
// pagination: Pagination,
|
||||
success: boolean;
|
||||
// results: Weight[];
|
||||
}
|
||||
|
||||
export interface UserBookmarksListQueries {
|
||||
page_index?: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export const GetBookmarksByUser = MakeRequest<
|
||||
string,
|
||||
UserBookmarksListRequest,
|
||||
UserBookmarksListResponse,
|
||||
UserBookmarksListQueries
|
||||
>({
|
||||
method: "GET",
|
||||
routingFunction: (userToken: string) =>
|
||||
`/v1/user_bookmarks/list/user/${userToken}`,
|
||||
});
|
||||
@@ -19,7 +19,7 @@ export const apiHost = "https://api.storyteller.ai";
|
||||
//V2V
|
||||
// export const listV2V = `${apiHost}/v1/voice_conversion/model_list`;
|
||||
// export const inferV2V = `${apiHost}/v1/voice_conversion/inference`;
|
||||
export const uploadAudioV2V = `${apiHost}/v1/media_uploads/upload_audio`;
|
||||
// export const uploadAudioV2V = `${apiHost}/v1/media_uploads/upload_audio`;
|
||||
|
||||
// User Media
|
||||
// export const listMediaByUser = (username: string) =>
|
||||
@@ -1,14 +0,0 @@
|
||||
import MakeRequest from "../MakeRequest";
|
||||
|
||||
export interface EditCoverImageRequest {
|
||||
cover_image_media_file_token: string
|
||||
}
|
||||
|
||||
export interface EditCoverImageResponse {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export const EditCoverImage = MakeRequest<string, EditCoverImageRequest, EditCoverImageResponse,{}>({
|
||||
method: "POST",
|
||||
routingFunction: (media_token: string) => `/v1/media_files/cover_image/${ media_token }`,
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
import MakeMultipartRequest from "../MakeMultipartRequest";
|
||||
|
||||
export enum MediaFileEngineCategory {
|
||||
Scene = "scene",
|
||||
Character = "character",
|
||||
Animation = "animation",
|
||||
Object = "object",
|
||||
Skybox = "skybox",
|
||||
Expression = "expression",
|
||||
}
|
||||
|
||||
export enum MediaFileAnimationType {
|
||||
ar_kit = "ar_kit",
|
||||
miku_miku_dance = "miku_miku_dance",
|
||||
miku_miku_dance_ar_kit = "miku_miku_dance_ar_kit",
|
||||
mixamo = "mixamo",
|
||||
mixamo_ar_kit = "mixamo_ar_kit",
|
||||
mocap_net = "mocap_net",
|
||||
mocap_net_ar_kit = "mocap_net_ar_kit",
|
||||
move_ai = "move_ai",
|
||||
move_ai_ar_kit = "move_ai_ar_kit",
|
||||
rigify = "rigify",
|
||||
rigify_ar_kit = "rigify_ar_kit",
|
||||
}
|
||||
|
||||
export enum MediaFileSubtype {
|
||||
/// Animation file from Mixamo
|
||||
/// Primarily used for FBX and GLB.
|
||||
Mixamo = "mixamo",
|
||||
|
||||
/// Animation file from MocapNet
|
||||
/// Primarily used for BVH.
|
||||
MocapNet = "mocap_net",
|
||||
|
||||
/// Generic animation case
|
||||
/// Used for BVH files, but can also pertain to animation-only files of other types.
|
||||
AnimationOnly = "animation_only",
|
||||
|
||||
// TODO(bt,2024-03-08): Migrate records and code, then remove
|
||||
/// DEPRECATED: Use `SceneImport` instead.
|
||||
Scene = "scene",
|
||||
|
||||
/// Generic 3D scene file.
|
||||
/// Can pertain to glTF, glB, FBX, etc.
|
||||
SceneImport = "scene_import",
|
||||
|
||||
/// Native Storyteller scene format.
|
||||
/// Typically stored in a `.scn.ron` file.
|
||||
StorytellerScene = "storyteller_scene",
|
||||
}
|
||||
|
||||
export interface UploadEngineAssetRequest {
|
||||
engine_category: MediaFileEngineCategory;
|
||||
file: File;
|
||||
maybe_animation_type: MediaFileAnimationType | null;
|
||||
maybe_title?: string;
|
||||
maybe_visibility?: string;
|
||||
uuid_idempotency_token?: string;
|
||||
}
|
||||
|
||||
export interface UploadEngineAssetResponse {
|
||||
media_file_token: string;
|
||||
engine_category: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export const UploadEngineAsset = (request: UploadEngineAssetRequest) => {
|
||||
return MakeMultipartRequest("/v1/media_files/upload/engine_asset", request);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import MakeMultipartRequest from "../MakeMultipartRequest";
|
||||
|
||||
export interface UploadMediaRequest {
|
||||
uuid_idempotency_token?: string;
|
||||
file: File;
|
||||
source: string;
|
||||
title?: string;
|
||||
// dataset_token: string,
|
||||
}
|
||||
|
||||
export interface UploadMediaResponse {
|
||||
media_file_token: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export const UploadMedia = (request: UploadMediaRequest) => {
|
||||
return MakeMultipartRequest("/v1/media_files/upload", request);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
import MakeMultipartRequest from "../MakeMultipartRequest";
|
||||
|
||||
export enum MediaFileEngineCategory {
|
||||
Scene = "scene",
|
||||
Character = "character",
|
||||
Animation = "animation",
|
||||
Expression = "expression",
|
||||
Object = "object",
|
||||
Skybox = "skybox",
|
||||
}
|
||||
|
||||
export enum MediaFileAnimationType {
|
||||
ArKit = "ar_kit",
|
||||
MikuMikuDance = "miku_miku_dance",
|
||||
MikuMikuDanceArKit = "miku_miku_dance_ar_kit",
|
||||
Mixamo = "mixamo",
|
||||
MixamoArKit = "mixamo_ar_kit",
|
||||
MocapNet = "mocap_net",
|
||||
MocapNetArKit = "mocap_net_ar_kit",
|
||||
MoveAi = "move_ai",
|
||||
MoveAiArKit = "move_ai_ar_kit",
|
||||
Rigify = "rigify",
|
||||
RigifyArKit = "rigify_ar_kit",
|
||||
Rokoko = "rokoko",
|
||||
}
|
||||
|
||||
export interface UploadNewEngineAssetRequest {
|
||||
engine_category: MediaFileEngineCategory;
|
||||
file: File;
|
||||
maybe_animation_type?: MediaFileAnimationType;
|
||||
maybe_title?: string;
|
||||
maybe_visibility?: "public" | "private";
|
||||
maybe_duration_millis?: number;
|
||||
uuid_idempotency_token?: string;
|
||||
}
|
||||
|
||||
export interface UploadNewEngineAssetResponse {
|
||||
media_file_token: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export const UploadNewEngineAsset = (request: UploadNewEngineAssetRequest) => {
|
||||
return MakeMultipartRequest(
|
||||
"/v1/media_files/upload/new_engine_asset",
|
||||
request,
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { Fragment } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { faCheck, faChevronDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { MediaFileAnimationType } from "~/api/media_files/UploadNewEngineAsset";
|
||||
import { MediaFileAnimationType } from "~/enums";
|
||||
|
||||
export type SelectValue = string | number;
|
||||
export type SelectOption = { label: string; value: SelectValue };
|
||||
|
||||
@@ -86,7 +86,6 @@ export const UploadAudioButtonDialogue = ({
|
||||
}
|
||||
|
||||
//handle errors
|
||||
console.log(response.errorMessage);
|
||||
addToast(
|
||||
ToastTypes.ERROR,
|
||||
response.errorMessage || "Unknown Error: Upload Audio",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { FileUploader } from "react-drag-drop-files";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
@@ -11,32 +11,30 @@ import {
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { UploadAudio, UploadAudioIsOk, UploadAudioRequest } from "./utilities";
|
||||
|
||||
import { Button, ButtonIcon, P, Tooltip, WaveformPlayer } from "~/components";
|
||||
import { AUDIO_FILE_TYPE } from "~/enums";
|
||||
import { MediaUploadApi } from "~/Classes/ApiManager";
|
||||
import { getFileName } from "~/utilities";
|
||||
import { AUDIO_FILE_TYPE, ToastTypes } from "~/enums";
|
||||
import { addToast } from "~/signals";
|
||||
const FILE_TYPES = Object.keys(AUDIO_FILE_TYPE);
|
||||
|
||||
interface Props {
|
||||
file?: File;
|
||||
onFileStaged?: (file: File) => void;
|
||||
onClear?: () => void;
|
||||
onFileUploaded: (token: string) => void;
|
||||
}
|
||||
|
||||
export const UploadAudioComponent = ({
|
||||
file: propsFile,
|
||||
onFileStaged,
|
||||
onClear,
|
||||
onFileUploaded,
|
||||
}: Props) => {
|
||||
const [{ file, uploadState, uploadToken }, setState] = useState<{
|
||||
const [{ file, uploadState }, setState] = useState<{
|
||||
file: File | undefined;
|
||||
uploadState: "init" | "none" | "uploading" | "uploaded" | "error";
|
||||
uploadToken?: string;
|
||||
uploadState: "none" | "staged" | "uploading" | "uploaded" | "error";
|
||||
}>({
|
||||
file: propsFile,
|
||||
uploadState: propsFile ? "none" : "init",
|
||||
file: undefined,
|
||||
uploadState: "none",
|
||||
});
|
||||
|
||||
const audioUrl = file ? URL.createObjectURL(file) : "";
|
||||
@@ -45,8 +43,11 @@ export const UploadAudioComponent = ({
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
file: file,
|
||||
uploadState: "none",
|
||||
uploadState: "staged",
|
||||
}));
|
||||
if (onFileStaged) {
|
||||
onFileStaged(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
@@ -56,42 +57,43 @@ export const UploadAudioComponent = ({
|
||||
uploadState: "none",
|
||||
uploadToken: undefined,
|
||||
}));
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadFile = () => {
|
||||
if (file === undefined) return false;
|
||||
const handleUploadFile = useCallback(
|
||||
async (file: File) => {
|
||||
setState((curr) => ({ ...curr, uploadState: "uploading" }));
|
||||
|
||||
setState((curr) => ({ ...curr, uploadState: "uploading" }));
|
||||
const request: UploadAudioRequest = {
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
file: file,
|
||||
source: "file",
|
||||
};
|
||||
|
||||
UploadAudio(request).then((res) => {
|
||||
if (UploadAudioIsOk(res)) {
|
||||
const mediaUploadApi = new MediaUploadApi();
|
||||
const request = {
|
||||
blob: file,
|
||||
fileName: getFileName(file),
|
||||
uuid: uuidv4(),
|
||||
maybe_title: getFileName(file),
|
||||
};
|
||||
const response = await mediaUploadApi.UploadAudio(request);
|
||||
if (response.success && response.data) {
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
uploadState: "uploaded",
|
||||
uploadToken: res.upload_token,
|
||||
}));
|
||||
} else {
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
uploadState: "error",
|
||||
uploadToken: undefined,
|
||||
}));
|
||||
onFileUploaded(response.data);
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (file && uploadState === "none" && onFileStaged) onFileStaged(file);
|
||||
if (!file && uploadState === "none" && onClear) onClear();
|
||||
}, [file, uploadState]);
|
||||
useEffect(() => {
|
||||
if (uploadState === "uploaded" && uploadToken) onFileUploaded(uploadToken);
|
||||
}, [uploadToken, uploadState]);
|
||||
//handle errors
|
||||
addToast(
|
||||
ToastTypes.ERROR,
|
||||
response.errorMessage || "Unknown Error: Upload Audio",
|
||||
);
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
uploadState: "staged",
|
||||
}));
|
||||
},
|
||||
[onFileUploaded],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
@@ -123,8 +125,14 @@ export const UploadAudioComponent = ({
|
||||
<Button
|
||||
className="w-full py-2.5"
|
||||
variant="action"
|
||||
onClick={handleUploadFile}
|
||||
disabled={uploadState !== "none" && uploadState !== "error"}
|
||||
onClick={() => {
|
||||
if (!file) {
|
||||
//TODO: Maybe more verbose on not having file
|
||||
return false;
|
||||
}
|
||||
handleUploadFile(file);
|
||||
}}
|
||||
disabled={uploadState !== "staged"}
|
||||
icon={
|
||||
uploadState === "uploaded"
|
||||
? faCheck
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { uploadAudioV2V } from "~/api";
|
||||
export interface UploadAudioRequest {
|
||||
uuid_idempotency_token: string;
|
||||
source?: string; // eg. "device", "file"
|
||||
file: File;
|
||||
}
|
||||
|
||||
export interface UploadAudioResponse {
|
||||
success: boolean;
|
||||
upload_token?: string;
|
||||
}
|
||||
|
||||
export function UploadAudioIsOk(response: UploadAudioResponse) {
|
||||
return response?.success === true;
|
||||
}
|
||||
|
||||
export async function UploadAudio(
|
||||
request: UploadAudioRequest,
|
||||
): Promise<UploadAudioResponse> {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append("uuid_idempotency_token", request.uuid_idempotency_token);
|
||||
formData.append("file", request.file);
|
||||
|
||||
if (request.source !== undefined) {
|
||||
formData.append("source", request.source);
|
||||
}
|
||||
|
||||
return fetch(uploadAudioV2V, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (res && "success" in res) {
|
||||
return res;
|
||||
}
|
||||
return { success: false };
|
||||
})
|
||||
.catch(() => {
|
||||
return { success: false };
|
||||
});
|
||||
}
|
||||
@@ -92,20 +92,6 @@ export const PageVoicetoVoice = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="my-4 flex w-full gap-4 justify-between items-center">
|
||||
<span className="bg-brand-secondary grow h-1"/>
|
||||
<H4>OR</H4>
|
||||
<span className="bg-brand-secondary grow h-1"/>
|
||||
</div> */}
|
||||
{/* <Button
|
||||
className="h-11 w-full text-sm"
|
||||
variant="secondary"
|
||||
icon={faCircleDot}
|
||||
onClick={() => {
|
||||
console.log("Record Button is clicked");
|
||||
}}>
|
||||
Record Audio
|
||||
</Button> */}
|
||||
<Button
|
||||
className="w-full py-3 text-sm"
|
||||
variant="primary"
|
||||
|
||||
Reference in new issue
Block a user