Merge branch 'master' into bflat-prettier-configs

This commit is contained in:
BFlat authored and GitHub committed 2023-12-01 12:00:06 +07:00
commit 9ac3be3a94
59 files changed
+483 -438

No files matched your search

+1
View File
@@ -39,6 +39,7 @@
},
"dependencies": {
"@react-spring/types": "^9.7.3",
"moment": "^2.29.4",
"universal-cookie": "^6.1.1",
"yarn": "^1.22.19"
},
@@ -15,9 +15,10 @@ export interface InferenceInfo {
}
export interface ByQueueStats {
pending_svc_jobs: number,
pending_rvc_jobs: number,
pending_face_animation_jobs: number,
pending_rvc_jobs: number,
pending_svc_jobs: number,
pending_tacotron2_jobs: number,
}
export interface LegacyTtsInfo {
@@ -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`,
});
@@ -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>({
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "ডাউনলোড ফাইল",
"generate": "তৈরি করুন",
"makeAnother": "আরেকটা তৈরি কর"
"makeAnother": "আরেকটা তৈরি কর",
"viewResult": "ফলাফল দেখুন",
"yourJobs": "আপনার কাজ"
},
"status": {
"animationComplete": "অ্যানিমেশন সম্পূর্ণ!",
@@ -17,7 +17,7 @@
"heroButtonUpgradePlan": "আপগ্রেড পরিকল্পনা",
"heroText": "আপনি যা চান তা বলে আপনার প্রিয় চরিত্রের অডিও বা ভিডিও তৈরি করতে FakeYou এর ডিপফেক প্রযুক্তি ব্যবহার করুন।",
"heroTitle": "এআই মিউজিক, টেক্সট টু স্পিচ এবং ভয়েস টু ভয়েস",
"productFaceAnimatorText": "অডিওর সাথে মুখগুলিকে অ্যানিমেট করুন",
"productFaceAnimatorText": "অডিও দিয়ে মুখগুলোকে অ্যানিমেট করুন",
"productFaceAnimatorTitle": "ফেস অ্যানিমেটর",
"productNewTag": "নতুন",
"productTtsText": "পাঠ্য থেকে অডিও তৈরি করুন",
@@ -5,5 +5,6 @@
"ratingGood": "ভয়েস ভাল শোনাচ্ছে",
"ratingGreat": "ভয়েস দুর্দান্ত শোনাচ্ছে",
"ratingLabel": "রেটিং:",
"ratingNone": "ভয়েস রেট করা হয়নি",
"ratingOk": "ভয়েস ঠিক আছে শোনাচ্ছে"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Download-Datei",
"generate": "Generieren",
"makeAnother": "Mach etwas anderes"
"makeAnother": "Mach etwas anderes",
"viewResult": "Zeige Ergebnis",
"yourJobs": "Ihre Jobs"
},
"status": {
"animationComplete": "Animation abgeschlossen!",
@@ -5,5 +5,6 @@
"ratingGood": "Stimme klingt gut",
"ratingGreat": "Stimme klingt großartig",
"ratingLabel": "Bewertung:",
"ratingNone": "Stimme nicht bewertet",
"ratingOk": "Die Stimme klingt in Ordnung"
}
@@ -3,14 +3,14 @@
"audio": "Audio",
"image": "Image",
"subtitle": "Select and image with a clear face, and an audio sample, and generate a lipsynced video.",
"title": "Face Animator",
"yourJobs": "Your jobs"
"title": "Face Animator"
},
"inputs": {
"downloadFile": "Download File",
"generate": "Generate",
"makeAnother": "Make another",
"viewResult": "View result"
"viewResult": "View result",
"yourJobs": "Your jobs"
},
"status": {
"animationComplete": "Animation complete!",
@@ -5,5 +5,6 @@
"ratingGood": "Voice Sounds Good",
"ratingGreat": "Voice Sounds Great",
"ratingLabel": "Rating:",
"ratingNone": "Voice Not Rated",
"ratingOk": "Voice Sounds Okay"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Descargar archivo",
"generate": "Generar",
"makeAnother": "Hacer otro"
"makeAnother": "Hacer otro",
"viewResult": "Ver resultado",
"yourJobs": "Tus trabajos"
},
"status": {
"animationComplete": "¡Animación completa!",
@@ -5,5 +5,6 @@
"ratingGood": "La voz suena bien",
"ratingGreat": "La voz suena genial",
"ratingLabel": "Clasificación:",
"ratingNone": "Voz no clasificada",
"ratingOk": "La voz suena bien"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Télécharger un fichier",
"generate": "Générer",
"makeAnother": "Faire un autre"
"makeAnother": "Faire un autre",
"viewResult": "Voir résultat",
"yourJobs": "Vos emplois"
},
"status": {
"animationComplete": "Animation terminée !",
@@ -5,5 +5,6 @@
"ratingGood": "La voix sonne bien",
"ratingGreat": "La voix sonne bien",
"ratingLabel": "Notation:",
"ratingNone": "Voix non classée",
"ratingOk": "La voix sonne bien"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "डाउनलोड फ़ाइल",
"generate": "उत्पन्न",
"makeAnother": "एक और बनाने"
"makeAnother": "एक और बनाने",
"viewResult": "परिणाम देख",
"yourJobs": "आपकी नौकरियाँ"
},
"status": {
"animationComplete": "एनिमेशन पूर्ण!",
@@ -5,5 +5,6 @@
"ratingGood": "आवाज़ अच्छी लगती है",
"ratingGreat": "आवाज़ बहुत अच्छी लगती है",
"ratingLabel": "रेटिंग:",
"ratingNone": "आवाज रेटेड नहीं",
"ratingOk": "आवाज़ ठीक लगती है"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Unduh berkas",
"generate": "Menghasilkan",
"makeAnother": "Buat yang lain"
"makeAnother": "Buat yang lain",
"viewResult": "Lihat hasil",
"yourJobs": "Pekerjaan Anda"
},
"status": {
"animationComplete": "Animasi selesai!",
@@ -5,5 +5,6 @@
"ratingGood": "Suara Kedengarannya Bagus",
"ratingGreat": "Suara Kedengarannya Hebat",
"ratingLabel": "Peringkat:",
"ratingNone": "Suara Tidak Dinilai",
"ratingOk": "Suara Kedengarannya Oke"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Download file",
"generate": "creare",
"makeAnother": "Fanne un'altro"
"makeAnother": "Fanne un'altro",
"viewResult": "Visualizza risultato",
"yourJobs": "I tuoi lavori"
},
"status": {
"animationComplete": "Animazione completata!",
@@ -5,5 +5,6 @@
"ratingGood": "La voce suona bene",
"ratingGreat": "La voce suona alla grande",
"ratingLabel": "Valutazione:",
"ratingNone": "Voce non classificata",
"ratingOk": "La voce suona bene"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "ダウンロードファイル",
"generate": "生成する",
"makeAnother": "別のものを作る"
"makeAnother": "別のものを作る",
"viewResult": "結果を見る",
"yourJobs": "あなたの仕事"
},
"status": {
"animationComplete": "アニメーション完成!",
@@ -5,5 +5,6 @@
"ratingGood": "声が良い",
"ratingGreat": "声が素晴らしい",
"ratingLabel": "評価:",
"ratingNone": "音声が評価されていません",
"ratingOk": "声は大丈夫です"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "파일 다운로드",
"generate": "생성하다",
"makeAnother": "다른 것을 만드세요"
"makeAnother": "다른 것을 만드세요",
"viewResult": "결과 보기",
"yourJobs": "귀하의 직업"
},
"status": {
"animationComplete": "애니메이션 완성!",
@@ -5,5 +5,6 @@
"ratingGood": "목소리가 좋다",
"ratingGreat": "목소리가 좋아요",
"ratingLabel": "평가:",
"ratingNone": "음성 등급 없음",
"ratingOk": "목소리는 괜찮아요"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "⇬ Fazer download do arquivo",
"generate": "Gerar",
"makeAnother": "Faça outro"
"makeAnother": "Faça outro",
"viewResult": "Ver resultado",
"yourJobs": "Seus empregos"
},
"status": {
"animationComplete": "Animação concluída!",
@@ -5,5 +5,6 @@
"ratingGood": "Voz soa bem",
"ratingGreat": "A voz parece ótima",
"ratingLabel": "Avaliação:",
"ratingNone": "Voz não avaliada",
"ratingOk": "Voz soa bem"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Загрузить файл",
"generate": "Генерировать",
"makeAnother": "Сделайте еще один"
"makeAnother": "Сделайте еще один",
"viewResult": "Посмотреть результат",
"yourJobs": "Ваши вакансии"
},
"status": {
"animationComplete": "Анимация завершена!",
@@ -5,5 +5,6 @@
"ratingGood": "Голос звучит хорошо",
"ratingGreat": "Голос звучит великолепно",
"ratingLabel": "Рейтинг:",
"ratingNone": "Голос без рейтинга",
"ratingOk": "Голос звучит нормально"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "ดาวน์โหลดไฟล์",
"generate": "สร้าง",
"makeAnother": "ทำอีก"
"makeAnother": "ทำอีก",
"viewResult": "ดูผลลัพธ์",
"yourJobs": "งานของคุณ"
},
"status": {
"animationComplete": "แอนิเมชั่นเสร็จสมบูรณ์!",
@@ -5,5 +5,6 @@
"ratingGood": "เสียงฟังดูดี",
"ratingGreat": "เสียงฟังดูดีมาก",
"ratingLabel": "เรตติ้ง:",
"ratingNone": "เสียงไม่ได้รับการจัดอันดับ",
"ratingOk": "เสียงฟังดูโอเค"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Dosyayı indir",
"generate": "Oluştur",
"makeAnother": "Başka bir tane yap"
"makeAnother": "Başka bir tane yap",
"viewResult": "Sonucu göster",
"yourJobs": "İşleriniz"
},
"status": {
"animationComplete": "Animasyon tamamlandı!",
@@ -5,5 +5,6 @@
"ratingGood": "Ses Kulağa İyi Geliyor",
"ratingGreat": "Sesi harika geliyor",
"ratingLabel": "Değerlendirme:",
"ratingNone": "Ses Derecelendirilmedi",
"ratingOk": "Ses Kulağa Sorunsuz Geliyor"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "Tải tập tin",
"generate": "Phát ra",
"makeAnother": "Làm một cái khác"
"makeAnother": "Làm một cái khác",
"viewResult": "Xem kết quả",
"yourJobs": "Công việc của bạn"
},
"status": {
"animationComplete": "Hoạt hình hoàn tất!",
@@ -5,5 +5,6 @@
"ratingGood": "Giọng nói nghe hay",
"ratingGreat": "Giọng nói nghe rất hay",
"ratingLabel": "Xếp hạng:",
"ratingNone": "Giọng nói không được xếp hạng",
"ratingOk": "Giọng nói ổn"
}
@@ -8,7 +8,9 @@
"inputs": {
"downloadFile": "下载文件",
"generate": "产生",
"makeAnother": "再做一个"
"makeAnother": "再做一个",
"viewResult": "查看结果",
"yourJobs": "你的职位"
},
"status": {
"animationComplete": "动画完成!",
@@ -5,5 +5,6 @@
"ratingGood": "声音听起来不错",
"ratingGreat": "声音听起来很棒",
"ratingLabel": "评分:",
"ratingNone": "声音未评级",
"ratingOk": "声音听起来不错"
}
+8 -1
View File
@@ -146,6 +146,8 @@ interface State {
// Rollout of vocodes 2.0
enableAlpha: boolean;
sessionFetched: boolean;
sessionWrapper: SessionWrapper;
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
@@ -237,6 +239,7 @@ class App extends React.Component<Props, State> {
enableAlpha: enableAlpha,
migrationMode: migrationMode,
sessionFetched: false,
sessionWrapper: SessionWrapper.emptySession(),
sessionSubscriptionsWrapper:
SessionSubscriptionsWrapper.emptySubscriptions(),
@@ -328,6 +331,7 @@ class App extends React.Component<Props, State> {
};
querySessionSubscriptions = async () => {
this.setState({ sessionFetched: true });
const cookies = new Cookies();
const sessionSubscriptionsWrapper =
@@ -836,7 +840,10 @@ class App extends React.Component<Props, State> {
}}
>
<SessionProvider
{...{ sessionWrapper: this.state.sessionWrapper }}
{...{
sessionWrapper: this.state.sessionWrapper,
sessionFetched: this.state.sessionFetched
}}
>
<Switch>
<Route path="/">
@@ -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,22 @@ 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) {
const transitions = useTransition(file, {
config: { tension: 120, friction: 15 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
onRest,
});
export default function AudioInput({ children, clear = n, file, hideDetails, hideClearDetails, inputProps, ...rest }: Props) {
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 }
</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" }}>
@@ -114,9 +114,10 @@ export default function SideNav(props: SideNavProps) {
total_pending_job_count: 0,
pending_job_count: 0,
by_queue: {
pending_svc_jobs: 0,
pending_rvc_jobs: 0,
pending_face_animation_jobs: 0,
pending_rvc_jobs: 0,
pending_svc_jobs: 0,
pending_tacotron2_jobs: 0,
},
},
legacy_tts: {
@@ -223,6 +224,11 @@ export default function SideNav(props: SideNavProps) {
);
}
// NB(bt,2023-11-28): These are representative of tacotron2 jobs handled by two queueing systems:
// The legacy queue (tts-inference-job) and the modern queue (inference-job). We'll add both totals
// while we migrate off of the legacy system, then eventually kill the legacy statistic.
const ttsQueuedCount = queueStats.legacy_tts.pending_job_count + queueStats.inference.by_queue.pending_tacotron2_jobs;
return (
<div
id="sidebar-wrapper"
@@ -360,7 +366,7 @@ export default function SideNav(props: SideNavProps) {
<div>
{t("queueTts")}:{" "}
<span className="text-red">
{queueStats.legacy_tts.pending_job_count}
{ttsQueuedCount}
</span>
</div>
<div>
@@ -3,11 +3,12 @@ import { SessionContext } from 'context';
interface Props {
children?: any;
sessionFetched: boolean;
sessionWrapper?: any;
}
export default function Session({ children, sessionWrapper }: Props) {
return <SessionContext.Provider {...{ value: { sessionWrapper } }}>
export default function Session({ children, sessionFetched, sessionWrapper }: Props) {
return <SessionContext.Provider {...{ value: { sessionFetched, sessionWrapper } }}>
{ children }
</SessionContext.Provider>
};
@@ -1,7 +1,10 @@
import { createContext } from "react";
interface SessionContextType {
sessionFetched: boolean,
sessionWrapper?: any
}
export default createContext<SessionContextType>({});
export default createContext<SessionContextType>({
sessionFetched: false
});
@@ -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,
}
};
};
@@ -2,6 +2,9 @@ import { useContext } from 'react';
import { SessionContext } from 'context';
export default function useInferenceJobs() {
const { sessionWrapper } = useContext(SessionContext);
return sessionWrapper?.sessionStateResponse || {};
const { sessionFetched, sessionWrapper } = useContext(SessionContext);
return {
sessionFetched,
...sessionWrapper?.sessionStateResponse
};
};
@@ -30,7 +30,7 @@ function RatingStats(props: Props) {
icon={faStarExclamation}
className="me-2 rating-icon"
/>
<p>{t("common.RatingStats.voice.notRated")}</p>
<p>{t("ratingNone")}</p>
</div>
);
}
@@ -56,9 +56,10 @@ function FooterNav(props: Props) {
total_pending_job_count: 0,
pending_job_count: 0,
by_queue: {
pending_svc_jobs: 0,
pending_rvc_jobs: 0,
pending_face_animation_jobs: 0,
pending_rvc_jobs: 0,
pending_svc_jobs: 0,
pending_tacotron2_jobs: 0,
},
},
legacy_tts: {
@@ -122,6 +123,11 @@ function FooterNav(props: Props) {
);
}
// NB(bt,2023-11-28): These are representative of tacotron2 jobs handled by two queueing systems:
// The legacy queue (tts-inference-job) and the modern queue (inference-job). We'll add both totals
// while we migrate off of the legacy system, then eventually kill the legacy statistic.
const ttsQueuedCount = queueStats.legacy_tts.pending_job_count + queueStats.inference.by_queue.pending_tacotron2_jobs;
return (
<div>
<footer id="footer">
@@ -130,7 +136,7 @@ function FooterNav(props: Props) {
<div>
TTS Queued:{" "}
<span className="text-red">
{queueStats.legacy_tts.pending_job_count}
{ttsQueuedCount}
</span>
</div>
<span className="opacity-25">•</span>
@@ -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" }}>
@@ -1,11 +1,6 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import {
faEye,
faLanguage,
faPencil,
faWaveform,
} from "@fortawesome/pro-solid-svg-icons";
import { faEye, faLanguage, faPencil, faWaveform } from "@fortawesome/pro-solid-svg-icons";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import Panel from "components/common/Panel";
import { Stepper } from "./components/Stepper";
@@ -15,16 +10,12 @@ import { VoiceDetails } from "./components/steps/VoiceDetails";
import PageHeader from "components/layout/PageHeader";
import Container from "components/common/Container";
import { useHistory } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { useFile } from "hooks";
import useVoiceRequests from "./useVoiceRequests";
import useUploadedFiles from "hooks/useUploadedFiles";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useSession } from "hooks";
interface RouteParams {
dataset_token?: string;
@@ -37,8 +28,11 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
const [visibility, visibilitySet] = useState("hidden");
const [title, titleSet] = useState("");
const [fetched,fetchedSet] = useState(false);
const deleteEverything = useUploadedFiles((state: any) => state.deleteEverything);
const audioProps = useFile({}); // contains upload inout state and controls, see docs
const [deleting,deletingSet] = useState([]); // samples currently being uploaded
const [inProgress,inProgressSet] = useState([]); // samples currently being uploaded
const [samples,samplesSet] = useState([]); // fetched/uploaded samples
const audioProps = useFile({});
const { user, sessionFetched } = useSession();
const datasetInputs = [
{
@@ -67,28 +61,21 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
];
const { dataset_token } = useParams<RouteParams>();
const [isNewCreation] = useState(!dataset_token);
const isEditMode = Boolean(dataset_token) && !isNewCreation;
const existingVoice = !!dataset_token;
usePrefixedDocumentTitle(isEditMode ? "Edit Dataset" : "Create New Voice");
usePrefixedDocumentTitle(existingVoice ? "Edit Dataset" : "Create New Voice");
const initialStep = history.location.pathname.includes("/upload") ? 1 : 0;
const [currentStep, setCurrentStep] = useState(initialStep);
const [audioSamplesReady, setAudioSamplesReady] = useState(false);
const steps = existingVoice ? ["Edit Details", "Edit Samples"] : ["Voice Details", "Upload Samples"];
const uploadProps = { audioProps, datasetToken: dataset_token, deleting, deletingSet, inProgress, inProgressSet, samples, samplesSet };
const steps = isEditMode
? ["Edit Details", "Edit Samples"]
: ["Voice Details", "Upload Samples"];
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 {...uploadProps }/>;
default:
return null;
}
@@ -106,7 +93,7 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
const handleNext = () => {
if (currentStep === 0) {
if (isNewCreation) {
if (!existingVoice) {
// It's a new creation and on the first step
datasets.create("",{
@@ -115,7 +102,6 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
idempotency_token: uuidv4(),
}).then((res: any) => {
if (res && res.success && res.token) {
deleteEverything();
history.push(`/voice-designer/dataset/${ res.token }/upload`);
}
});
@@ -145,7 +131,6 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
voice_dataset_token: dataset_token || "",
})
.then((res: any) => {
deleteEverything();
if (res && res.success) {
enqueueInferenceJob(
res.inference_job_token,
@@ -169,17 +154,17 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
}
},[dataset_token,datasets,fetched]);
if (!sessionWrapper.isLoggedIn()) {
if (sessionFetched && !user) {
history.push("/voice-designer");
}
return (
<Container type="panel">
<PageHeader
title={isEditMode ? "Edit Dataset" : "Create New Voice"}
titleIcon={isEditMode ? faPencil : faWaveform}
title={existingVoice ? "Edit Dataset" : "Create New Voice"}
titleIcon={existingVoice ? faPencil : faWaveform}
subText={
isEditMode
existingVoice
? "Edit your dataset by uploading more samples to create a new voice"
: "Add voice details and upload audio samples to clone your voice!"
}
@@ -187,11 +172,10 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
showBackButton={true}
backbuttonLabel="Back to Voice Designer"
backbuttonTo={
isEditMode ? "/voice-designer/datasets" : "/voice-designer/voices"
existingVoice ? "/voice-designer/datasets" : "/voice-designer/voices"
}
/>
<Panel>
{ sessionFetched && user && <Panel>
<div className="p-3 px-lg-4 bg-stepper">
<Stepper steps={steps} currentStep={currentStep} />
</div>
@@ -204,9 +188,10 @@ function VoiceDesignerFormPage({ enqueueInferenceJob, sessionWrapper }: { enqueu
onBack={handleBack}
onNext={handleNext}
onCreate={handleCreateVoice}
createDisabled={!audioSamplesReady}
createDisabled={ !!deleting.length || !!inProgress.length || !samples.length } // deleting, uploading, or no samples = disabled
/>
</Panel>
}
</Container>
);
}
@@ -15,8 +15,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,
@@ -24,7 +27,7 @@ import {
} from "@storyteller/components/src/jobs/InferenceJob";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { Button } from "components/common";
import { useSession } from "hooks";
interface Props {
sessionWrapper: SessionWrapper;
@@ -36,27 +39,21 @@ function VoiceDesignerMainPage(props: Props) {
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({
title: "",
text: "",
});
const history = useHistory();
const { user } = useSession();
if (pathname === "/voice-designer" || pathname === "/voice-designer/") {
return <Redirect to="/voice-designer/voices" />;
}
const openDeleteModal = (token: string, type: string) => {
setDeleteItem(token);
setDeleteType(type);
@@ -87,26 +84,24 @@ function VoiceDesignerMainPage(props: Props) {
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",
@@ -128,7 +123,7 @@ function VoiceDesignerMainPage(props: Props) {
const actionVoices = voices.list.map((voice, i) => {
return {
...voice,
badge: "voice",
badge: VoiceBadge,
buttons: [
{
label: "Edit",
@@ -180,122 +175,101 @@ function VoiceDesignerMainPage(props: Props) {
to: "/pricing",
};
const isLoggedIn = props.sessionWrapper.isLoggedIn();
let pageHeader = <></>;
if (!isLoggedIn) {
pageHeader = (
<PageHeader
button={signUpButton}
secondaryButton={pricingButton}
title="Voice Designer"
titleIcon={faWaveform}
subText="Create your own AI voice by providing audio files of the voice you want to clone."
panel={false}
imageUrl="/images/header/voice-designer.png"
/>
);
} else {
pageHeader = (
<PageHeader
button={createVoiceButton}
title="Voice Designer"
titleIcon={faWaveform}
subText="Create your own AI voice by providing audio files of the voice you want to clone."
panel={false}
imageUrl="/images/header/voice-designer.png"
/>
);
}
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>;
let body = <></>;
if (!isLoggedIn) {
body = (
<Panel padding={true}>
<div className="d-flex flex-column align-items-center py-3 my-3 py-md-4 my-md-4 gap-4">
<div className="text-center">
<h4 className="fw-bold">Please log in to access voice creation.</h4>
<p className="text-center opacity-75">
If you don't have an account yet, sign up now to unlock this
feature!
</p>
const LogggedInView = () => <>
<InferenceJobsList
{...{
t,
inferenceJobs: props.inferenceJobsByCategory.get(
FrontendInferenceJobType.VoiceDesignerCreateVoice
),
statusTxt,
}}
/>
<Panel>
<nav>
<ul className="nav nav-tabs">
<div className="d-flex flex-grow-1">
<li className="nav-item">
<NavLink
to="/voice-designer/voices"
className="nav-link fs-6 px-3 px-lg-4"
activeClassName="active"
>
My Voices
</NavLink>
</li>
<li className="nav-item">
<NavLink
to="/voice-designer/datasets"
className="nav-link fs-6"
activeClassName="active"
>
My Datasets
</NavLink>
</li>
</div>
</ul>
</nav>
<div className="d-flex gap-3 align-items-center">
<Button
label="Sign Up"
variant="primary"
icon={faPenToSquare}
to="/signup"
/>
<Button
label="Login"
variant="secondary"
icon={faRightToBracket}
to="/login"
/>
</div>
</div>
</Panel>
);
} else {
body = (
<>
<InferenceJobsList
{...{
t,
inferenceJobs: props.inferenceJobsByCategory.get(
FrontendInferenceJobType.VoiceDesignerCreateVoice
),
statusTxt,
}}
<div className="p-3 p-lg-4">
<ListItems {...{ data: view ? actionVoices : actionDataSets, dataPlaceholder, isLoading }}/>
</div>
</Panel>
</>;
const LoggedOutView = () => <Panel padding={true}>
<div className="d-flex flex-column align-items-center py-3 my-3 py-md-4 my-md-4 gap-4">
<div className="text-center">
<h4 className="fw-bold">Please log in to access voice creation.</h4>
<p className="text-center opacity-75">
If you don't have an account yet, sign up now to unlock this
feature!
</p>
</div>
<div className="d-flex gap-3 align-items-center">
<Button
label="Sign Up"
variant="primary"
icon={faPenToSquare}
to="/signup"
/>
<Panel mb={true}>
<nav>
<ul className="nav nav-tabs">
<div className="d-flex flex-grow-1">
<li className="nav-item">
<NavLink
to="/voice-designer/voices"
className="nav-link fs-6 px-3 px-lg-4"
activeClassName="active"
>
My Voices
</NavLink>
</li>
<li className="nav-item">
<NavLink
to="/voice-designer/datasets"
className="nav-link fs-6"
activeClassName="active"
>
My Datasets
</NavLink>
</li>
</div>
</ul>
</nav>
<div className="p-3 p-lg-4">
<ListItems
{...{ data: view ? actionVoices : actionDataSets }}
isLoading={isFetching}
/>
</div>
</Panel>
</>
);
}
<Button
label="Login"
variant="secondary"
icon={faRightToBracket}
to="/login"
/>
</div>
</div>
</Panel>;
return (
<>
<Container type="panel">
{pageHeader}
{body}
</Container>
<PageHeader {...{
button: user ? createVoiceButton : signUpButton,
...!user ? { secondaryButton: pricingButton } : {},
title: "Voice Designer",
titleIcon: faWaveform,
subText: "Create your own AI voice by providing audio files of the voice you want to clone.",
panel: false,
imageUrl: "/images/header/voice-designer.png"
}}/>
{ user ? <LogggedInView /> : <LoggedOutView /> }
{/* Delete Modal */}
<Modal
show={isDeleteModalOpen}
@@ -304,6 +278,7 @@ function VoiceDesignerMainPage(props: Props) {
content={<p>{deleteText.text}</p>}
onConfirm={handleDelete}
/>
</Container>
</>
);
}
@@ -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 />;
};
@@ -1,54 +1,113 @@
import React, { useState } from "react";
import VoiceDesignerUploadComponent from "../VoiceDesignerUploadComponent";
import React, { useEffect, useState } from "react";
import { AudioInput } from "components/common";
import moment from 'moment';
import ListItems from "../NewList";
import { v4 as uuidv4 } from "uuid";
// import { SessionContext } from 'context';
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 */
interface Props {
audioProps: any,
datasetToken?: string,
deleting: any,
deletingSet: any,
inProgress: any,
inProgressSet: any
samples: any,
samplesSet: any
}
const [formIsCleared, setFormIsCleared] = useState(false);
function UploadSamples({ audioProps, datasetToken, deleting, deletingSet, inProgress, inProgressSet, samples, samplesSet }: Props) {
const [listFetched,listFetchedSet] = useState(false);
const changeConvertIdempotencyToken = () => {
setConvertIdempotencyToken(uuidv4());
const SampleBadge = () => <FontAwesomeIcon icon={faWaveform} className="me-2 me-lg-3" />;
const sampleClick = () => ({ target }: { target: any }) => {
let sampleToken = samples[target.name.split(",")[0].split(":")[1]].sample_token;
deletingSet([ ...deleting, sampleToken ]); // add to deleting list
DeleteSample(sampleToken,{ as_mod: false, set_delete: true })
.then((res) => {
listFetchedSet(false);
deletingSet(deleting.filter((item: any) => item === sampleToken)); // remove from deleting list
});
};
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,
}}
/>
</div>
</div>
const actionSamples = [ // these spread operators combine the inProgress and sample arrays
...inProgress,
...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) {
let uuid_idempotency_token = uuidv4();
audioProps.clear();
inProgressSet([ // add sample to in progress list
{
// badge: Component // this can be a loading indicator
name: "Uploading",
uuid_idempotency_token
},
...inProgress
]);
UploadSample("",{
dataset_token: datasetToken || "",
file: audioProps.file,
uuid_idempotency_token,
})
.then((res) => {
if (res.success) {
inProgressSet(inProgress.filter((item: any) => // removes sample from in progress list
item.uuid_idempotency_token === uuid_idempotency_token)
);
listFetchedSet(false); // refetches sample list
}
});
}
if (datasetToken && !listFetched) {
listFetchedSet(true);
ListSamplesForDataset(datasetToken,{})
.then((res) => {
if (res.success && res.samples) {
samplesSet(res.samples);
}
});
}
},[audioProps, datasetToken, listFetched, inProgress, inProgressSet, samplesSet]);
return <div className="d-flex flex-column gap-4">
<label className="sub-title">Upload Audio</label>
<div className="d-flex flex-column gap-3 upload-component">
<AudioInput {...{ ...audioProps }}/>
{ actionSamples.length ? <ListItems {...{ data: actionSamples, isLoading: false }}/> :
<div className="panel panel-inner text-center p-5 rounded-5 h-100">
<div className="d-flex flex-column opacity-75 h-100 justify-content-center">
<FontAwesomeIcon icon={faWaveform} className="fs-3 mb-3" />
<h5>No voice samples yet</h5>
<p>Uploaded samples will appear here.</p>
</div>
</div> }
</div>
);
</div>;
}
export { UploadSamples };
@@ -51,17 +51,18 @@ export default function useVoiceRequests({
const [datasets, datasetsSet] = useState<Dataset[]>([]);
const [voices, voicesSet] = useState<Voice[]>([]);
// this state stays here, fetched states are not success, merely an attempt was made. Each list fetch sets to true. Set to false to retry
const [fetchDatasets, fetchDatasetsSet] = useState(requestDatasets);
const [fetchVoices, fetchVoicesSet] = useState(requestVoices);
const [isFetchingVoices, setIsFetchingVoices] = useState(requestVoices);
const [isFetchingDatasets, setIsFetchingDatasets] = useState(requestDatasets);
// 0 = paused, 1 = requested, 2 = started, 3 = success, we could even do 4 for errors
const [datasetStatus, datasetStatusSet] = useState(requestDatasets ? 1 : 0);
const [voicesStatus, voicesStatusSet] = useState(requestVoices ? 1 : 0);
const isBusy = (status: number) => status === 1 || status === 2;
const { user } = useSession();
// const [timestamp, timestampSet] = useState(Date.now());
const refreshData = () => {
fetchDatasetsSet(true);
fetchVoicesSet(true);
datasetStatusSet(1);
voicesStatusSet(1);
}; // later we can do refresh per list
const createDataset = (
@@ -89,7 +90,6 @@ export default function useVoiceRequests({
}).then((res) => {
refreshData();
return res;
// console.log("🏧",res);
});
const deleteDataset = (voiceToken: string): Promise<DeleteDatasetResponse> =>
@@ -99,7 +99,6 @@ export default function useVoiceRequests({
}).then((res) => {
refreshData();
return res;
// console.log("🏧",res);
});
const datasetByToken = (datasetToken?: string) =>
@@ -111,7 +110,6 @@ export default function useVoiceRequests({
datasetToken: string,
request: UpdateDatasetRequest
): Promise<UpdateDatasetResponse> => {
// console.log("🍄", datasetToken);
return UpdateDataset(datasetToken, request).then((res) => {
refreshData();
return res;
@@ -119,7 +117,7 @@ export default function useVoiceRequests({
};
const listDatasets = () => {
fetchDatasetsSet(true);
datasetStatusSet(1);
return datasets;
};
@@ -136,27 +134,24 @@ export default function useVoiceRequests({
useEffect(() => {
if (user && user.username) {
if (fetchDatasets) {
setIsFetchingDatasets(true);
fetchDatasetsSet(false);
if (datasetStatus === 1) {
datasetStatusSet(2);
ListDatasetsByUser(user.username, {}).then((res) => {
datasetStatusSet(3);
if (res.datasets) datasetsSet(res.datasets);
setIsFetchingDatasets(false);
});
}
if (fetchVoices) {
setIsFetchingVoices(true);
fetchVoicesSet(false);
if (voicesStatus === 1) {
voicesStatusSet(2);
ListVoicesByUser(user.username, {}).then((res) => {
voicesStatusSet(3);
if (res.voices) voicesSet(res.voices);
setIsFetchingVoices(false);
});
}
}
}, [user, fetchDatasets, fetchVoices]);
}, [user, datasetStatus, voicesStatus]);
return {
isFetching: isFetchingVoices || isFetchingDatasets,
datasets: {
byToken: datasetByToken,
create: createDataset,
@@ -170,6 +165,7 @@ export default function useVoiceRequests({
inference: {
enqueue: EnqueueTts,
},
isLoading: isBusy(datasetStatus) || isBusy(voicesStatus),
languages,
visibilityOptions,
voices: {
@@ -182,9 +178,6 @@ export default function useVoiceRequests({
},
inputCtrl:
(todo: any) =>
({ target }: { target: any }) => {
console.log("🌿", target);
todo(target.value);
},
({ target }: { target: any }) => { todo(target.value); },
};
}
+8
View File
@@ -4579,6 +4579,7 @@ __metadata:
lerna: ^4.0.0
prettier: ^3.1.0
pretty-quick: ^3.1.3
moment: ^2.29.4
universal-cookie: ^6.1.1
yarn: ^1.22.19
languageName: unknown
@@ -14849,6 +14850,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"