Release NVIDIA WakeWord Trainer v27

This commit is contained in:
MasterPhooey
2026-08-10 07:06:44 -05:00
parent 68c4227cb7
commit 13f229e451
14 changed files with 572 additions and 262 deletions

View File

@@ -70,7 +70,7 @@ const dataCategories = computed(() => {
return Array.from(groups, ([name, items]) => ({ name, items }));
});
watch(() => trainer.language, ensureSupportedTtsMode);
watch([() => trainer.language, () => trainer.ttsMode], ensureSupportedTtsMode);
watch(() => trainer.toast.serial, () => window.setTimeout(() => { trainer.toast.message = ""; }, 4500));
watch(consoleLines, async () => {
if (!consoleFollowing.value) return;
@@ -197,6 +197,7 @@ function consoleTone(line: string): string {
<div class="form-grid phrase-form">
<label class="field wide"><span>Wake phrase</span><input v-model="trainer.phrase" type="text" placeholder='e.g. "hey tater"' :disabled="Boolean(trainer.session.safe_word) || isBusy('session')" @keyup.enter="startSession" /></label>
<label class="field"><span>Language</span><select v-model="trainer.language" :disabled="Boolean(trainer.session.safe_word) || isBusy('session')"><option v-for="item in trainer.languages" :key="item.code" :value="item.code">{{ item.label }}</option></select><small>{{ ttsRoute }}</small></label>
<label v-if="trainer.language === 'en' && trainer.ttsMode !== 'piper'" class="field"><span>English accent emphasis</span><select v-model="trainer.englishAccent" :disabled="Boolean(trainer.session.safe_word) || isBusy('session')"><option v-for="accent in trainer.englishAccents" :key="accent.code" :value="accent.code">{{ accent.label }}</option></select><small>Qwen emphasizes this accent; MOSS carries it through accepted references. OmniVoice and Piper keep broad English coverage.</small></label>
<label class="field"><span>TTS source</span><select v-model="trainer.ttsMode" :disabled="Boolean(trainer.session.safe_word) || isBusy('session')">
<option value="hybrid" :disabled="!trainer.languages.find((item) => item.code === trainer.language)?.engines?.includes('piper')">Four-provider ensemble · recommended</option>
<option value="modern" :disabled="!trainer.languages.find((item) => item.code === trainer.language)?.engines?.some((engine) => engine !== 'piper')">Modern only · no Piper</option>
@@ -224,6 +225,7 @@ function consoleTone(line: string): string {
<div class="form-grid">
<label class="field"><span>Wake phrase</span><input v-model="trainer.autoForm.wake_phrase" type="text" /></label>
<label class="field"><span>STT language</span><input v-model="trainer.autoForm.language" type="text" /></label>
<label v-if="String(trainer.autoForm.language).toLowerCase().startsWith('en')" class="field"><span>English accent emphasis</span><select v-model="trainer.autoForm.english_accent"><option v-for="accent in trainer.englishAccents" :key="accent.code" :value="accent.code">{{ accent.label }}</option></select><small>Used when Auto Training needs to regenerate English TTS.</small></label>
<label class="field wide"><span>STT engine</span><select v-model="trainer.autoForm.stt_engine"><option v-for="engine in sttEngines" :key="engine.id || engine.value" :value="engine.id || engine.value">{{ engine.label || engine.name || engine.id }}</option></select><small>{{ sttEngines.find((row) => (row.id || row.value) === trainer.autoForm.stt_engine)?.description || "Runs locally on this trainer." }}</small></label>
<label class="field"><span>Minimum transcript characters</span><input v-model.number="trainer.autoForm.minimum_transcript_chars" min="1" max="100" type="number" /></label>
</div>

View File

@@ -1,6 +1,7 @@
import { computed, reactive } from "vue";
import { getJson, postJson, putJson, request, type JsonRecord } from "./api";
import type {
AccentOption,
AudioItem,
AutoTrainForm,
AutoTrainPayload,
@@ -26,6 +27,7 @@ const defaultAutoForm = (): AutoTrainForm => ({
enabled: false,
wake_phrase: "",
language: "en",
english_accent: "mixed",
stt_engine: "faster_whisper",
minimum_transcript_chars: 2,
delete_confirmed_wakes: false,
@@ -43,8 +45,21 @@ export const trainer = reactive({
busy: new Set<string>(),
phrase: "",
language: "en",
englishAccent: "mixed",
ttsMode: "hybrid",
languages: [{ code: "en", label: "English (en)", engines: ["omnivoice"] }] as LanguageOption[],
englishAccents: [
{ code: "mixed", label: "Mixed English" },
{ code: "australian", label: "Australian" },
{ code: "american", label: "American" },
{ code: "british", label: "British" },
{ code: "canadian", label: "Canadian" },
{ code: "irish", label: "Irish" },
{ code: "scottish", label: "Scottish" },
{ code: "new_zealand", label: "New Zealand" },
{ code: "indian", label: "Indian" },
{ code: "south_african", label: "South African" },
] as AccentOption[],
session: {} as SessionPayload,
samples: emptySamples(),
captured: emptyCaptured(),
@@ -123,8 +138,12 @@ function applySession(payload: SessionPayload): void {
if (Array.isArray(payload.available_languages) && payload.available_languages.length) {
trainer.languages = payload.available_languages;
}
if (Array.isArray(payload.available_english_accents) && payload.available_english_accents.length) {
trainer.englishAccents = payload.available_english_accents;
}
if (payload.raw_phrase) trainer.phrase = payload.raw_phrase;
if (payload.language) trainer.language = payload.language;
if (payload.english_accent) trainer.englishAccent = payload.english_accent;
if (payload.tts_mode) trainer.ttsMode = payload.tts_mode;
if (payload.training) trainer.training = payload.training;
}
@@ -145,6 +164,7 @@ export async function startSession(): Promise<void> {
const payload = await postJson<SessionPayload>("/api/start_session", {
phrase: trainer.phrase.trim(),
language: trainer.language,
english_accent: trainer.englishAccent,
tts_mode: trainer.ttsMode,
});
applySession(payload);
@@ -193,6 +213,7 @@ export function ensureSupportedTtsMode(): void {
if (trainer.ttsMode === "modern" && !modern) trainer.ttsMode = "piper";
if (trainer.ttsMode === "hybrid" && !(modern && piper)) trainer.ttsMode = modern ? "modern" : "piper";
if (trainer.ttsMode === "piper" && !piper) trainer.ttsMode = "modern";
if (trainer.language !== "en" || trainer.ttsMode === "piper") trainer.englishAccent = "mixed";
}
export async function refreshSamples(quiet = false): Promise<SamplesPayload> {
@@ -340,6 +361,8 @@ function applyAuto(payload: AutoTrainPayload, populate: boolean): void {
trainer.autoForm = { ...defaultAutoForm(), ...(payload.config || {}) };
if (!trainer.autoForm.wake_phrase) trainer.autoForm.wake_phrase = trainer.session.raw_phrase || "";
if (!trainer.autoForm.language) trainer.autoForm.language = trainer.session.language || "en";
if (!trainer.autoForm.english_accent) trainer.autoForm.english_accent = trainer.session.english_accent || "mixed";
if (!String(trainer.autoForm.language).toLowerCase().startsWith("en")) trainer.autoForm.english_accent = "mixed";
}
export async function refreshAuto(populate = false): Promise<AutoTrainPayload> {

View File

@@ -10,6 +10,11 @@ export interface LanguageOption extends JsonRecord {
quality?: string;
}
export interface AccentOption extends JsonRecord {
code: string;
label: string;
}
export interface TrainingState extends JsonRecord {
running: boolean;
exit_code: number | null;
@@ -20,9 +25,11 @@ export interface SessionPayload extends JsonRecord {
safe_word?: string;
raw_phrase?: string;
language?: string;
english_accent?: string;
tts_mode?: string;
takes_received?: number;
available_languages?: LanguageOption[];
available_english_accents?: AccentOption[];
training?: TrainingState;
}
@@ -56,6 +63,7 @@ export interface AutoTrainForm extends JsonRecord {
enabled: boolean;
wake_phrase: string;
language: string;
english_accent: string;
stt_engine: string;
minimum_transcript_chars: number;
delete_confirmed_wakes: boolean;