diff --git a/packages/fakeyou.com/public/mascot/trump.png b/packages/fakeyou.com/public/mascot/trump.png deleted file mode 100644 index dcb115240e..0000000000 Binary files a/packages/fakeyou.com/public/mascot/trump.png and /dev/null differ diff --git a/packages/fakeyou.com/public/mascot/trump.webp b/packages/fakeyou.com/public/mascot/trump.webp new file mode 100644 index 0000000000..f81a90ab7c Binary files /dev/null and b/packages/fakeyou.com/public/mascot/trump.webp differ diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPage.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPage.tsx index b41955c8b4..5044789912 100644 --- a/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPage.tsx @@ -45,6 +45,7 @@ import { import { DynamicallyCategorizeModels } from "../../../../model/categories/SyntheticCategory"; import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle"; +import { SearchOmnibar } from "./search/SearchOmnibar"; export interface EnqueueJobResponsePayload { success: boolean; inference_job_token?: string; @@ -359,7 +360,21 @@ function TrumpTtsPage(props: Props) { onSubmit={handleFormSubmit} > {/* Explore Rollout */} - + {/* diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx index e599af8eb8..38a7452657 100644 --- a/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx @@ -8,6 +8,7 @@ import { faStar, faArrowRight, faWaveformLines, + faVolume, } from "@fortawesome/pro-solid-svg-icons"; import { Analytics } from "../../../../common/Analytics"; import { WebUrl } from "../../../../common/WebUrl"; @@ -20,7 +21,7 @@ interface Props { export function TtsPageHero(props: Props) { const randomImage = useMemo(() => { - const images = ["mascot/trump.png"]; + const images = ["mascot/trump.webp"]; return images[Math.floor(Math.random() * images.length)]; }, []); @@ -54,7 +55,7 @@ export function TtsPageHero(props: Props) { > ); - const titleIcon = ; + const titleIcon = ; const title = <>Donald Trump TTS>; const subText = ( <>Type your text below and hear it in the voice of Donald Trump.> diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/SearchOmnibar.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/search/SearchOmnibar.tsx new file mode 100644 index 0000000000..9feb9339ef --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/SearchOmnibar.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { TtsCategoryType } from "../../../../../AppWrapper"; +import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels"; +import { ExploreVoicesTray } from "./components/ExploreVoicesTray"; +import { ScopedVoiceModelOptions } from "./components/ScopedVoiceModelOptions"; +import { VoiceCountLabel } from "./components/VoiceCountLabel"; + +interface Props { + allTtsCategories: TtsCategoryType[]; + allTtsModels: TtsModelListItem[]; + + allTtsCategoriesByTokenMap: Map; + allTtsModelsByTokenMap: Map; + ttsModelsByCategoryToken: Map>; + + dropdownCategories: TtsCategoryType[][]; + setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void; + + selectedCategories: TtsCategoryType[]; + setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void; + + maybeSelectedTtsModel?: TtsModelListItem; + setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void; + + selectedTtsLanguageScope: string; + setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void; +} + +export function SearchOmnibar(props: Props) { + return ( + <> + + + + + + + + + + > + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/components/ExploreVoicesTray.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/ExploreVoicesTray.tsx new file mode 100644 index 0000000000..64c7699137 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/ExploreVoicesTray.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import { faEraser } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { LanguageOptions } from "../filters/LanguageOptions"; +import { TtsCategoryType } from "../../../../../../AppWrapper"; +import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels"; +import { CategoryOptions } from "../filters/CategoryOptions"; +import { t } from "i18next"; + +interface Props { + allTtsCategories: TtsCategoryType[]; + allTtsModels: TtsModelListItem[]; + + allTtsCategoriesByTokenMap: Map; + allTtsModelsByTokenMap: Map; + ttsModelsByCategoryToken: Map>; + + dropdownCategories: TtsCategoryType[][]; + setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void; + + selectedCategories: TtsCategoryType[]; + setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void; + + maybeSelectedTtsModel?: TtsModelListItem; + setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void; + + selectedTtsLanguageScope: string; + setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void; +} + +export function ExploreVoicesTray(props: Props) { + const { + allTtsCategories, + allTtsCategoriesByTokenMap, + ttsModelsByCategoryToken, + dropdownCategories, + setDropdownCategories, + selectedCategories, + setSelectedCategories, + maybeSelectedTtsModel, + } = props; + + const doChangeCategory = (level: number, maybeToken: string) => { + // Slice off all the irrelevant child category choices, then append new choice. + let newCategorySelections = selectedCategories.slice(0, level); + + // And the dropdowns themselves + let newDropdownCategories = dropdownCategories.slice(0, level + 1); + + let category = allTtsCategoriesByTokenMap.get(maybeToken); + if (!!category) { + newCategorySelections.push(category); + } + + setSelectedCategories(newCategorySelections); + + const newSubcategories = allTtsCategories.filter((category) => { + return category.maybe_super_category_token === maybeToken; + }); + + newDropdownCategories.push(newSubcategories); + setDropdownCategories(newDropdownCategories); + + // We might have switched into a category without our selected TTS model. + // If so, pick a new TTS model. + let maybeNewModel = undefined; + const availableModelsForCategory = ttsModelsByCategoryToken.get(maybeToken); + if (!!availableModelsForCategory && !!maybeSelectedTtsModel) { + const modelValid = availableModelsForCategory.has(maybeSelectedTtsModel); + if (!modelValid) { + maybeNewModel = Array.from(availableModelsForCategory)[0]; + } + } + if (!!maybeNewModel) { + props.setMaybeSelectedTtsModel(maybeNewModel); + } + }; + + const handleChangeCategory = (level: number, maybeCategoryToken?: string) => { + if (!maybeCategoryToken) { + return true; + } + doChangeCategory(level, maybeCategoryToken); + return true; + }; + + return ( + + + + + {t("tts.TtsModelListPage.exploreModal.languageLabel")} + + + + + + + + {t("tts.TtsModelListPage.exploreModal.categoryLabel")} + + { + handleChangeCategory(0, "*"); + }} + type="button" + > + + {t("tts.TtsModelListPage.exploreModal.clearCategoryFilterButton")} + + + + + + + + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/components/ScopedVoiceModelOptions.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/ScopedVoiceModelOptions.tsx new file mode 100644 index 0000000000..f69d899705 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/ScopedVoiceModelOptions.tsx @@ -0,0 +1,209 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels"; +import { TtsCategoryType } from "../../../../../../AppWrapper"; +import Select, { createFilter } from "react-select"; +import { SearchFieldClass } from "./SearchFieldClass"; +import { FastReactSelectOption } from "../../../../_common/react_select/FastReactSelectOption"; +import { Analytics } from "../../../../../../common/Analytics"; +import { FixedSingleValueSelectOption } from "../../../../_common/react_select/FixedSingleValueSelectOption"; +import { faMicrophone } from "@fortawesome/pro-duotone-svg-icons"; + +interface Props { + allTtsCategories: TtsCategoryType[]; + allTtsModels: TtsModelListItem[]; + + allTtsCategoriesByTokenMap: Map; + allTtsModelsByTokenMap: Map; + ttsModelsByCategoryToken: Map>; + + dropdownCategories: TtsCategoryType[][]; + selectedCategories: TtsCategoryType[]; + + maybeSelectedTtsModel?: TtsModelListItem; + setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void; + + selectedTtsLanguageScope: string; +} + +export function ScopedVoiceModelOptions(props: Props) { + const { + allTtsModels, + ttsModelsByCategoryToken, + selectedCategories, + maybeSelectedTtsModel, + } = props; + + //const { t } = useTranslation(); + + const handleChange = (option: any, actionMeta: any) => { + const ttsModelToken = option?.value; + const maybeNewTtsModel = props.allTtsModelsByTokenMap.get(ttsModelToken); + if (maybeNewTtsModel !== undefined) { + props.setMaybeSelectedTtsModel(maybeNewTtsModel); + } + }; + + const leafiestCategory = selectedCategories[selectedCategories.length - 1]; + + let leafiestCategoryModels: Array = []; + + if (leafiestCategory !== undefined) { + leafiestCategoryModels = Array.from( + ttsModelsByCategoryToken.get(leafiestCategory.category_token) || new Set() + ); + } else { + leafiestCategoryModels = Array.from(new Set(allTtsModels)); + } + + interface DropdownOption { + label: string; + value: string; + creatorName?: string; + } + + let options: DropdownOption[] = leafiestCategoryModels + .filter((ttsModel) => { + // Scope to currently selected language + if (props.selectedTtsLanguageScope === "*") { + return true; // NB: Sentinel value of "*" means all languages. + } + return ( + ttsModel.ietf_primary_language_subtag === props.selectedTtsLanguageScope + ); + }) + .map((ttsModel) => { + return { + label: ttsModel.title, + value: ttsModel.model_token, + creatorName: ttsModel.creator_display_name, + }; + }); + + let selectedOption = options.find( + (option) => option.value === maybeSelectedTtsModel?.model_token + ); + + if (selectedOption === undefined && options.length > 0) { + // NB: We shouldn't select the first item in the list since that won't update the currently + // selected model. If the user were to close the dialogue, they'd think they had picked a voice, + // when in reality no state would have changed. By forcing the user to choose, the user will set + // the state appropriately. + selectedOption = { + label: "Select voice...", + value: "*", + creatorName: undefined, + }; + } + + let isLoading = false; + + if (props.allTtsModels.length === 0) { + // NB: react-select will cache values, even across different instances (!!!) + // This can cause confusion when initializing a select instance before the data + // is loaded, and the select will never update to show the new data. + // The proper way to change voices after load from a placeholder "Loading..." + // label is to use controlled props / value as is done here: + isLoading = true; + selectedOption = { + label: "Loading...", + value: "*", + creatorName: undefined, + }; + } else if (options.length === 0) { + // NB: Perhaps the user has refined their search to be too narrow (langauge + category) + selectedOption = { + label: "No results (remove some filters)", + value: "*", + creatorName: undefined, + }; + } + + const numberVoices = options.length; + + const canSearchVoices = + numberVoices > 100 || // Always allow search when there are over 100 voices + window.innerWidth >= 1000; // Always allow search when on desktop + + let select; + + // Function to build the options themselves, so we can introduce extra elements. + const formatOptionLabel = ( + data: DropdownOption, + formatOptionLabelMeta: any + ) => { + let creatorName = <>>; + if (data.creatorName !== undefined) { + creatorName = — {data.creatorName}; + } + return ( + + {data.label} + {creatorName} + + ); + }; + + // TODO: Cleanup + + if (canSearchVoices) { + select = ( + { + Analytics.ttsOpenPrimaryVoiceSelectMenu(); + }} + isLoading={isLoading} + isSearchable={true} + // NB: The following settings improve upon performance. + // See: https://github.com/JedWatson/react-select/issues/3128 + filterOption={createFilter({ ignoreAccents: false })} + components={ + { + SingleValue: FixedSingleValueSelectOption, + Option: FastReactSelectOption, + } as any + } + formatOptionLabel={formatOptionLabel} + /> + ); + } else { + select = ( + { + Analytics.ttsOpenScopedVoiceSelectMenu(); + }} + isLoading={isLoading} + // On mobile, we don't want the onscreen keyboard to take up half the UI. + autoFocus={false} + isSearchable={false} + // NB: The following settings improve upon performance. + // See: https://github.com/JedWatson/react-select/issues/3128 + filterOption={createFilter({ ignoreAccents: false })} + components={{ Option: FastReactSelectOption } as any} + formatOptionLabel={formatOptionLabel} + /> + ); + } + + return ( + <> + + + + + + + {select} + + + > + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/components/SearchFieldClass.ts b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/SearchFieldClass.ts new file mode 100644 index 0000000000..5c7ca2b92c --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/SearchFieldClass.ts @@ -0,0 +1,14 @@ +export const SearchFieldClass = { + control: (state: any) => + state.isFocused ? "select-search focused" : "select-search", + option: (state: any) => (state.isFocused ? "select-option" : "select-option"), + input: (state: any) => (state.isFocused ? "select-input" : "select-input"), + placeholder: (state: any) => + state.isFocused ? "select-placeholder" : "select-placeholder", + singleValue: (state: any) => + state.isFocused ? "select-value" : "select-value", + menu: (state: any) => + state.isFocused ? "select-container" : "select-container", + indicatorSeparator: (state: any) => + state.isFocused ? "select-separator" : "select-separator", +}; diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/components/VoiceCountLabel.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/VoiceCountLabel.tsx new file mode 100644 index 0000000000..4f6093bd8a --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/components/VoiceCountLabel.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { Trans } from "react-i18next"; +import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels"; +import { TtsCategoryType } from "../../../../../../AppWrapper"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faShuffle } from "@fortawesome/pro-solid-svg-icons"; +import { Analytics } from "../../../../../../common/Analytics"; +import { GetRandomArrayValue } from "@storyteller/components/src/utils/GetRandomArrayValue"; +import Tippy from "@tippyjs/react"; +import "tippy.js/dist/tippy.css"; + +interface Props { + allTtsModels: TtsModelListItem[]; + ttsModelsByCategoryToken: Map>; + selectedCategories: TtsCategoryType[]; + selectedTtsLanguageScope: string; + setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void; +} + +// NB/TODO: This duplicates the work of a sister component, but it was the fastest way +// to hack this in without passing callbacks around or moving calculation higher up the +// component tree. + +export function VoiceCountLabel(props: Props) { + const { allTtsModels, ttsModelsByCategoryToken, selectedCategories } = props; + + const leafiestCategory = selectedCategories[selectedCategories.length - 1]; + + let leafiestCategoryModels: Array = []; + + if (leafiestCategory !== undefined) { + leafiestCategoryModels = Array.from( + ttsModelsByCategoryToken.get(leafiestCategory.category_token) || new Set() + ); + } else { + leafiestCategoryModels = Array.from(new Set(allTtsModels)); + } + + let possibleVoices = leafiestCategoryModels.filter((ttsModel) => { + // Scope to currently selected language + if (props.selectedTtsLanguageScope === "*") { + return true; // NB: Sentinel value of "*" means all languages. + } + return ( + ttsModel.ietf_primary_language_subtag === props.selectedTtsLanguageScope + ); + }); + + const selectRandomVoice = () => { + let randomVoice = undefined; + for (let i = 0; i < 10; i++) { + // We're going to try to find a *good* voice. + randomVoice = GetRandomArrayValue(possibleVoices); + if ( + randomVoice.user_ratings.positive_count > + randomVoice.user_ratings.negative_count + ) { + break; // First "mostly good" voice is good. + } + if (i % 3 === 2 && randomVoice.user_ratings.total_count === 0) { + break; // Sometimes we'll return a voice that hasn't been rated. + } + } + if (randomVoice !== undefined) { + props.setMaybeSelectedTtsModel(randomVoice); + } + }; + + const voiceCount = possibleVoices.length; + + return ( + <> + + + Choose a Donald Trump Voice ({voiceCount} to choose from) + + { + Analytics.ttsClickRandomVoice(); + selectRandomVoice(); + }} + className="btn-link pt-0 ps-2 pe-0" + type="button" + > + + + + + + > + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/filters/CategoryOptions.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/search/filters/CategoryOptions.tsx new file mode 100644 index 0000000000..244888a430 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/filters/CategoryOptions.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRightLong, faTags } from "@fortawesome/free-solid-svg-icons"; +import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels"; +import { TtsCategoryType } from "../../../../../../AppWrapper"; +//import { useTranslation } from "react-i18next"; +import Select from "react-select"; +import { SearchFieldClass } from "../components/SearchFieldClass"; +import { Analytics } from "../../../../../../common/Analytics"; +import { t } from "i18next"; + +interface Props { + allTtsCategories: TtsCategoryType[]; + allTtsModels: TtsModelListItem[]; + + allTtsCategoriesByTokenMap: Map; + allTtsModelsByTokenMap: Map; + ttsModelsByCategoryToken: Map>; + + dropdownCategories: TtsCategoryType[][]; + setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void; + + selectedCategories: TtsCategoryType[]; + setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void; + + maybeSelectedTtsModel?: TtsModelListItem; + setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void; + + handleChangeCategory: (level: number, maybeToken?: string) => void; + + selectedTtsLanguageScope: string; +} + +export function CategoryOptions(props: Props) { + const { + ttsModelsByCategoryToken, + dropdownCategories, + selectedCategories, + handleChangeCategory, + } = props; + + //const { t } = useTranslation(); + + let categoryDropdowns = buildDropdowns( + dropdownCategories, + selectedCategories, + ttsModelsByCategoryToken, + handleChangeCategory + ); + + const CATEGORY_SEPARATOR = ( + + + + ); + + let categoryDropdownsWithSeparators = []; + for (let i = 0; i < categoryDropdowns.length; i++) { + categoryDropdownsWithSeparators.push(categoryDropdowns[i]); + if (i < categoryDropdowns.length - 1) { + categoryDropdownsWithSeparators.push(CATEGORY_SEPARATOR); + } + } + + return ( + <> + + {categoryDropdownsWithSeparators} + + > + ); +} + +// ========= Build dropdowns ======== + +function buildDropdowns( + dropdownCategories: TtsCategoryType[][], + selectedCategories: TtsCategoryType[], + ttsModelsByCategoryToken: Map>, + handleChangeCategory: (i: number, categoryToken?: string) => void +) { + if (dropdownCategories.length === 0 || ttsModelsByCategoryToken.size === 0) { + // While the XHR requests are still completing, we may have nothing to build. + // It's easier to return a fully disabled "loading" component. + return [ + + + + + + , + ]; + } + + let categoryDropdowns = []; + + for (let i = 0; i < dropdownCategories.length; i++) { + const currentDropdownCategories = dropdownCategories[i]; + const selectedCategory = selectedCategories[i]; + + let defaultName = i === 0 ? "All Voices" : "Select..."; + + let dropdownOptions = []; + dropdownOptions.push( + + {defaultName} + + ); + + // TODO(bt, 2023-01-18): Clean this up + + // Transform into "react-select" library compatible options + const options = currentDropdownCategories + .filter((category) => { + // If there are no models at the leaves, skip the category + const models = ttsModelsByCategoryToken.get(category.category_token); + return !(models === undefined || models.size === 0); + }) + .map((category) => { + return { + value: category.category_token, + label: category.name_for_dropdown, + }; + }); + + currentDropdownCategories.forEach((category) => { + const models = ttsModelsByCategoryToken.get(category.category_token); + if (models === undefined || models.size === 0) { + return; // If there are no models at the leaves, skip + } + dropdownOptions.push( + + {category.name_for_dropdown} + + ); + }); + + let selectedCategoryOption = undefined; + if (selectedCategory !== undefined) { + selectedCategoryOption = { + value: selectedCategory.category_token, + label: selectedCategory.name_for_dropdown, + }; + } + + if (dropdownOptions.length <= 1) { + // We've run out of subcategories. (1 == "Select...") + // No sense trying to build more. + break; + } + + let selectProps: any = { + options: options, + classNames: SearchFieldClass, + className: "w-100", + autoFocus: false, // On mobile, we don't want the onscreen keyboard to take up half the UI. + isSearchable: false, // On mobile, we don't want the onscreen keyboard to take up half the UI. + onMenuOpen: () => { + Analytics.ttsOpenCategorySelectMenu(); + }, + onChange: (option: any) => handleChangeCategory(i, option?.value), + }; + + if (selectedCategoryOption === undefined) { + // NB(bt, 2023-01-19): I'm not sure why we're having to do this to clear categories. + // If I had more time to spend with this library, I might have a better solution than this hack. + selectProps["value"] = { + value: "*", + label: t("tts.TtsModelListPage.exploreModal.selectCategoryText"), + }; + } else { + selectProps["value"] = selectedCategoryOption; + } + + categoryDropdowns.push( + + + + + + + + + ); + } + + return categoryDropdowns; +} diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/search/filters/LanguageOptions.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/search/filters/LanguageOptions.tsx new file mode 100644 index 0000000000..2eafeb569a --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/search/filters/LanguageOptions.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import { faGlobe } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Select from "react-select"; +import { SearchFieldClass } from "../components/SearchFieldClass"; +import { AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP } from "../../../../../../_i18n/AvailableLanguageMap"; +import { Analytics } from "../../../../../../common/Analytics"; +import { t } from "i18next"; + +interface Props { + selectedTtsLanguageScope: string; + setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void; +} + +export function LanguageOptions(props: Props) { + const handleChange = (option: any, actionMeta: any) => { + props.setSelectedTtsLanguageScope(option.value); + }; + + let languageOptions = Object.entries(AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP).map( + ([languageCode, language]) => { + let label = `${language.languageName}`; + + if (language.languageNameLocalized !== undefined) { + label = `${language.languageNameLocalized} / ${label}`; + } + + if (language.flags.length > 0) { + label += ` ${language.flags.join(" ")}`; + } + + return { + value: languageCode, + label: label, + }; + } + ); + + languageOptions = [ + { + label: `${t("tts.TtsModelListPage.exploreModal.allLanguagesOptionText")}`, + value: "*", + }, + ...languageOptions, + ]; + + const currentValue = + languageOptions.find((option) => { + return option.value === props.selectedTtsLanguageScope; + }) || languageOptions[0]; + + return ( + + + + + { + Analytics.ttsOpenCategorySelectMenu(); + }} + /> + + ); +}