UI for editing custom vocoders in TTS models

This commit is contained in:
Brandon Thomas committed 2022-08-10 05:33:57 -04:00
1 parent 322bcd5504
commit 72d6c0c528
3 files changed
+146 -7

No files matched your search

+8
View File
@@ -284,6 +284,14 @@ class ApiConfig {
return base_url + query;
}
listVocoderModels(): string {
return `${this.getScheme()}://${this.getNewApiHost()}/vocoder/list`;
}
getVocoderModel(vocoderToken: string): string {
return `${this.getScheme()}://${this.getNewApiHost()}/vocoder/model/${vocoderToken}`;
}
createCategory(): string {
return `${this.getScheme()}://${this.getNewApiHost()}/category/create`;
}
@@ -0,0 +1,71 @@
import { ApiConfig } from "../ApiConfig";
export interface ListVocoderModelsSuccessResponse {
success: boolean,
vocoders: Array<VocoderModel>
}
export interface VocoderModel {
vocoder_token: string,
vocoder_type: string,
title: string,
is_staff_recommended: boolean,
creator_username: string,
creator_display_name: string,
creator_gravatar_hash: string,
created_at: Date,
updated_at: Date,
moderator_fields: VocoderModelModeratorFields | undefined | null,
}
export interface VocoderModelModeratorFields {
is_mod_disabled_from_public_use: boolean,
is_mod_disabled_from_author_use: boolean,
is_mod_author_editing_locked: boolean,
mod_deleted_at: string | undefined | null,
user_deleted_at: string | undefined | null,
}
export interface ListVocoderModelsErrorResponse {
success: boolean,
}
type ListVocoderModelsResponse = ListVocoderModelsSuccessResponse | ListVocoderModelsErrorResponse;
export function ListVocoderModelsIsOk(response: ListVocoderModelsResponse): response is ListVocoderModelsSuccessResponse {
return response?.success === true;
}
export function ListVocoderModelsIsError(response: ListVocoderModelsResponse): response is ListVocoderModelsErrorResponse {
return response?.success === false;
}
export async function ListVocoderModels() : Promise<ListVocoderModelsResponse>
{
const endpoint = new ApiConfig().listVocoderModels();
return await fetch(endpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false }; // TODO: This loses error semantics and is deprecated
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -13,6 +13,11 @@ import {
TtsModel,
TtsModelLookupError,
} from "@storyteller/components/src/api/tts/GetTtsModel";
import {
ListVocoderModels,
ListVocoderModelsIsOk,
VocoderModel,
} from "@storyteller/components/src/api/vocoder/ListVocoderModels";
import { BackLink } from "../../_common/BackLink";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
@@ -51,20 +56,28 @@ function TtsModelEditPage(props: Props) {
const [ttsModel, setTtsModel] = useState<TtsModel | undefined>(undefined);
const [notFoundState, setNotFoundState] = useState<boolean>(false);
// Vocoder lookup
const [vocoderModels, setVocoderModels] = useState<VocoderModel[]>([]);
// Fields
const [title, setTitle] = useState("");
const [textPipelineType, setTextPipelineType] = useState<string | null>(null);
const [descriptionMarkdown, setDescriptionMarkdown] = useState("");
const [fullLanguageTag, setFullLanguageTag] = useState(""); // NB: Should be full IETF, eg. ["en", "en-US", "es-419", etc.]
const [visibility, setVisibility] = useState(DEFAULT_VISIBILITY);
const [defaultPretrainedVocoder, setDefaultPretrainedVocoder] = useState(
DEFAULT_PRETRAINED_VOCODER
);
const [isFrontPageFeatured, setIsFrontPageFeatured] = useState(false);
const [isTwitchFeatured, setIsTwitchFeatured] = useState(false);
const [suggestedUniqueBotCommand, setSuggestedUniqueBotCommand] =
useState("");
// Vocoder config (modern)
const [maybeCustomVocoderToken, setMaybeCustomVocoderToken] = useState<string | null>(null);
// Vocoder config (legacy)
const [defaultPretrainedVocoder, setDefaultPretrainedVocoder] = useState(
DEFAULT_PRETRAINED_VOCODER
);
const getModel = useCallback(async (token) => {
const model = await GetTtsModel(token);
@@ -81,14 +94,17 @@ function TtsModelEditPage(props: Props) {
setDescriptionMarkdown(model.description_markdown || "");
setFullLanguageTag(model.ietf_language_tag || DEFAULT_MODEL_LANGUAGE);
setVisibility(model.creator_set_visibility || DEFAULT_VISIBILITY);
setDefaultPretrainedVocoder(
model.maybe_default_pretrained_vocoder || DEFAULT_PRETRAINED_VOCODER
);
setIsFrontPageFeatured(model.is_front_page_featured || false);
setIsTwitchFeatured(model.is_twitch_featured || false);
setSuggestedUniqueBotCommand(
model.maybe_suggested_unique_bot_command || ""
);
if (!!model.maybe_custom_vocoder) {
setMaybeCustomVocoderToken(model.maybe_custom_vocoder.vocoder_token);
}
setDefaultPretrainedVocoder(
model.maybe_default_pretrained_vocoder || DEFAULT_PRETRAINED_VOCODER
);
} else if (GetTtsModelIsErr(model)) {
switch (model) {
case TtsModelLookupError.NotFound:
@@ -98,9 +114,20 @@ function TtsModelEditPage(props: Props) {
}
}, []);
const listVocoderModels = useCallback(async () => {
const models = await ListVocoderModels();
if (ListVocoderModelsIsOk(models)) {
setVocoderModels(models.vocoders);
}
}, []);
useEffect(() => {
getModel(token);
}, [token, getModel]);
listVocoderModels();
}, [token, getModel, listVocoderModels]);
const handleTitleChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
@@ -138,6 +165,13 @@ function TtsModelEditPage(props: Props) {
setDefaultPretrainedVocoder((ev.target as HTMLSelectElement).value);
};
const handleCustomVocoderChange = (
ev: React.FormEvent<HTMLSelectElement>
) => {
setMaybeCustomVocoderToken((ev.target as HTMLSelectElement).value);
};
const handleIsFrontPageFeaturedChange = (
ev: React.FormEvent<HTMLSelectElement>
) => {
@@ -184,6 +218,7 @@ function TtsModelEditPage(props: Props) {
creator_set_visibility: visibility || DEFAULT_VISIBILITY,
maybe_default_pretrained_vocoder:
defaultPretrainedVocoder || DEFAULT_PRETRAINED_VOCODER,
maybe_custom_vocoder_token: maybeCustomVocoderToken,
ietf_language_tag: fullLanguageTag || DEFAULT_MODEL_LANGUAGE,
text_pipeline_type: textPipelineType,
};
@@ -312,6 +347,17 @@ function TtsModelEditPage(props: Props) {
);
}
let customVocoderOptions = vocoderModels.map((vocoder) => {
return (
<option
key={vocoder.vocoder_token}
value={vocoder.vocoder_token}
>
{vocoder.title} (by {vocoder.creator_display_name})
</option>
)
});
let usableTextPipelines = isModerator
? TEXT_PIPELINE_NAMES_FOR_MODERATORS
: TEXT_PIPELINE_NAMES;
@@ -437,6 +483,20 @@ function TtsModelEditPage(props: Props) {
</div>
</div>
<div>
<label className="sub-title">Custom tuned vocoder</label>
<div className="form-group">
<select
name="custom_vocoder"
onChange={handleCustomVocoderChange}
value={maybeCustomVocoderToken || ""}
className="form-select"
>
{customVocoderOptions}
</select>
</div>
</div>
<div>
<label className="sub-title">
Model Visibility&nbsp;{visibilityIcon}