mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
synthetic categories
This commit is contained in:
1 parent
c477622d59
commit
6ebfaef5aa
6 files changed
+119
-49
No files matched your search
+8
-8
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
Reference in new issue
Block a user