mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-10 20:11:49 +00:00
Listing voice designer samples + List Component cleanup
This commit is contained in:
1 parent
4684f566ef
commit
46fe2a481f
19 files changed
+253
-212
No files matched your search
@@ -36,6 +36,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-spring/types": "^9.7.3",
|
||||
"moment": "^2.29.4",
|
||||
"universal-cookie": "^6.1.1",
|
||||
"yarn": "^1.22.19"
|
||||
},
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import MakeRequest from "../../MakeRequest";
|
||||
|
||||
export interface DreateVoiceRequest {
|
||||
export interface DeleteSampleRequest {
|
||||
set_delete: boolean;
|
||||
as_mod: boolean;
|
||||
}
|
||||
|
||||
export interface DreateVoiceResponse {
|
||||
export interface DeleteSampleResponse {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export const DeleteDataset = MakeRequest<string, DreateVoiceRequest, DreateVoiceResponse>({
|
||||
export const DeleteSample = MakeRequest<string, DeleteSampleRequest, DeleteSampleResponse>({
|
||||
method: "DELETE",
|
||||
routingFunction: (sampleToken: string) => `/v1/voice_designer/sample/${ sampleToken }/delete`,
|
||||
});
|
||||
+2
-13
@@ -3,19 +3,8 @@ import MakeRequest from "../../MakeRequest";
|
||||
export interface ListSamplesForDatasetRequest {}
|
||||
|
||||
export interface ListSamplesForDatasetResponse {
|
||||
sample_token: string,
|
||||
|
||||
media_file_token: string,
|
||||
media_type: string,
|
||||
|
||||
public_bucket_directory_hash: string,
|
||||
maybe_public_bucket_prefix?: string,
|
||||
maybe_public_bucket_extension?: string,
|
||||
|
||||
maybe_creator_user_token?: string,
|
||||
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
samples: any,
|
||||
success: true
|
||||
}
|
||||
|
||||
export const ListSamplesForDataset = MakeRequest<string, ListSamplesForDatasetRequest, ListSamplesForDatasetResponse>({
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.fy-audio-blob {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { a, useTransition } from "@react-spring/web";
|
||||
import { InputVcAudioPlayer } from "v2/view/_common/InputVcAudioPlayer";
|
||||
import { FileActions } from "components/common";
|
||||
import "./AudioBlobPreview.scss";
|
||||
|
||||
interface Props {
|
||||
blob?: any,
|
||||
clear?: any,
|
||||
file?: any,
|
||||
hideActions: boolean,
|
||||
onRest?: () => void,
|
||||
success?: any,
|
||||
submit?: () => void,
|
||||
working?: any
|
||||
}
|
||||
|
||||
export default function AudioBlobPreview({ clear, blob, file, hideActions, onRest, success, submit, working }: Props) {
|
||||
const transitions = useTransition(file, {
|
||||
config: { tension: 120, friction: 15 },
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
leave: { opacity: 0 },
|
||||
onRest,
|
||||
});
|
||||
|
||||
return transitions((style, i) => i ? <a.div {...{ className: "audio-details", style }}>
|
||||
<div {...{ className: "fy-audio-blob panel" }}>
|
||||
<InputVcAudioPlayer {...{ filename: blob as string }}/>
|
||||
</div>
|
||||
{
|
||||
!hideActions && <FileActions {...{ clear, success, submit, working }}/>
|
||||
}
|
||||
</a.div> : null );
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './AudioBlobPreview';
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { a, useTransition } from '@react-spring/web';
|
||||
import { InputVcAudioPlayer } from "v2/view/_common/InputVcAudioPlayer";
|
||||
import { FileActions, FileDetails, FileLabel, FileWrapper } from 'components/common';
|
||||
// import { a, useTransition } from '@react-spring/web';
|
||||
// import { InputVcAudioPlayer } from "v2/view/_common/InputVcAudioPlayer";
|
||||
import { FileDetails, FileLabel, FileWrapper } from 'components/common';
|
||||
import { faFileAudio } from "@fortawesome/pro-solid-svg-icons";
|
||||
import './AudioInput.scss'
|
||||
|
||||
@@ -9,39 +9,38 @@ const fileTypes = ["MP3", "WAV", "FLAC", "OGG"];
|
||||
|
||||
interface Props {
|
||||
blob?: string;
|
||||
children?: any;
|
||||
clear?: (file?: any) => void;
|
||||
file?: any;
|
||||
hideActions?: boolean;
|
||||
hideDetails?: boolean;
|
||||
hideClearDetails?: boolean;
|
||||
inputProps?: any;
|
||||
onRest?: () => void;
|
||||
success?: boolean;
|
||||
submit?: () => void;
|
||||
working?: boolean;
|
||||
[x:string]: any;
|
||||
}
|
||||
|
||||
const n = () => {};
|
||||
|
||||
export default function AudioInput({ blob = "", clear = n, file, hideActions, hideClearDetails, inputProps, onRest = n, success = false, submit = n, working = false, ...rest }: Props) {
|
||||
export default function AudioInput({ children, clear = n, file, hideDetails, hideClearDetails, inputProps, ...rest }: Props) {
|
||||
|
||||
const transitions = useTransition(file, {
|
||||
config: { tension: 120, friction: 15 },
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
leave: { opacity: 0 },
|
||||
onRest,
|
||||
});
|
||||
// const transitions = useTransition(file, {
|
||||
// config: { tension: 120, friction: 15 },
|
||||
// from: { opacity: 0 },
|
||||
// enter: { opacity: 1 },
|
||||
// leave: { opacity: 0 },
|
||||
// onRest,
|
||||
// });
|
||||
|
||||
return <div {...{ className: "fy-audio-uploader" }}>
|
||||
<FileWrapper {...{ fileTypes, ...inputProps, panelClass: "p-3", ...rest }}>
|
||||
{ file ? <FileDetails {...{ clear, hideClearDetails, icon: faFileAudio, file }}/> : <FileLabel {...{ fileTypes }}/> }
|
||||
</FileWrapper>
|
||||
{ transitions((style, i) => i ? <a.div {...{ className: "audio-details", style }}>
|
||||
<div {...{ className: "panel panel-inner rounded p-3" }}>
|
||||
<InputVcAudioPlayer {...{ filename: blob as string }}/>
|
||||
</div>
|
||||
{ !hideActions && <FileActions {...{ clear, success, submit, working }}/> }
|
||||
</a.div> : null ) }
|
||||
{ children
|
||||
// transitions((style, i) => i ? <a.div {...{ className: "audio-details", style }}>
|
||||
// <div {...{ className: "panel panel-inner rounded p-3" }}>
|
||||
// <InputVcAudioPlayer {...{ filename: blob as string }}/>
|
||||
// </div>
|
||||
// { !hideActions && <FileActions {...{ clear, success, submit, working }}/> }
|
||||
// </a.div> : null )
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import AudioBlobPreview from "./AudioBlobPreview";
|
||||
import AudioInput from "./AudioInput";
|
||||
import BasicVideo from "./BasicVideo";
|
||||
import Button from "./Button";
|
||||
@@ -20,6 +21,7 @@ import TempInput from "./TempInput";
|
||||
import TempSelect from "./TempSelect";
|
||||
|
||||
export {
|
||||
AudioBlobPreview,
|
||||
AudioInput,
|
||||
BasicVideo,
|
||||
Button,
|
||||
|
||||
@@ -28,7 +28,7 @@ interface JobsListProps{
|
||||
export default function InferenceJobsList({ filter, onSelect, statusTxt, t, inferenceJobs }: JobsListProps) {
|
||||
// const { inferenceJobs = [] } = useInferenceJobs({ type: 0 });
|
||||
// const oldJobs = thejobs || inferenceJobs;
|
||||
console.log("🥬",inferenceJobs);
|
||||
|
||||
const [pending, pendingSet] = useState<GetPendingTtsJobCountSuccessResponse>({
|
||||
success: true,
|
||||
pending_job_count: 0,
|
||||
@@ -97,7 +97,6 @@ export default function InferenceJobsList({ filter, onSelect, statusTxt, t, infe
|
||||
{ jobs
|
||||
// .filter(filter)
|
||||
.map((job: any, key: number) => {
|
||||
console.log("🍇",job);
|
||||
return <div {...{ className: "panel face-animator-job", key }}>
|
||||
<FontAwesomeIcon {...{ className: `job-status-icon job-status-${job.statusIndex}`, icon: statusIcons[job.statusIndex] }}/>
|
||||
<div {...{ className: "job-details" }}>
|
||||
|
||||
@@ -12,21 +12,12 @@ interface Props {
|
||||
export default function useFile({ debug, onChange = n, onClear = n, onSubmit = n }: Props) {
|
||||
const [file, fileSet] = useState<any>(undefined);
|
||||
const [blob, blobSet] = useState<string>();
|
||||
const [status, statusSet] = useState<number>(0);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const submit = async () => {
|
||||
if (file === undefined) { return false; }
|
||||
statusSet(1);
|
||||
const onUploadResult = await onSubmit(file);
|
||||
if (onUploadResult) { statusSet(2); }
|
||||
else { statusSet(3); }
|
||||
};
|
||||
const fileChange = (inputFile?: any) => {
|
||||
onChange(file);
|
||||
onChange(inputFile);
|
||||
fileSet(inputFile || null);
|
||||
blobSet(inputFile ? URL.createObjectURL(inputFile) : "");
|
||||
statusSet(0);
|
||||
};
|
||||
const inputChange = ({ target = {} }: { target: any }) => {
|
||||
fileChange(target.value);
|
||||
@@ -35,7 +26,6 @@ export default function useFile({ debug, onChange = n, onClear = n, onSubmit = n
|
||||
if (inputRef?.current?.value) inputRef.current.value = '';
|
||||
fileChange();
|
||||
onClear();
|
||||
statusSet(0);
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -45,10 +35,6 @@ export default function useFile({ debug, onChange = n, onClear = n, onSubmit = n
|
||||
inputProps: {
|
||||
onChange: inputChange,
|
||||
inputRef
|
||||
},
|
||||
status,
|
||||
submit,
|
||||
success: status === 2,
|
||||
working: status === 1,
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { animated } from "@react-spring/web";
|
||||
import { AudioInput, BasicVideo, Checkbox, ImageInput, SegmentButtons } from "components/common";
|
||||
import { AudioBlobPreview, AudioInput, BasicVideo, Checkbox, ImageInput, SegmentButtons } from "components/common";
|
||||
import { FaceAnimatorSlide } from "./FaceAnimatorTypes";
|
||||
|
||||
export default function FaceAnimatorInput({
|
||||
@@ -35,9 +35,17 @@ export default function FaceAnimatorInput({
|
||||
onRest: (p: any, c: any, item: any, l: any) => {
|
||||
toggle.audio(!!audioProps.file);
|
||||
},
|
||||
hideActions: true,
|
||||
// hideActions: true,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<AudioBlobPreview {...{
|
||||
...audioProps,
|
||||
onRest: (p: any, c: any, item: any, l: any) => {
|
||||
toggle.audio(!!audioProps.file);
|
||||
},
|
||||
hideActions: true,
|
||||
}}/>
|
||||
</AudioInput>
|
||||
<BasicVideo {...{ className: "face-animator-wide-sample", src: "/videos/face-animator-instruction-en.mp4" }}/>
|
||||
</div>
|
||||
<div {...{ className: "animation-configure-panel panel" }}>
|
||||
|
||||
@@ -79,15 +79,13 @@ function VoiceDesignerFormPage({ enqueueInferenceJob }: { enqueueInferenceJob: a
|
||||
? ["Edit Details", "Edit Samples"]
|
||||
: ["Voice Details", "Upload Samples"];
|
||||
|
||||
const key = !isNewCreation && (dataset_token || uuidv4());
|
||||
// const key = !isNewCreation && (dataset_token || uuidv4());
|
||||
const displayStep = (step: any) => {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return <VoiceDetails {...{ datasetInputs }} />;
|
||||
case 1:
|
||||
return (
|
||||
<UploadSamples key={key as any} {...{ audioProps, datasetToken: dataset_token, setAudioSamplesReady }} />
|
||||
);
|
||||
return <UploadSamples {...{ audioProps, datasetToken: dataset_token, setAudioSamplesReady }}/>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@ import Container from "components/common/Container";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import ListItems from "./components/NewList";
|
||||
import Modal from "components/common/Modal";
|
||||
import { Button } from "components/common";
|
||||
import useVoiceRequests from "./useVoiceRequests";
|
||||
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faMicrophone } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
@@ -25,14 +28,9 @@ function VoiceDesignerMainPage({
|
||||
usePrefixedDocumentTitle("AI Voice Designer");
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useLocalize("FaceAnimator");
|
||||
const { datasets, voices, isFetching } = useVoiceRequests({
|
||||
requestDatasets: true,
|
||||
requestVoices: true,
|
||||
});
|
||||
const { datasets, voices, isLoading } = useVoiceRequests({ requestDatasets: true,requestVoices: true });
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const view = ["/voice-designer/datasets", "/voice-designer/voices"].indexOf(
|
||||
pathname
|
||||
);
|
||||
const view = ["/voice-designer/datasets", "/voice-designer/voices"].indexOf(pathname);
|
||||
const [deleteItem, setDeleteItem] = useState("");
|
||||
const [deleteType, setDeleteType] = useState("");
|
||||
const [deleteText, setDeleteText] = useState({
|
||||
@@ -76,26 +74,24 @@ function VoiceDesignerMainPage({
|
||||
history.push(`/voice-designer/voice/${token}`);
|
||||
};
|
||||
|
||||
const voiceClick =
|
||||
(todo: any, type: string) =>
|
||||
({ target }: { target: any }) => {
|
||||
let voiceToken =
|
||||
voices.list[target.name.split(",")[0].split(":")[1]].voice_token;
|
||||
todo(voiceToken, type);
|
||||
};
|
||||
const DataBadge = () => <span className="dataset-badge mb-0">Dataset</span>;
|
||||
const VoiceBadge = () => <FontAwesomeIcon icon={faMicrophone} className="me-2 me-lg-3" />;
|
||||
|
||||
const datasetClick =
|
||||
(todo: any, type: string) =>
|
||||
({ target }: { target: any }) => {
|
||||
let datasetToken =
|
||||
datasets.list[target.name.split(",")[0].split(":")[1]].dataset_token;
|
||||
todo(datasetToken, type);
|
||||
};
|
||||
// these need to be abstracted to use over again -V
|
||||
const voiceClick = (todo: any, type: string) => ({ target }: { target: any }) => {
|
||||
let voiceToken = voices.list[target.name.split(",")[0].split(":")[1]].voice_token;
|
||||
todo(voiceToken, type);
|
||||
};
|
||||
|
||||
const datasetClick = (todo: any, type: string) => ({ target }: { target: any }) => {
|
||||
let datasetToken = datasets.list[target.name.split(",")[0].split(":")[1]].dataset_token;
|
||||
todo(datasetToken, type);
|
||||
};
|
||||
|
||||
const actionDataSets = datasets.list.map((dataset, i) => {
|
||||
return {
|
||||
...dataset,
|
||||
badge: "dataset",
|
||||
badge: DataBadge,
|
||||
buttons: [
|
||||
{
|
||||
label: "Edit",
|
||||
@@ -117,7 +113,7 @@ function VoiceDesignerMainPage({
|
||||
const actionVoices = voices.list.map((voice, i) => {
|
||||
return {
|
||||
...voice,
|
||||
badge: "voice",
|
||||
badge: VoiceBadge,
|
||||
buttons: [
|
||||
{
|
||||
label: "Edit",
|
||||
@@ -157,6 +153,18 @@ function VoiceDesignerMainPage({
|
||||
to: "/voice-designer/create",
|
||||
};
|
||||
|
||||
const dataPlaceholder = () => <div className="d-flex flex-column list-items p-5 align-items-center">
|
||||
<h5 className="fw-semibold mb-3">
|
||||
You haven't created any voices.
|
||||
</h5>
|
||||
<Button
|
||||
icon={faPlus} // 1
|
||||
label="Create New Voice" // 2
|
||||
small={true}
|
||||
to="/voice-designer/create" // 3
|
||||
/>
|
||||
</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container type="panel">
|
||||
@@ -204,10 +212,7 @@ function VoiceDesignerMainPage({
|
||||
</nav>
|
||||
|
||||
<div className="p-3 p-lg-4">
|
||||
<ListItems
|
||||
{...{ data: view ? actionVoices : actionDataSets }}
|
||||
isLoading={isFetching}
|
||||
/>
|
||||
<ListItems {...{ data: view ? actionVoices : actionDataSets, dataPlaceholder, isLoading }}/>
|
||||
</div>
|
||||
</Panel>
|
||||
</Container>
|
||||
|
||||
@@ -88,7 +88,6 @@ function VoiceDesignerVoiceEditPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
aaaaa
|
||||
<TempSelect
|
||||
{...{
|
||||
icon: faEye,
|
||||
|
||||
+4
@@ -27,3 +27,7 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.fy-vd-badge > * {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
@@ -1,124 +1,64 @@
|
||||
import React from "react";
|
||||
import Button from "components/common/Button";
|
||||
import { faMicrophone, faPlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import React from 'react';
|
||||
import Skeleton from "components/common/Skeleton";
|
||||
import { Button } from "components/common";
|
||||
|
||||
import "./ListItems/ListItems.scss";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Skeleton from "components/common/Skeleton";
|
||||
|
||||
|
||||
interface ListItemsProps {
|
||||
data: any[];
|
||||
dataPlaceholder?: any;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
interface ListItem {
|
||||
badge: "voice" | "dataset";
|
||||
badge?: any;
|
||||
buttons: any;
|
||||
index: number;
|
||||
isCreating?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const Working = () => (
|
||||
<div className="d-flex align-items-center gap-2 py-1">
|
||||
<p className="fw-medium opacity-75">Voice is being created...</p>
|
||||
<div className="spinner-border spinner-border-sm text-light" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const LoaderView = () => <div className="list-items p-3">
|
||||
<h3 className="mb-0">
|
||||
<Skeleton type="medium" rounded />
|
||||
</h3>
|
||||
</div>;
|
||||
|
||||
const ItemRow = ({
|
||||
badge,
|
||||
buttons,
|
||||
index,
|
||||
isCreating,
|
||||
name,
|
||||
...rest
|
||||
}: ListItem) => {
|
||||
return (
|
||||
<div className="d-flex flex-column flex-lg-row gap-3 list-items p-3 align-items-lg-center">
|
||||
<div className="d-inline-flex flex-wrap align-items-center flex-grow-1">
|
||||
{badge === "dataset" ? (
|
||||
<div className="d-flex flex-column flex-lg-row align-items-start gap-2">
|
||||
<span className="dataset-badge mb-0">Dataset</span>
|
||||
<h5 className="fw-semibold mb-0">{name}</h5>
|
||||
</div>
|
||||
) : null}
|
||||
{badge === "voice" ? (
|
||||
<div className="d-inline-flex align-items-center">
|
||||
<h5 className="fw-semibold d-flex mb-0 align-items-start">
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faMicrophone} className="me-2 me-lg-3" />
|
||||
</span>
|
||||
{name}
|
||||
</h5>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{isCreating ? (
|
||||
<Working />
|
||||
) : (
|
||||
<div className="d-flex gap-2">
|
||||
{buttons && buttons.length
|
||||
? buttons.map((action: any, key: number) => {
|
||||
return (
|
||||
<Button
|
||||
{...{
|
||||
...action,
|
||||
key,
|
||||
name: `item-row:${index},button:${key}`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
const ItemRow = ({ badge: Badge, buttons, index, isCreating, name, ...rest }: ListItem) => {
|
||||
return <div className="d-flex flex-column flex-lg-row gap-3 list-items p-3 align-items-lg-center">
|
||||
<div className="d-inline-flex flex-wrap align-items-center flex-grow-1 gap-2">
|
||||
<h5 className="fy-vd-badge fw-semibold mb-0">
|
||||
{ Badge ? <Badge /> : null }
|
||||
{ name }
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
<div className="d-flex">
|
||||
<div className="d-flex gap-2">
|
||||
{buttons && buttons.length
|
||||
? buttons.map((action: any, key: number) => {
|
||||
return (
|
||||
<Button
|
||||
{...{
|
||||
...action,
|
||||
key,
|
||||
name: `item-row:${index},button:${key}`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default function NewList({ data, isLoading }: ListItemsProps) {
|
||||
const DataPlaceholder = () => (
|
||||
<div className="d-flex flex-column list-items p-5 align-items-center">
|
||||
<h5 className="fw-semibold mb-3">You haven't created any voices.</h5>
|
||||
<Button
|
||||
icon={faPlus} // 1
|
||||
label="Create New Voice" // 2
|
||||
small={true}
|
||||
to="/voice-designer/create" // 3
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
export default function NewList({ data, dataPlaceholder, isLoading }: ListItemsProps) {
|
||||
const DataPlaceholder = dataPlaceholder || null;
|
||||
|
||||
if (isLoading) {
|
||||
const loaderView = (
|
||||
<div className="list-items p-3">
|
||||
<h3 className="mb-0">
|
||||
<Skeleton type="medium" rounded />
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column gap-3">
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<React.Fragment key={i}>{loaderView}</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return data.length ? (
|
||||
<div className="d-flex flex-column gap-3">
|
||||
{data.map((item, key) => (
|
||||
<ItemRow {...{ key, ...item, index: key }} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<DataPlaceholder />
|
||||
);
|
||||
}
|
||||
return isLoading ? <LoaderView /> :
|
||||
data.length ? <div className="d-flex flex-column gap-3">
|
||||
{ data.map((item, key) => <ItemRow {...{ key, ...item, index: key }}/> ) }
|
||||
</div> :
|
||||
<DataPlaceholder />;
|
||||
};
|
||||
+79
-16
@@ -1,31 +1,94 @@
|
||||
import React, { useState } from "react";
|
||||
import VoiceDesignerUploadComponent from "../VoiceDesignerUploadComponent";
|
||||
import React, { useEffect, useState } from "react";
|
||||
// import VoiceDesignerUploadComponent from "../VoiceDesignerUploadComponent";
|
||||
import { AudioInput } from "components/common";
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import ListItems from "../NewList";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { ListSamplesForDataset } from "@storyteller/components/src/api/voice_designer/voice_dataset_samples/ListSamplesForDataset";
|
||||
import { UploadSample } from "@storyteller/components/src/api/voice_designer/voice_dataset_samples/UploadSample";
|
||||
import { DeleteSample } from "@storyteller/components/src/api/voice_designer/voice_dataset_samples/DeleteSample";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faWaveform } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
|
||||
function UploadSamples({ audioProps, datasetToken, setAudioSamplesReady }: { audioProps: any, datasetToken?: string, setAudioSamplesReady:any }) {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const [convertLoading, setConvertLoading] = useState(false);
|
||||
const [canConvert, setCanConvert] = useState(false);
|
||||
const [mediaUploadToken, setMediaUploadToken] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [convertIdempotencyToken, setConvertIdempotencyToken] = useState(
|
||||
uuidv4()
|
||||
);
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// const [convertLoading, setConvertLoading] = useState(false);
|
||||
// const [canConvert, setCanConvert] = useState(false);
|
||||
// const [mediaUploadToken, setMediaUploadToken] = useState<string | undefined>();
|
||||
// const [formIsCleared, setFormIsCleared] = useState(false);
|
||||
const [uploaded,uploadedSet] = useState(false);
|
||||
const [samples,samplesSet] = useState<any[]>([]);
|
||||
const [listFetched,listFetchedSet] = useState(false);
|
||||
|
||||
const [formIsCleared, setFormIsCleared] = useState(false);
|
||||
const SampleBadge = () => <FontAwesomeIcon icon={faWaveform} className="me-2 me-lg-3" />;
|
||||
|
||||
const changeConvertIdempotencyToken = () => {
|
||||
setConvertIdempotencyToken(uuidv4());
|
||||
const sampleClick = () => ({ target }: { target: any }) => {
|
||||
let sampleToken = samples[target.name.split(",")[0].split(":")[1]].sample_token;
|
||||
DeleteSample(sampleToken,{ as_mod: false, set_delete: true })
|
||||
.then((res) => {
|
||||
listFetchedSet(false);
|
||||
// set unBusy here
|
||||
});
|
||||
};
|
||||
|
||||
const actionSamples = samples.map((sample: any, i: number) => {
|
||||
let date = new Date(sample.created_at);
|
||||
return {
|
||||
...sample,
|
||||
badge: SampleBadge,
|
||||
buttons: [
|
||||
{
|
||||
label: "Delete",
|
||||
small: true,
|
||||
variant: "secondary",
|
||||
onClick: sampleClick()
|
||||
},
|
||||
],
|
||||
name: `Sample from ${ date ? moment(date).format("MMMM Do YYYY, h:mm a") : "" }`,
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (audioProps.file && datasetToken && !uploaded) {
|
||||
uploadedSet(true);
|
||||
UploadSample("",{
|
||||
dataset_token: datasetToken || "",
|
||||
file: audioProps.file,
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
audioProps.clear();
|
||||
uploadedSet(false);
|
||||
listFetchedSet(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (datasetToken && !listFetched) {
|
||||
listFetchedSet(true);
|
||||
ListSamplesForDataset(datasetToken,{})
|
||||
.then((res) => {
|
||||
if (res.success && res.samples) {
|
||||
samplesSet(res.samples);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},[audioProps, datasetToken, listFetched, uploaded]);
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column gap-4">
|
||||
<div>
|
||||
<label className="sub-title">Upload Audio</label>
|
||||
<div className="d-flex flex-column gap-3 upload-component">
|
||||
<VoiceDesignerUploadComponent {...{ changeConvertIdempotencyToken, datasetToken, formIsCleared, setCanConvert, setFormIsCleared, setMediaUploadToken, setAudioSamplesReady }}/>
|
||||
<AudioInput {...{ ...audioProps }}/>
|
||||
{ samples.length ? <ListItems {...{ data: actionSamples, isLoading: false }}/> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -152,7 +152,6 @@ export default function useVoiceRequests({
|
||||
}, [user, datasetStatus, voicesStatus]);
|
||||
|
||||
return {
|
||||
isFetching: isBusy(datasetStatus) || isBusy(voicesStatus),
|
||||
datasets: {
|
||||
byToken: datasetByToken,
|
||||
create: createDataset,
|
||||
@@ -166,6 +165,7 @@ export default function useVoiceRequests({
|
||||
inference: {
|
||||
enqueue: EnqueueTts,
|
||||
},
|
||||
isLoading: isBusy(datasetStatus) || isBusy(voicesStatus),
|
||||
languages,
|
||||
visibilityOptions,
|
||||
voices: {
|
||||
|
||||
@@ -4576,6 +4576,7 @@ __metadata:
|
||||
craco: ^0.0.3
|
||||
cross-env: ^7.0.3
|
||||
lerna: ^4.0.0
|
||||
moment: ^2.29.4
|
||||
universal-cookie: ^6.1.1
|
||||
yarn: ^1.22.19
|
||||
languageName: unknown
|
||||
@@ -14797,6 +14798,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"moment@npm:^2.29.4":
|
||||
version: 2.29.4
|
||||
resolution: "moment@npm:2.29.4"
|
||||
checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"move-concurrently@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "move-concurrently@npm:1.0.1"
|
||||
|
||||
Reference in new issue
Block a user