diff --git a/packages/components/src/api/ApiConfig.ts b/packages/components/src/api/ApiConfig.ts index fc2703cc89..bf5e82de7f 100644 --- a/packages/components/src/api/ApiConfig.ts +++ b/packages/components/src/api/ApiConfig.ts @@ -380,6 +380,14 @@ class ApiConfig { return `${this.getScheme()}://${this.getNewApiHost()}/voice_clone_requests/check`; } + enqueueRetrievalJob(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/retrieval/enqueue`; + } + + getRetrievalJobStatus(jobToken: string): string { + return `${this.getScheme()}://${this.getNewApiHost()}/retrieval/job_status/${jobToken}`; + } + // =============== Storyteller-specific =============== listTwitchEventRules(): string { diff --git a/packages/components/src/api/retrieval/EnqueueRetrieval.ts b/packages/components/src/api/retrieval/EnqueueRetrieval.ts new file mode 100644 index 0000000000..c067cb3cee --- /dev/null +++ b/packages/components/src/api/retrieval/EnqueueRetrieval.ts @@ -0,0 +1,53 @@ +import { ApiConfig } from "../ApiConfig"; + +export interface EnqueueRetrievalRequest { + idempotency_token: string, + title: string, + download_url: string, + generic_download_type: string, +} + +export interface EnqueueRetrievalSuccessResponse { + success: boolean, + job_token: string, +} + +export interface EnqueueRetrievalErrorResponse { + success: boolean, +} + +type EnqueueRetrievalResponse = EnqueueRetrievalSuccessResponse | EnqueueRetrievalErrorResponse; + +export function EnqueueRetrievalIsOk(response: EnqueueRetrievalResponse): response is EnqueueRetrievalSuccessResponse { + return response?.success === true; +} + +export function EnqueueRetrievalIsError(response: EnqueueRetrievalResponse): response is EnqueueRetrievalErrorResponse { + return response?.success === false; +} + +export async function EnqueueRetrieval(request: EnqueueRetrievalRequest) : Promise +{ + const endpoint = new ApiConfig().enqueueRetrievalJob(); + + return fetch(endpoint, { + method: 'POST', + credentials: 'include', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + }) + .then(res => res.json()) + .then(res => { + if (res && 'success' in res) { + return res; + } else { + return { success : false }; + } + }) + .catch(e => { + return { success : false }; + }); +} diff --git a/packages/components/src/api/retrieval/GetRetrievalJobStatus.ts b/packages/components/src/api/retrieval/GetRetrievalJobStatus.ts new file mode 100644 index 0000000000..07b244b06e --- /dev/null +++ b/packages/components/src/api/retrieval/GetRetrievalJobStatus.ts @@ -0,0 +1,63 @@ +import { ApiConfig } from "../ApiConfig"; + +export interface GetRetrievalJobStatusSuccessResponse { + success: boolean, + state: RetrievalJobStatus, +} + +export interface RetrievalJobStatus { + // Job primary key + job_token: string, + + // Job state machine (enum) and retry count + status: string, + attempt_count: number, + + // Optional unstructured extra details during the inference process + maybe_extra_status_description?: string, + + // Job completion: foreign key to entity + maybe_downloaded_entity_type?: string, + maybe_downloaded_entity_token?: string, + + created_at: Date, + updated_at: Date, +} + +export interface GetRetrievalJobStatusErrorResponse { + success: boolean, +} + +type GetRetrievalJobStatusResponse = GetRetrievalJobStatusSuccessResponse | GetRetrievalJobStatusErrorResponse; + +export function GetRetrievalJobStatusIsOk(response: GetRetrievalJobStatusResponse): response is GetRetrievalJobStatusSuccessResponse { + return response?.success === true; +} + +export function GetRetrievalJobStatusIsError(response: GetRetrievalJobStatusResponse): response is GetRetrievalJobStatusErrorResponse { + return response?.success === false; +} + +export async function GetRetrievalJobStatus(jobToken: string) : Promise +{ + const endpoint = new ApiConfig().getRetrievalJobStatus(jobToken); + + return fetch(endpoint, { + method: 'GET', + credentials: 'include', + headers: { + 'Accept': 'application/json', + }, + }) + .then(res => res.json()) + .then(res => { + if (res && 'success' in res) { + return res; + } else { + return { success : false }; + } + }) + .catch(e => { + return { success : false }; + }); +} diff --git a/packages/components/src/jobs/VocoderUploadJobs.ts b/packages/components/src/jobs/VocoderUploadJobs.ts new file mode 100644 index 0000000000..4cc3d85a07 --- /dev/null +++ b/packages/components/src/jobs/VocoderUploadJobs.ts @@ -0,0 +1,59 @@ +import { RetrievalJobStatus } from "../api/retrieval/GetRetrievalJobStatus"; +import { JobState, jobStateFromString } from "./JobStates"; + + +export class VocoderUploadJob { + jobToken: string; + maybeDownloadedEntityType: string | undefined | null; + maybeDownloadedEntityToken: string | undefined | null; + jobState: JobState; + maybeExtraStatusDescription: string | null; + attemptCount: number; + + constructor( + jobToken: string, + status: string = 'unknown', + maybeExtraStatusDescription: string | null = null, + attemptCount: number = 0, + maybeDownloadedEntityType: string | undefined | null = undefined, + maybeDownloadedEntityToken: string | undefined | null = undefined + ) { + this.jobState = jobStateFromString(status); + this.maybeExtraStatusDescription = maybeExtraStatusDescription; + this.attemptCount = attemptCount; + this.jobToken = jobToken; + if (!!maybeDownloadedEntityType) { + this.maybeDownloadedEntityType = maybeDownloadedEntityType; + } + if (!!maybeDownloadedEntityToken) { + this.maybeDownloadedEntityToken = maybeDownloadedEntityToken; + } + } + + static fromResponse(response: RetrievalJobStatus) : VocoderUploadJob { + return new VocoderUploadJob( + response.job_token, + response.status, + response.maybe_extra_status_description || null, + response.attempt_count || 0, + response.maybe_downloaded_entity_type, + response.maybe_downloaded_entity_token, + ); + } +} + +export interface VocoderUploadJobStateResponsePayload { + success: boolean, + state?: VocoderUploadJobState, +} + +export interface VocoderUploadJobState { + job_token: string, + status: string, + maybe_extra_status_description: string | null, + attempt_count: number | undefined, + maybe_downloaded_entity_type: string | undefined | null, + maybe_downloaded_entity_token: string | undefined | null, + created_at: string, + updated_at: string, +} diff --git a/packages/fakeyou/src/App.tsx b/packages/fakeyou/src/App.tsx index fe632eb81a..5bf66f2978 100644 --- a/packages/fakeyou/src/App.tsx +++ b/packages/fakeyou/src/App.tsx @@ -31,6 +31,8 @@ import { FAKEYOU_MERGED_TRANSLATIONS } from "./_i18n/FakeYouTranslations"; import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import { USE_REFRESH } from "./Refresh"; +import { VocoderUploadJob } from "@storyteller/components/src/jobs/VocoderUploadJobs"; +import { GetRetrievalJobStatus, GetRetrievalJobStatusIsOk } from "@storyteller/components/src/api/retrieval/GetRetrievalJobStatus"; i18n .use(initReactI18next) // passes i18n down to react-i18next @@ -108,6 +110,7 @@ interface State { w2lInferenceJobs: Array; ttsModelUploadJobs: Array; w2lTemplateUploadJobs: Array; + vocoderUploadJobs: Array; // Current text entered textBuffer: string; @@ -163,6 +166,7 @@ class App extends React.Component { w2lInferenceJobs: [], ttsModelUploadJobs: [], w2lTemplateUploadJobs: [], + vocoderUploadJobs: [], textBuffer: "", }; @@ -543,6 +547,42 @@ class App extends React.Component { }); }; + enqueueVocoderUploadJob = (jobToken: string) => { + const newJob = new VocoderUploadJob(jobToken); + let uploadJobs = this.state.vocoderUploadJobs.concat([newJob]); + + this.setState({ + vocoderUploadJobs: uploadJobs, + }); + }; + + checkVocoderUploadJob = async (jobToken: string) => { + const lookupResult = await GetRetrievalJobStatus(jobToken); + + if (GetRetrievalJobStatusIsOk(lookupResult)) { + let updatedJobs: Array = []; + + this.state.vocoderUploadJobs.forEach((existingJob) => { + if ( + existingJob.jobToken !== lookupResult.state!.job_token || + !jobStateCanChange(existingJob.jobState) + ) { + updatedJobs.push(existingJob); + return; + } + + let updatedJob = VocoderUploadJob.fromResponse( + lookupResult.state! + ); + updatedJobs.push(updatedJob); + }); + + this.setState({ + vocoderUploadJobs: updatedJobs, + }); + } + }; + pollJobs = () => { this.state.ttsInferenceJobs.forEach((job) => { if (jobStateCanChange(job.jobState)) { @@ -564,6 +604,11 @@ class App extends React.Component { this.checkW2lTemplateUploadJob(job.jobToken); } }); + this.state.vocoderUploadJobs.forEach((job) => { + if (jobStateCanChange(job.jobState)) { + this.checkVocoderUploadJob(job.jobToken); + } + }); }; setMigrationMode = (mode: MigrationMode) => { @@ -644,6 +689,8 @@ class App extends React.Component { this.enqueueW2lTemplateUploadJob } w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs} + enqueueVocoderUploadJob={this.enqueueVocoderUploadJob} + vocoderUploadJobs={this.state.vocoderUploadJobs} textBuffer={this.state.textBuffer} setTextBuffer={this.setTextBuffer} clearTextBuffer={this.clearTextBuffer} diff --git a/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx b/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx index 9cab98c382..4aedfbd4b0 100644 --- a/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx +++ b/packages/fakeyou/src/v2/view/NewVocodesContainer.tsx @@ -17,13 +17,14 @@ import { TtsInferenceJob, W2lInferenceJob } from '../../App'; import { TtsModelDeletePage } from './tts/tts_model_delete/TtsModelDeletePage'; import { TtsModelEditPage } from './tts/tts_model_edit/TtsModelEditPage'; import { TtsModelUploadJob } from '@storyteller/components/src/jobs/TtsModelUploadJobs'; +import { VocoderUploadJob } from '@storyteller/components/src/jobs/VocoderUploadJobs'; import { TtsModelViewPage } from './tts/tts_model_view/TtsModelViewPage'; import { TtsResultDeletePage } from './tts/tts_result_delete/TtsResultDeletePage'; import { TtsResultViewPage } from './tts/tts_result_view/TtsResultViewPage'; import { ContributeIndexPage } from './contribute/ContributeIndexPage'; -import { UploadTtsModelFc } from './upload/UploadTtsModelFc'; -import { UploadW2lPhotoFc } from './upload/UploadW2lPhotoFc'; -import { UploadW2lVideoFc } from './upload/UploadW2lVideoFc'; +import { UploadTtsModelPage } from './upload/UploadTtsModelPage'; +import { UploadW2lPhotoPage } from './upload/UploadW2lPhotoPage'; +import { UploadW2lVideoPage } from './upload/UploadW2lVideoPage'; import { W2lResultViewFc } from './w2l/w2l_result_view/W2lResultViewFc'; import { W2lTemplateListFc } from './w2l/w2l_template_list/W2lTemplateListFc'; import { W2lTemplateUploadJob } from '@storyteller/components/src/jobs/W2lTemplateUploadJobs'; @@ -53,6 +54,7 @@ import ScrollToTop from './_common/ScrollToTop'; import { Language } from '@storyteller/components/src/i18n/Language'; import { VoiceCloneRequestPage } from './clone_voice_requests/VoiceCloneRequestPage'; import { VocodesPage } from './vocodes/VocodesPage'; +import { UploadVocoderPage } from './upload/UploadVocoderPage'; interface Props { sessionWrapper: SessionWrapper, @@ -87,6 +89,9 @@ interface Props { enqueueW2lTemplateUploadJob: (jobToken: string) => void, w2lTemplateUploadJobs: Array, + enqueueVocoderUploadJob: (jobToken: string) => void, + vocoderUploadJobs: Array, + textBuffer: string, setTextBuffer: (textBuffer: string) => void, clearTextBuffer: () => void, @@ -293,7 +298,7 @@ class NewVocodesContainer extends React.Component { - { - { - + + + + ; +} + +function SessionVocoderUploadResultListFc(props: Props) { + let results: Array = []; + + props.vocoderUploadJobs.forEach((job) => { + if (!job.maybeDownloadedEntityToken) { + let stateDescription = "Pending..."; + + switch (job.jobState) { + case JobState.PENDING: + case JobState.UNKNOWN: + stateDescription = + job.maybeExtraStatusDescription == null + ? "Pending..." + : job.maybeExtraStatusDescription; + break; + case JobState.STARTED: + stateDescription = + job.maybeExtraStatusDescription == null + ? "Started..." + : job.maybeExtraStatusDescription; + break; + case JobState.ATTEMPT_FAILED: + stateDescription = `Failed ${job.attemptCount} attempt(s). Will retry...`; + break; + case JobState.COMPLETE_FAILURE: + case JobState.DEAD: + stateDescription = + "Failed Permanently. Please tell us in Discord so we can fix. :("; + break; + case JobState.COMPLETE_SUCCESS: + stateDescription = "Success!"; // Not sure why we're here instead of other branch! + break; + } + + results.push( +
+
{stateDescription}
+
+ ); + } else { + let permalink = `/vocoder/${job.maybeDownloadedEntityToken}`; + + results.push( +
+ + Complete! + + See & use vocoder model + + +
+ ); + } + }); + + let title = ; + if (results.length !== 0) { + title = ( + + Vocoder Upload Status + + ); + } + + return ( + +
+
{title}
+
{results}
+
+
+ ); +} + +export { SessionVocoderUploadResultListFc }; diff --git a/packages/fakeyou/src/v2/view/contribute/ContributeIndexPage.tsx b/packages/fakeyou/src/v2/view/contribute/ContributeIndexPage.tsx index 330580a43d..c804575f6f 100644 --- a/packages/fakeyou/src/v2/view/contribute/ContributeIndexPage.tsx +++ b/packages/fakeyou/src/v2/view/contribute/ContributeIndexPage.tsx @@ -13,8 +13,8 @@ import { faImage, faTags, faHandsHelping, + faChartArea, } from "@fortawesome/free-solid-svg-icons"; -import { USE_REFRESH } from "../../../Refresh"; import { motion } from "framer-motion"; import { container, item, panel } from "../../../data/animation"; @@ -31,96 +31,6 @@ function ContributeIndexPage(props: Props) { ? t("pages.contributeIndex.buttonCreateCategory") : t("pages.contributeIndex.buttonSuggestCategory"); - if (!USE_REFRESH) { - return ( -
-
-

- {" "} - {t("pages.contributeIndex.heroTitle")}{" "} -

-

- - You make FakeYou better by contributing - -

-
- -
- -
-

{t("pages.contributeIndex.introText")}

- -

- {t("pages.contributeIndex.headingUploadModels")} -

- -

- - Create new voices and video templates for FakeYou. - - to learn how. - -

- - - {t("pages.contributeIndex.buttonUploadVoice")} - - -
- - - {t("pages.contributeIndex.buttonUploadW2lVideo")} - - -
- - - {t("pages.contributeIndex.buttonUploadW2lPhoto")} - - -

{categoryHeading}

- -

{t("pages.contributeIndex.describeSuggest")}

- - - {categoryButton} - - -

- {t("pages.contributeIndex.headingMore")} -

- -

- - Want to contribute code, design, or data science? - - ! - -

-
-
- ); - } - return (
@@ -161,6 +71,10 @@ function ContributeIndexPage(props: Props) { {t("pages.contributeIndex.buttonUploadVoice")} + + + Upload Vocoder + {t("pages.contributeIndex.buttonUploadW2lVideo")} diff --git a/packages/fakeyou/src/v2/view/upload/UploadTtsModelFc.tsx b/packages/fakeyou/src/v2/view/upload/UploadTtsModelPage.tsx similarity index 99% rename from packages/fakeyou/src/v2/view/upload/UploadTtsModelFc.tsx rename to packages/fakeyou/src/v2/view/upload/UploadTtsModelPage.tsx index 081d6f15b4..08fb4c80c7 100644 --- a/packages/fakeyou/src/v2/view/upload/UploadTtsModelFc.tsx +++ b/packages/fakeyou/src/v2/view/upload/UploadTtsModelPage.tsx @@ -23,7 +23,7 @@ interface TtsModelUploadJobResponsePayload { job_token?: string; } -function UploadTtsModelFc(props: Props) { +function UploadTtsModelPage(props: Props) { let history = useHistory(); const [downloadUrl, setDownloadUrl] = useState(""); @@ -232,4 +232,4 @@ function UploadTtsModelFc(props: Props) { ); } -export { UploadTtsModelFc }; +export { UploadTtsModelPage }; diff --git a/packages/fakeyou/src/v2/view/upload/UploadVocoderPage.tsx b/packages/fakeyou/src/v2/view/upload/UploadVocoderPage.tsx new file mode 100644 index 0000000000..c29a694356 --- /dev/null +++ b/packages/fakeyou/src/v2/view/upload/UploadVocoderPage.tsx @@ -0,0 +1,182 @@ +import React, { useState } from "react"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { DiscordLink } from "@storyteller/components/src/elements/DiscordLink"; +import { EnqueueRetrieval, EnqueueRetrievalIsOk } from "@storyteller/components/src/api/retrieval/EnqueueRetrieval"; +import { useHistory } from "react-router-dom"; +import { v4 as uuidv4 } from "uuid"; +import { BackLink } from "../_common/BackLink"; +import { Link } from "react-router-dom"; +import { FrontendUrlConfig } from "../../../common/FrontendUrlConfig"; +import { motion } from "framer-motion"; +import { container, item, panel } from "../../../data/animation"; +import { VocoderUploadJob } from "@storyteller/components/src/jobs/VocoderUploadJobs"; +import { SessionVocoderUploadResultListFc } from "../_common/SessionVocoderUploadResultsListFc"; + +interface Props { + sessionWrapper: SessionWrapper; + enqueueVocoderUploadJob: (jobToken: string) => void; + vocoderUploadJobs: Array; +} + +function UploadVocoderPage(props: Props) { + let history = useHistory(); + + const [downloadUrl, setDownloadUrl] = useState(""); + const [title, setTitle] = useState(""); + const [downloadUrlInvalidReason] = useState(""); + const [titleInvalidReason] = useState(""); + + if (!props.sessionWrapper.isLoggedIn()) { + return ( +
+
+

+ You need to create an account or log in. +

+
+ + Login + + + Sign Up + +
+
+
+ ); + } + + const handleDownloadUrlChange = (ev: React.FormEvent) => { + ev.preventDefault(); + const downloadUrlValue = (ev.target as HTMLInputElement).value; + setDownloadUrl(downloadUrlValue); + return false; + }; + + const handleTitleChange = (ev: React.FormEvent) => { + ev.preventDefault(); + const titleValue = (ev.target as HTMLInputElement).value; + setTitle(titleValue); + return false; + }; + + const handleFormSubmit = async (ev: React.FormEvent) => { + ev.preventDefault(); + + let idempotencyToken = uuidv4(); + + const request = { + idempotency_token: idempotencyToken, + title: title, + download_url: downloadUrl, + generic_download_type: 'hifigan', + }; + + const response = await EnqueueRetrieval(request); + + if (EnqueueRetrievalIsOk(response)) { + props.enqueueVocoderUploadJob(response.job_token); + history.push("/"); + } + + return false; + }; + + return ( + +
+
+ + Upload Vocoder Model + + Make your voice sound better! + + + +
+
+ + +

+ If you're new to voice cloning, join our{" "} + + + {" "} + to get started. We have a friendly community that can help you start + creating your own voices of your favorite characters. +

+ +

+ Once your vocoder is successfully uploaded, you'll be able to + add it to your voice models. Others will be able to use it too, and + you'll get credited. +

+ + {/* TODO TEMP (2022-03-08)

+ Please do not upload voices that you didn't train yourself or voices of individuals + who wish to not be voice cloned. We'll post a list of banned voices soon. +

*/} +
+ + +
+
+
+
+ +
+ +
+

{titleInvalidReason}

+
+ + {/* + https://drive.google.com/file/d/{TOKEN}/view?usp=sharing + */} +
+ +
+ +
+

{downloadUrlInvalidReason}

+
+
+ +
+
+ + + + +
+ + +
+ ); +} + +export { UploadVocoderPage }; diff --git a/packages/fakeyou/src/v2/view/upload/UploadW2lPhotoFc.tsx b/packages/fakeyou/src/v2/view/upload/UploadW2lPhotoPage.tsx similarity index 98% rename from packages/fakeyou/src/v2/view/upload/UploadW2lPhotoFc.tsx rename to packages/fakeyou/src/v2/view/upload/UploadW2lPhotoPage.tsx index 1dad169f11..a80e176c01 100644 --- a/packages/fakeyou/src/v2/view/upload/UploadW2lPhotoFc.tsx +++ b/packages/fakeyou/src/v2/view/upload/UploadW2lPhotoPage.tsx @@ -21,7 +21,7 @@ interface Props { w2lTemplateUploadJobs: Array; } -function UploadW2lPhotoFc(props: Props) { +function UploadW2lPhotoPage(props: Props) { let history = useHistory(); const [downloadUrl, setDownloadUrl] = useState(""); @@ -174,4 +174,4 @@ function UploadW2lPhotoFc(props: Props) { ); } -export { UploadW2lPhotoFc }; +export { UploadW2lPhotoPage }; diff --git a/packages/fakeyou/src/v2/view/upload/UploadW2lVideoFc.tsx b/packages/fakeyou/src/v2/view/upload/UploadW2lVideoPage.tsx similarity index 98% rename from packages/fakeyou/src/v2/view/upload/UploadW2lVideoFc.tsx rename to packages/fakeyou/src/v2/view/upload/UploadW2lVideoPage.tsx index 38b27e8008..6f41bc339d 100644 --- a/packages/fakeyou/src/v2/view/upload/UploadW2lVideoFc.tsx +++ b/packages/fakeyou/src/v2/view/upload/UploadW2lVideoPage.tsx @@ -21,7 +21,7 @@ interface Props { w2lTemplateUploadJobs: Array; } -function UploadW2lVideoFc(props: Props) { +function UploadW2lVideoPage(props: Props) { let history = useHistory(); const [downloadUrl, setDownloadUrl] = useState(""); @@ -173,4 +173,4 @@ function UploadW2lVideoFc(props: Props) { ); } -export { UploadW2lVideoFc }; +export { UploadW2lVideoPage }; diff --git a/yarn.lock b/yarn.lock index fa573aac7b..97fdc6a14a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11746,14 +11746,6 @@ react-scripts@5.0.0: optionalDependencies: fsevents "^2.3.2" -react-tsparticles@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/react-tsparticles/-/react-tsparticles-2.1.3.tgz#45e570d869c3b26760525d8dcc52ea5cc8095f0c" - integrity sha512-1Jc0Z8umkCHzY3qebs7nBLUXfeygEBAdubfWg7PjeYuYmjUHymneUXzXGiPLbqIMN+QQNM69485O3QLWks9lng== - dependencies: - fast-deep-equal "^3.1.3" - tsparticles-engine "^2.0.6" - react@^16.13.1: version "16.14.0" resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" @@ -13601,322 +13593,6 @@ tslib@^2.1.0, tslib@^2.3.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tsparticles-engine@^2.0.6, tsparticles-engine@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-engine/-/tsparticles-engine-2.1.3.tgz#d8922cf7811c40e5e9b87b18f9cc86043af8e900" - integrity sha512-BmCo534tF5yRQofUczGSfCFrdeMYO6e7FXHv6AdRxL+j9NNPH3zF4HX6k24tbO4kmTH8tGX/+F9mugEQVo9AEg== - -tsparticles-interaction-external-attract@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-attract/-/tsparticles-interaction-external-attract-2.1.3.tgz#9a6be2683d77f9ed214a0ca4d478c40bed87b2f4" - integrity sha512-r9DDDlmzA47j0hrmur/2ITip4Qwki3fM1yNgmro048CPamF5pnrhCZBG8zq8hy6BO8KASe55OU5KCAJ9Zt3T2w== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-bounce@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-bounce/-/tsparticles-interaction-external-bounce-2.1.3.tgz#69ba88c7148a1c5790b570f38ea36ca365341dc8" - integrity sha512-ueRHeuQhXMgWGhxFd3REK2/L4QJjhAuQxGZxIM6Tl80EmYNsCXSyQJ3RG/A839eLpGHDg5fLIytcilfu9o3XvA== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-bubble@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-bubble/-/tsparticles-interaction-external-bubble-2.1.3.tgz#27cc5350df4d968e0be8d1215de82cc173074272" - integrity sha512-0ov4Glx0xfANPX7Tw09vOGC4MjrMoQkA8dqaRuPl8Q2SPGIMpWUWrBK9LzaZdfgtCixSeI+D/c7sgwEU5NovJg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-connect@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-connect/-/tsparticles-interaction-external-connect-2.1.3.tgz#bab9643e5e2b835defa90f54547091dd93d7efd1" - integrity sha512-dHbVmYboa8bCEjkCn6QA81Lam6h+2nEORwL/ALee1gAsgyHU+cHWZ9umrL5dhYVbW3uxIn03zwZEwO+tSbISLw== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-grab@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-grab/-/tsparticles-interaction-external-grab-2.1.3.tgz#2c0e788b37c104682d74fd7c01bdb9d362aa18ed" - integrity sha512-Q/wbjf24YozYJJ73r2ISEQ5ixjgBXEe2s0fAW+7r+p3D4MGTYUPLmFdapislO9AW9mRLif60k4T2cWzbXxmx+w== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-pause@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-pause/-/tsparticles-interaction-external-pause-2.1.3.tgz#86ad1cc120b79b2ce33845372915da26f1e9a898" - integrity sha512-M1OMqN1pkpUcPdXdZoqjSJNfXUZy1qCTu+Yuy6O2trQUL2fMmnqWYUxqvcOLVgGSuacC1PCnoypBXt0BIjKyhw== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-push@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-push/-/tsparticles-interaction-external-push-2.1.3.tgz#2d259b65397555ec12667ab5c06901cb0e462957" - integrity sha512-tRzQy8DgxPy3iQkDCARKbRy1OFST4f+WOvzzMEyphddbFZq5cllvT4s61sVrzH4gWO+6ryT/e9PQH1Q0sYG9Mw== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-remove@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-remove/-/tsparticles-interaction-external-remove-2.1.3.tgz#3e6fd4abbe4e4bca1aed49a228908d265d3e4ac0" - integrity sha512-9jWds7NcQmrptg+RcRAKYy/vTtwAMz5DbIu4W1WjJ13NmoGuL0opv58bADEaY3rCEXLDPk2L8YU2792rWkp5Rg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-repulse@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-repulse/-/tsparticles-interaction-external-repulse-2.1.3.tgz#01f10b9a8aeb50af88dd388af5d4ddb02147ea41" - integrity sha512-MuZ1yCCxcL9h87oKtqgf+/m8kSFlo3L0J/hMK5dfGFKVhF3TR9kFb/4gCZngtCUUZh5ciWHpvnUwZ9Wk/SakEg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-external-trail@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-trail/-/tsparticles-interaction-external-trail-2.1.3.tgz#1ed0e9f17b86bd0c25eb65eacf94f89b22793413" - integrity sha512-IhrCgksvwP1JvZ31U/G5Hypp2WQ4ID8foB4IbOVD/mOvemUQW3G8o/cohSDwKXB5jQFclGgQOizMK/k/0wVMAg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-particles-attract@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-particles-attract/-/tsparticles-interaction-particles-attract-2.1.3.tgz#df28dfa4310ae8a81eab50203490b6d21c80c9bb" - integrity sha512-AG1HtpquRE5qTR2V3lnQ5fcCjlBa9RzxiiVeOX5dJDhvLrBA2NUPNRkGTMyl6PJZSnFnjMDKFQ/d24/A+ILWxQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-particles-collisions@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-particles-collisions/-/tsparticles-interaction-particles-collisions-2.1.3.tgz#6452c8044eb513102d97e315d6a1d5d4b6056383" - integrity sha512-V4x6EzjmkWZBuUfVvCBelBbxYx+eiHYTf18ebLE73atfaSKnMHo9UentU18W47Zw0Z5aur+4/V0hS2uUFmSq1Q== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-interaction-particles-links@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-interaction-particles-links/-/tsparticles-interaction-particles-links-2.1.3.tgz#f3a4573c130659f4da630ab0dc6a61aa05c09334" - integrity sha512-/gh3MAwQUfufkXcOggw0UwFJBXgs0hm0cvCNRFLx54qmzGFNjy7efkOFxl3YeqyQanqLiKYZmHdZEkNEYMnWIQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-move-base@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-move-base/-/tsparticles-move-base-2.1.3.tgz#b958970e07725340cd6ddbb213639173eb2b5c25" - integrity sha512-aq9k3682zaKxe6R8MwdldoYnizCTntUj7VbJ0kXQ2QV+F/Fk8y50okbYslxbpKASP6yIGE9wXK3m9QqH5VQAXQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-move-parallax@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-move-parallax/-/tsparticles-move-parallax-2.1.3.tgz#25b494e960aecff77f6f7def4f96a3bd345753a3" - integrity sha512-Miy/eMYTc+dO4n9wtd6PpONDpAy1Ta0+4tSY6K+QBse+Fp5bjokBSpnwDyAmIbgAcbj5XMlMp8yzCcE7ptlIhg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-particles.js@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-particles.js/-/tsparticles-particles.js-2.1.3.tgz#431ed900210fca8fe40be0b52a06244307eef9f7" - integrity sha512-V1NZ3b8rUrRzaFixq9pUDw2OxAIYp6b8/CSmt/3SR7UZnC0q/lSAw9HDQzqhsCNOpFc0PzQa0ogB6sMY3eaEgA== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-plugin-absorbers@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-plugin-absorbers/-/tsparticles-plugin-absorbers-2.1.3.tgz#d8e79389287484bd7d030b6b6e32fb6b279b61be" - integrity sha512-MRkP05QVNYwA4rV0nWTKOc+dANJ+dzw+t55qI7Xh9nmUineRJc784fjgZHk+/NDjgcfNb6RN05BSDkLzlANy3g== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-plugin-emitters@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-plugin-emitters/-/tsparticles-plugin-emitters-2.1.3.tgz#dd7769049a59e050e5a35877809103020d7d2ba1" - integrity sha512-G5IB+06AQR066Ss9XeszXsPbikEJjmdal/IzIwALaslQUIlGgJQhlPYY3R1aUAgtY7Oit0O6gFDqURs3dj9d8Q== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-plugin-polygon-mask@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-plugin-polygon-mask/-/tsparticles-plugin-polygon-mask-2.1.3.tgz#ca3fbe7c8540ae018acb10549384daff66b1172e" - integrity sha512-D8uuPZ+MpO2nBjeoYThePty3G3MGmBNIlQE92anrfUTEEKYjTlczY+nHlORfdNBL9fFwMiMnr/FgJwD0D68BdQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-circle@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-circle/-/tsparticles-shape-circle-2.1.3.tgz#a771e0aa40060104cbc85a0a402eb1bc4aa5e10e" - integrity sha512-OA6C67QEB3lL+GWOMOAmINYkHDO9LO929OTtkce6YDtrvm44zND5VhgevVYhriPvkxE5/n85F13cWNo2rO8LRw== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-image@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-image/-/tsparticles-shape-image-2.1.3.tgz#418ccb8b9505a65b9b264d040384d7d07c1e5d48" - integrity sha512-aV9JFnM4FjVIB0fNSf3agkiXPySdiM1f+9JyUaKSfMxYZPoY7hj6Mva3hQJQ+2GLDQE5fTjj+/IH9qQuSm5eiA== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-line@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-line/-/tsparticles-shape-line-2.1.3.tgz#1761dc2081f9271ef4edc0c8bbeb2e896661a3a5" - integrity sha512-22A3VvZvTN5g1iql8hNIU6NLcT72WKBtOPwKEFBTom7Foo7xrOH/xvjbTVN/TDUDKD86dMnsKVn0xF2NHk634A== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-polygon@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-polygon/-/tsparticles-shape-polygon-2.1.3.tgz#a143f5a1eca6d702008ba509936fc22ae26fa07f" - integrity sha512-VEMD1K+uvPY7dq8JBYQn6zsuRlDiRyMGnxTy6XKTQhVp3Zs6zwZ/789WnepUc42QAYvKzofg8G/POsevhOWOvA== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-square@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-square/-/tsparticles-shape-square-2.1.3.tgz#f28d9c60a1189ea9ee1982968e6be276220adba7" - integrity sha512-/q7aOUM5Kz+SRhpyICAvyBRmZy1Xt9Z545GHy6tqmxNn4RmiWNoqUda1V2DmMBD+rRb5jan7cQb37HQvltdrPQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-star@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-star/-/tsparticles-shape-star-2.1.3.tgz#63860426121391941585e9adc698ee140651d405" - integrity sha512-aKsWgK1iGJSBOpWJ9u5JW9X0zAWz9G1jMYSScCbLEVAQ98WV7xSEYsNOSdanedOsXfH2PNssk4KKxb5Cwz8qcg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-shape-text@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-shape-text/-/tsparticles-shape-text-2.1.3.tgz#aee95157b72d732acaefc2d6ed7d732c8a31833d" - integrity sha512-bsuHXHECRpl9Ty/70pEv1JHgdEjdq8GI2TrNCN7lyANuIFBka5gvmHqOIdKtuebqJb6OlNQB5E58EOiHvLObfw== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-slim@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-slim/-/tsparticles-slim-2.1.3.tgz#6f60191b91ca75ee4257de49b67662277b020a31" - integrity sha512-8FYaJt0oEWyKzLevi6dslFah5fIPAuxuqW25aJJxZJT0fu83HtHqgLinieFdoNS13afu+qKp+XlwYrkkczjStg== - dependencies: - tsparticles-engine "^2.1.3" - tsparticles-interaction-external-attract "^2.1.3" - tsparticles-interaction-external-bounce "^2.1.3" - tsparticles-interaction-external-bubble "^2.1.3" - tsparticles-interaction-external-connect "^2.1.3" - tsparticles-interaction-external-grab "^2.1.3" - tsparticles-interaction-external-pause "^2.1.3" - tsparticles-interaction-external-push "^2.1.3" - tsparticles-interaction-external-remove "^2.1.3" - tsparticles-interaction-external-repulse "^2.1.3" - tsparticles-interaction-particles-attract "^2.1.3" - tsparticles-interaction-particles-collisions "^2.1.3" - tsparticles-interaction-particles-links "^2.1.3" - tsparticles-move-base "^2.1.3" - tsparticles-move-parallax "^2.1.3" - tsparticles-particles.js "^2.1.3" - tsparticles-shape-circle "^2.1.3" - tsparticles-shape-image "^2.1.3" - tsparticles-shape-line "^2.1.3" - tsparticles-shape-polygon "^2.1.3" - tsparticles-shape-square "^2.1.3" - tsparticles-shape-star "^2.1.3" - tsparticles-shape-text "^2.1.3" - tsparticles-updater-angle "^2.1.3" - tsparticles-updater-color "^2.1.3" - tsparticles-updater-life "^2.1.3" - tsparticles-updater-opacity "^2.1.3" - tsparticles-updater-out-modes "^2.1.3" - tsparticles-updater-size "^2.1.3" - tsparticles-updater-stroke-color "^2.1.3" - -tsparticles-updater-angle@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-angle/-/tsparticles-updater-angle-2.1.3.tgz#2b34b9279d7e968717e5e86bd9e23d1f53223e76" - integrity sha512-9yiCS8iZYexSyLAaTOkge2WM5ZUDZv6B4lWpgoUQANNScnrDkCkPHOb7ZYq4fbHYA+GElppAjamDsg2WBT5bsg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-color@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-color/-/tsparticles-updater-color-2.1.3.tgz#59e2973fd021d6cff0301b32e408083b48279301" - integrity sha512-gsCLmwJsPauVyExRMUu0IA9R5HbEKgZ4/wndLLFxpdz/+b8PElL+SMxywEUf4WWWMC8o/1JQxlQmuW0Q+EWwyQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-life@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-life/-/tsparticles-updater-life-2.1.3.tgz#8fc77c1ae0b4a995248a082c8abb5dd92393a6fe" - integrity sha512-de9kkz6PFDq8OSrhNdmD24SpUC92Oz15QCiu4Sxbg98ngUun6ejE/3SZku9CX0lXgDK8k9IcH1SrIT4RzM2JKA== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-opacity@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-opacity/-/tsparticles-updater-opacity-2.1.3.tgz#ae8734fa9320322447c81f27a6ece448168b9809" - integrity sha512-WQEFM4KyLCE87LWUn8Wxh/Wmwlg/GLXvTJkFYRGjD1AjW85CJO9DM0t3O+xjKq3hR4LaMFRHF4Fki/PhmnUF+Q== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-out-modes@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-out-modes/-/tsparticles-updater-out-modes-2.1.3.tgz#fef5eed3b6cb934fcb9e9b33f635d373d6adbbbf" - integrity sha512-u7JtAKyvKCq7gwuN+NlzVJNYwvVBMx3wPCyJNZvaK9umM51lQHJCRsNj57lJ7jSJlaWsEYspw3rPXRvaw/+dVA== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-roll@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-roll/-/tsparticles-updater-roll-2.1.3.tgz#ebd6d0f06d4ec552121277f5a5c90230190e739f" - integrity sha512-0T9wEuclcV1nfNlmJH4zDco+7xB/tBFzCYVbZ1qnfwT29J66gz7/9iBYk7iRr91uhxckHXO4VcyJCk+e0vVb1w== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-size@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-size/-/tsparticles-updater-size-2.1.3.tgz#42e2250c178ee192e89bc82385b92d4e7f4fe2eb" - integrity sha512-uPpSvi73saaytTSfEtiLdlaJU81uLhDWwMe8UoVEsIggJ5nUQzGU9/WP97iSYR10zr9Xzof1Zweksj4hWvf3mQ== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-stroke-color@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-stroke-color/-/tsparticles-updater-stroke-color-2.1.3.tgz#e53b4cf27fb130b2ae088c1544d2fd7f5871f082" - integrity sha512-TSZcbhtt92710fBzF19OqwDu6ZN0LccLHFNgmWPy1EFT5oULe9ZrH6aWqEJ7q4HCdbxZytW+7rv5YRsRL15M0Q== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-tilt@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-tilt/-/tsparticles-updater-tilt-2.1.3.tgz#e653c381568a7bb276f0fca04822727affa7cf58" - integrity sha512-avSUIc6QOp/caaWUNodCQhNwLdvuC8fhDn7JiUcq2Z95YwcJTfM1JsvD2J6UeNQL5Jj2+cxjtECCQ1JzK0Sutg== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-twinkle@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-twinkle/-/tsparticles-updater-twinkle-2.1.3.tgz#61d905276e92fe2282c781f172bdef90116a778f" - integrity sha512-89WwhJRCIKWNUdK56pq8DuXuDriO/1gfddEImTeFGmA/q1my1W3nGX8LvHIR0/d9kS6tuL6sQIGolXOLwgp5Yw== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles-updater-wobble@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles-updater-wobble/-/tsparticles-updater-wobble-2.1.3.tgz#dd2ada0b9efd4a5e481e5a35fe653f01eed9bac0" - integrity sha512-UpucA4hBIn/bLYZeJgCb/JPHmSfHIUpjbEenyHm7u1BK+PuiiNDVZ13o9ClIXKDiOK6ebgIMy8afxmPIHGmG1Q== - dependencies: - tsparticles-engine "^2.1.3" - -tsparticles@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tsparticles/-/tsparticles-2.1.3.tgz#c23dce62f69274872f9f1044769227c06f0daf2a" - integrity sha512-7fKNV+6B9CcKQbFVFre2Rx2BAkW86/1jXjZGh9B0u+l6EARZSwS2aMOYWP1lJuzvHSmupoG0cGmZfKTr7/AIZw== - dependencies: - tsparticles-engine "^2.1.3" - tsparticles-interaction-external-trail "^2.1.3" - tsparticles-plugin-absorbers "^2.1.3" - tsparticles-plugin-emitters "^2.1.3" - tsparticles-plugin-polygon-mask "^2.1.3" - tsparticles-slim "^2.1.3" - tsparticles-updater-roll "^2.1.3" - tsparticles-updater-tilt "^2.1.3" - tsparticles-updater-twinkle "^2.1.3" - tsparticles-updater-wobble "^2.1.3" - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"