Fix Heute bootstrap redirect and German onboarding voices
When Kokoro lists no locale-matched voices, onboarding now uses offline presets instead of showing English-only options. Shell bootstrap skips bot redirects while the user is on /app/heute. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
parent
628b008e2a
commit
6b6e34c1a6
@ -52,7 +52,7 @@ test("German voice step lists German voices after choosing Deutsch", async ({ pa
|
||||
const germanVoice = page.getByRole("button", { name: /^(Eva|Bernd)$/i });
|
||||
await expect(germanVoice.first()).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByRole("button", { name: "Alloy", exact: true })).toHaveCount(0);
|
||||
await expect(germanVoice.filter({ has: page.locator('[aria-pressed="true"]') }).first()).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /^(Eva|Bernd)$/i, pressed: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("main conversation does not show a group subtitle for one Oo", async ({ page }) => {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import type { VoiceInfo } from "@rakazo/contracts";
|
||||
import { voicesForLocale } from "@rakazo/core";
|
||||
import { Button } from "@rakazo/ui-web";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { defaultOnboardingVoiceId, offlineOnboardingVoices } from "../../lib/onboarding-voices";
|
||||
import {
|
||||
defaultOnboardingVoiceId,
|
||||
offlineOnboardingVoices,
|
||||
onboardingVoicesFromList,
|
||||
} from "../../lib/onboarding-voices";
|
||||
import { rpc } from "../../lib/rpc";
|
||||
import type { UiLocale } from "../../lib/ui-locale";
|
||||
|
||||
@ -39,7 +42,7 @@ export function OnboardingVoiceStep({
|
||||
await rpc.voice.connect({ provider: "kokoro", apiKey: KOKORO_LOCAL_TOKEN });
|
||||
const listed = await rpc.voice.voices({ provider: "kokoro" });
|
||||
if (cancelled) return;
|
||||
const ordered = voicesForLocale(listed, uiLocale).slice(0, 6);
|
||||
const ordered = onboardingVoicesFromList(listed, uiLocale);
|
||||
setVoiceOptions(ordered);
|
||||
const nextId = defaultOnboardingVoiceId(uiLocale, ordered);
|
||||
if (nextId) onSelectedVoiceIdChange(nextId);
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { defaultOnboardingVoiceId, offlineOnboardingVoices } from "./onboarding-voices.js";
|
||||
import {
|
||||
defaultOnboardingVoiceId,
|
||||
offlineOnboardingVoices,
|
||||
onboardingVoicesFromList,
|
||||
} from "./onboarding-voices.js";
|
||||
|
||||
describe("offlineOnboardingVoices", () => {
|
||||
it("offers German voices for de", () => {
|
||||
@ -18,6 +22,18 @@ describe("offlineOnboardingVoices", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("onboardingVoicesFromList", () => {
|
||||
it("uses offline German presets when the server only lists English voices", () => {
|
||||
const englishOnly = [{ id: "af_alloy", label: "Alloy", description: "neutral" }];
|
||||
expect(onboardingVoicesFromList(englishOnly, "de").map((voice) => voice.id)).toEqual([
|
||||
"df_eva",
|
||||
"dm_bernd",
|
||||
"df_victoria",
|
||||
"dm_martin",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultOnboardingVoiceId", () => {
|
||||
it("prefers df_eva for German", () => {
|
||||
expect(defaultOnboardingVoiceId("de", offlineOnboardingVoices("de"))).toBe("df_eva");
|
||||
|
||||
@ -13,11 +13,41 @@ const ENGLISH_ONBOARDING_VOICES: VoiceInfo[] = [
|
||||
|
||||
const OFFLINE_CATALOG = [...GERMAN_VOICE_PRESETS, ...ENGLISH_ONBOARDING_VOICES];
|
||||
|
||||
const KOKORO_LANGUAGE_PREFIXES: Record<string, string> = {
|
||||
de: "d",
|
||||
en: "ab",
|
||||
es: "e",
|
||||
fr: "f",
|
||||
hi: "h",
|
||||
it: "i",
|
||||
ja: "j",
|
||||
pt: "p",
|
||||
zh: "z",
|
||||
};
|
||||
|
||||
function listedVoicesSpeakLocale(listed: VoiceInfo[], locale: string): boolean {
|
||||
const language = locale.toLowerCase().split("-")[0] ?? "";
|
||||
const prefixes = KOKORO_LANGUAGE_PREFIXES[language];
|
||||
if (!prefixes) return listed.length > 0;
|
||||
return listed.some((voice) => {
|
||||
const prefix = /^([a-z])[fm]_/.exec(voice.id)?.[1];
|
||||
return Boolean(prefix && prefixes.includes(prefix));
|
||||
});
|
||||
}
|
||||
|
||||
/** Locale-aware voice choices for onboarding when listing from the server fails. */
|
||||
export function offlineOnboardingVoices(locale: string): VoiceInfo[] {
|
||||
return voicesForLocale(OFFLINE_CATALOG, locale).slice(0, 6);
|
||||
}
|
||||
|
||||
/** Prefer server voices that speak the UI language; otherwise use offline presets. */
|
||||
export function onboardingVoicesFromList(listed: VoiceInfo[], locale: string): VoiceInfo[] {
|
||||
if (listedVoicesSpeakLocale(listed, locale)) {
|
||||
return voicesForLocale(listed, locale).slice(0, 6);
|
||||
}
|
||||
return offlineOnboardingVoices(locale);
|
||||
}
|
||||
|
||||
export function defaultOnboardingVoiceId(locale: string, voices: VoiceInfo[]): string {
|
||||
return defaultVoiceForLocale(voices, locale)?.id ?? "";
|
||||
}
|
||||
|
||||
@ -1004,9 +1004,11 @@ export function ShellPage() {
|
||||
return;
|
||||
}
|
||||
if (inProject && !botId) {
|
||||
if (routeOnHeute.current) return;
|
||||
navigate(firstThreadRoute(bootstrap.bots, groupList, true), { replace: true });
|
||||
return;
|
||||
}
|
||||
if (routeOnHeute.current) return;
|
||||
const selectedBotId = bootstrap.thread?.botId ?? bootstrap.bots[0]?.id;
|
||||
if (selectedBotId && selectedBotId !== botId) {
|
||||
navigate(`/app/${selectedBotId}`, { replace: true });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user