synthetic categories

This commit is contained in:
Brandon Thomas committed 2022-01-07 16:29:16 -05:00
1 parent c477622d59
commit 6ebfaef5aa
6 files changed
+119 -49

No files matched your search

+8 -8
View File
@@ -15,7 +15,7 @@ import { TtsModelUploadJob, TtsModelUploadJobStateResponsePayload } from './jobs
import { W2lTemplateUploadJob, W2lTemplateUploadJobStateResponsePayload } from './jobs/W2lTemplateUploadJobs';
import { jobStateCanChange } from './jobs/JobStates';
import { TtsModelListItem } from './v2/api/tts/ListTtsModels';
import { TtsCategory } from './v2/api/category/ListTtsCategories';
import { TtsCategoryType } from './AppWrapper';
enum MigrationMode {
NEW_VOCODES,
@@ -27,20 +27,20 @@ interface Props {
flashVocodesNotice: boolean,
allTtsCategories: TtsCategory[],
setAllTtsCategories: (allTtsCategories: TtsCategory[]) => void,
allTtsCategories: TtsCategoryType[],
setAllTtsCategories: (allTtsCategories: TtsCategoryType[]) => void,
allTtsModels: TtsModelListItem[],
setAllTtsModels: (allTtsModels: TtsModelListItem[]) => void,
allTtsCategoriesByTokenMap: Map<string,TtsCategory>,
allTtsCategoriesByTokenMap: Map<string,TtsCategoryType>,
allTtsModelsByTokenMap: Map<string,TtsModelListItem>,
ttsModelsByCategoryToken: Map<string,Set<TtsModelListItem>>,
dropdownCategories: TtsCategory[][],
setDropdownCategories: (dropdownCategories: TtsCategory[][]) => void,
selectedCategories: TtsCategory[],
setSelectedCategories: (selectedCategories: TtsCategory[]) => void,
dropdownCategories: TtsCategoryType[][],
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void,
selectedCategories: TtsCategoryType[],
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void,
maybeSelectedTtsModel?: TtsModelListItem,
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void,
+26 -4
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { TtsCategory } from './v2/api/category/ListTtsCategories';
import { TtsModelListItem } from './v2/api/tts/ListTtsModels';
import { App } from './App';
import { v4 as uuidv4 } from 'uuid';
interface Props {
// Certan browsers (iPhone) have pitiful support for drawing APIs. Worse yet,
@@ -14,14 +15,35 @@ interface Props {
flashVocodesNotice: boolean,
}
// Special synthetic categories created on the frontend.
// Used in a union type, but they should play well with `TtsCategory`.
export class SyntheticCategory {
name: string;
name_for_dropdown: string;
category_token: string;
maybe_super_category_token?: string;
ttsModels: Set<TtsModelListItem>;
constructor(name: string, token?: string, maybe_super_category_token?: string) {
this.name = name;
this.name_for_dropdown = name;
this.category_token = !!token ? token : `syn:${uuidv4()}`;
this.maybe_super_category_token = maybe_super_category_token;
this.ttsModels = new Set();
}
}
export type TtsCategoryType = TtsCategory|SyntheticCategory;
export function AppWrapper(props: Props) {
// Caches of all objects queried
// These may be triggered by a different page than the user initially lands on.
const [allTtsCategories, setAllTtsCategories] = useState<TtsCategory[]>([]);
const [allTtsCategories, setAllTtsCategories] = useState<TtsCategoryType[]>([]);
const [allTtsModels, setAllTtsModels] = useState<TtsModelListItem[]>([]);
// Precalculated maps for lookup by primary key
const [allCategoriesByTokenMap, setAllCategoriesByTokenMap] = useState<Map<string,TtsCategory>>(new Map());
const [allCategoriesByTokenMap, setAllCategoriesByTokenMap] = useState<Map<string,TtsCategoryType>>(new Map());
const [allTtsModelsByTokenMap, setAllTtsModelsByTokenMap] = useState<Map<string,TtsModelListItem>>(new Map());
// Precalculated map for lookup by foreign key
@@ -34,13 +56,13 @@ export function AppWrapper(props: Props) {
// Outer array has length of at least one, one element per <select>
// Inner array contains the categories in each level.
// Structure: [dropdownLevel][categories]
const [dropdownCategories, setDropdownCategories] = useState<TtsCategory[][]>([]);
const [dropdownCategories, setDropdownCategories] = useState<TtsCategoryType[][]>([]);
// User selections.
// Every category in the heirarchy that has been selected by the user.
// Empty list if none are selected.
// Structure: [firstSelected, secondSelected...]
const [selectedCategories, setSelectedCategories] = useState<TtsCategory[]>([]);
const [selectedCategories, setSelectedCategories] = useState<TtsCategoryType[]>([]);
// User selections.
const [maybeSelectedTtsModel, setMaybeSelectedTtsModel] = useState<TtsModelListItem|undefined>(undefined);
+14 -13
View File
@@ -27,25 +27,37 @@ class ApiConfig {
domain: Domain;
useSsl: boolean;
v2ApiHost: string;
constructor() {
let useSsl = true;
let domain = Domain.Unknown;
if (document.location.host.includes("localhost")) {
// NB: `localhost` seems to have problems with cookies.
// I've added jungle.horse as a localhost mapped domain in /etc/hosts,
// This should be the preferred mechanism for local testing.
domain = Domain.Localhost;
useSsl = false;
} else if (document.location.host.includes("jungle.horse")) {
// NB: Local dev.
domain = Domain.JungleHorse;
useSsl = false;
useSsl = document.location.protocol === 'https:';
} else if (document.location.host.includes("vo.codes")) {
domain = Domain.Vocodes;
} else if (document.location.host.includes("fakeyou.com")) {
domain = Domain.FakeYou;
}
let v2ApiHost = "api.fakeyou.com";
if (!useSsl && (domain === Domain.Localhost || domain === Domain.JungleHorse)) {
// NB: Lack of SSL means use local development.
v2ApiHost = "api.jungle.horse";
}
this.domain = domain;
this.useSsl = useSsl;
this.v2ApiHost = v2ApiHost;
}
speakEndpoint() : string {
@@ -331,18 +343,7 @@ class ApiConfig {
}
private getNewApiHost() : string {
// NB: `localhost` seems to have problems with cookies.
// I've added jungle.horse as a localhost mapped domain in /etc/hosts
switch (this.domain) {
case Domain.Localhost:
case Domain.JungleHorse:
return "api.jungle.horse";
case Domain.Vocodes:
return "api.vo.codes";
case Domain.FakeYou:
case Domain.Unknown:
return "api.fakeyou.com";
}
return this.v2ApiHost;
}
}
+8 -8
View File
@@ -47,7 +47,7 @@ import { TtsEditCategoriesPage } from './tts/tts_edit_categories/TtsEditCategori
import { ModerationTtsCategoryListPage } from './moderation/categories/ModerationTtsCategoryListPage';
import { ModerationTtsCategoryEditPage } from './moderation/categories/ModerationTtsCategoryEditPage';
import { ModerationCategoryDeletePage } from './moderation/categories/ModerationCategoryDeletePage';
import { TtsCategory } from '../api/category/ListTtsCategories';
import { TtsCategoryType } from '../../AppWrapper';
interface Props {
sessionWrapper: SessionWrapper,
@@ -75,17 +75,17 @@ interface Props {
ttsModels: Array<TtsModelListItem>,
setTtsModels: (ttsVoices: Array<TtsModelListItem>) => void,
allTtsCategories: TtsCategory[],
setAllTtsCategories: (allTtsCategories: TtsCategory[]) => void,
allTtsCategories: TtsCategoryType[],
setAllTtsCategories: (allTtsCategories: TtsCategoryType[]) => void,
allTtsCategoriesByTokenMap: Map<string,TtsCategory>,
allTtsCategoriesByTokenMap: Map<string,TtsCategoryType>,
allTtsModelsByTokenMap: Map<string,TtsModelListItem>,
ttsModelsByCategoryToken: Map<string,Set<TtsModelListItem>>,
dropdownCategories: TtsCategory[][],
setDropdownCategories: (dropdownCategories: TtsCategory[][]) => void,
selectedCategories: TtsCategory[],
setSelectedCategories: (selectedCategories: TtsCategory[]) => void,
dropdownCategories: TtsCategoryType[][],
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void,
selectedCategories: TtsCategoryType[],
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void,
maybeSelectedTtsModel?: TtsModelListItem,
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void,
@@ -1,22 +1,22 @@
import React, { useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight, faHeadphonesAlt, faTags, faTimes } from '@fortawesome/free-solid-svg-icons';
import { TtsCategory } from '../../../api/category/ListTtsCategories';
import { TtsModelListItem } from '../../../api/tts/ListTtsModels';
import { TtsCategoryType } from '../../../../AppWrapper';
interface Props {
allTtsCategories: TtsCategory[],
allTtsCategories: TtsCategoryType[],
allTtsModels: TtsModelListItem[],
allTtsCategoriesByTokenMap: Map<string,TtsCategory>,
allTtsCategoriesByTokenMap: Map<string,TtsCategoryType>,
allTtsModelsByTokenMap: Map<string,TtsModelListItem>,
ttsModelsByCategoryToken: Map<string,Set<TtsModelListItem>>,
dropdownCategories: TtsCategory[][],
setDropdownCategories: (dropdownCategories: TtsCategory[][]) => void,
dropdownCategories: TtsCategoryType[][],
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void,
selectedCategories: TtsCategory[],
setSelectedCategories: (selectedCategories: TtsCategory[]) => void,
selectedCategories: TtsCategoryType[],
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void,
maybeSelectedTtsModel?: TtsModelListItem,
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void,
@@ -14,8 +14,9 @@ import { v4 as uuidv4 } from 'uuid';
import { ListTtsModels, TtsModelListItem } from '../../../api/tts/ListTtsModels';
import { GravatarFc } from '../../_common/GravatarFc';
import { VocodesNotice } from './VocodesNotice';
import { ListTtsCategories, ListTtsCategoriesIsError, ListTtsCategoriesIsOk, TtsCategory } from '../../../api/category/ListTtsCategories';
import { ListTtsCategories, ListTtsCategoriesIsError, ListTtsCategoriesIsOk } from '../../../api/category/ListTtsCategories';
import { MultiDropdownSearch } from './MultiDropdownSearch';
import { SyntheticCategory, TtsCategoryType } from '../../../../AppWrapper';
export interface EnqueueJobResponsePayload {
success: boolean,
@@ -40,17 +41,17 @@ interface Props {
ttsModels: Array<TtsModelListItem>,
setTtsModels: (ttsVoices: Array<TtsModelListItem>) => void,
allTtsCategories: TtsCategory[],
setAllTtsCategories: (allTtsCategories: TtsCategory[]) => void,
allTtsCategories: TtsCategoryType[],
setAllTtsCategories: (allTtsCategories: TtsCategoryType[]) => void,
allTtsCategoriesByTokenMap: Map<string,TtsCategory>,
allTtsCategoriesByTokenMap: Map<string,TtsCategoryType>,
allTtsModelsByTokenMap: Map<string,TtsModelListItem>,
ttsModelsByCategoryToken: Map<string,Set<TtsModelListItem>>,
dropdownCategories: TtsCategory[][],
setDropdownCategories: (dropdownCategories: TtsCategory[][]) => void,
selectedCategories: TtsCategory[],
setSelectedCategories: (selectedCategories: TtsCategory[]) => void,
dropdownCategories: TtsCategoryType[][],
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void,
selectedCategories: TtsCategoryType[],
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void,
maybeSelectedTtsModel?: TtsModelListItem,
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void,
@@ -76,6 +77,7 @@ function TtsModelListFc(props: Props) {
}
const models = await ListTtsModels();
if (models) {
dynamicallyCategorizeModels(models);
setTtsModels(models);
if (!maybeSelectedTtsModel && models.length > 0) {
const model = models[0];
@@ -90,7 +92,10 @@ function TtsModelListFc(props: Props) {
}
const categoryList = await ListTtsCategories();
if (ListTtsCategoriesIsOk(categoryList)) {
setAllTtsCategories(categoryList.categories);
const serverCategories : TtsCategoryType[] = categoryList.categories;
const dynamicCategories : TtsCategoryType[] = generateSyntheticCategories();
const allCategories = serverCategories.concat(dynamicCategories);
setAllTtsCategories(allCategories);
} else if (ListTtsCategoriesIsError(categoryList)) {
// TODO: Retry on decay function
}
@@ -264,4 +269,46 @@ function TtsModelListFc(props: Props) {
)
}
function generateSyntheticCategories() : SyntheticCategory[] {
return [
// Under-categorized
new SyntheticCategory('Under-categorized Models', 'syn:under'),
new SyntheticCategory('With 0 categories', 'syn:uncategorized', 'syn:under'),
new SyntheticCategory('With 1 category', 'syn:one-category', 'syn:under'),
// Most recent
new SyntheticCategory('Most Recent Voices', 'syn:most-recent'),
]
}
// Directly mutate the model records
function dynamicallyCategorizeModels(models: TtsModelListItem[]) {
// NB: Sorting by creation date will involve more refactoring, so this is fine for now.
const mostRecentModelTokens = new Set([...models].sort((modelA, modelB) => {
const dateA = new Date(modelA.created_at);
const dateB = new Date(modelB.created_at);
if (dateA > dateB) {
return -1;
} else if (dateA < dateB) {
return 1;
} else {
return 0;
}
}).map(model => model.model_token).slice(0, 25));
models.forEach(model => {
if (!model.category_tokens) {
model.category_tokens = [];
}
if (model.category_tokens.length === 1) {
model.category_tokens.push('syn:one-category');
} else if (model.category_tokens.length === 0) {
model.category_tokens.push('syn:uncategorized');
}
if (mostRecentModelTokens.has(model.model_token)) {
model.category_tokens.push('syn:most-recent');
}
})
}
export { TtsModelListFc };