feat: add multilingual dictation and simplify chat STT picker

This commit is contained in:
2026-10-26 00:58:25 +03:00
parent 1c6ae6b2e2
commit bdd7d5e866
10 changed files with 140 additions and 40 deletions
+25 -6
View File
@@ -7,7 +7,7 @@ import { createTeacherAgent } from "../src/agent/teacher-agent.ts";
import { createPracticeAgent } from "../src/agent/teacher-agent.ts";
import { sanitizeUserSettings } from "../src/core/settings.ts";
import { parseGeneralSettingsBody } from "../src/server/settings-body.ts";
import { countLabel, dictationUrl, resolveLanguage, systemLanguage, translate } from "../src/core/i18n.ts";
import { countLabel, dictationUrl, resolveLanguage, resolveSttLanguage, systemLanguage, translate } from "../src/core/i18n.ts";
import { sessionMarkdown } from "../src/core/session-markdown.ts";
import type { LhMessage } from "../src/core/native-messages.ts";
import { I18nProvider, LanguageSelect } from "../components/i18n.tsx";
@@ -29,6 +29,15 @@ describe("language preferences", () => {
assert.equal(resolveLanguage("en", "ru-RU"), "en");
assert.equal(resolveLanguage("ru", "en-US"), "ru");
});
it("resolves multilingual only for STT and keeps system defaults independent", () => {
assert.equal(resolveSttLanguage("multi", "ru-RU"), "multi");
assert.equal(resolveSttLanguage("multi", "en-US"), "multi");
assert.equal(resolveLanguage("multi", "ru-RU"), "ru");
assert.equal(resolveSttLanguage(undefined, "ru-RU"), "ru");
assert.equal(resolveSttLanguage(undefined, "de-DE"), "en");
assert.equal(resolveSttLanguage("en", "ru-RU"), "en");
assert.equal(resolveSttLanguage("ru", "en-US"), "ru");
});
it("validates both independent preferences without changing existing general options", () => {
const general = { autoUpdate: false, language: "ru", sttLanguage: "en" };
assert.deepEqual(parseGeneralSettingsBody(general), { ok: true, settings: general });
@@ -38,15 +47,25 @@ describe("language preferences", () => {
assert.equal(parseGeneralSettingsBody({ sttLanguage: "fr" }).ok, false);
assert.deepEqual(sanitizeUserSettings({ version: 1, general: { language: null, sttLanguage: "fr", autoUpdate: false } })?.general, { autoUpdate: false });
});
it("accepts a multilingual STT preference without allowing a multilingual interface", () => {
const general = { autoUpdate: false, language: "ru", sttLanguage: "multi" };
assert.deepEqual(parseGeneralSettingsBody(general), { ok: true, settings: general });
assert.deepEqual(sanitizeUserSettings({ version: 1, general })?.general, general);
assert.equal(parseGeneralSettingsBody({ language: "multi" }).ok, false);
assert.deepEqual(sanitizeUserSettings({ version: 1, general: { language: "multi", sttLanguage: "multi" } })?.general, { sttLanguage: "multi" });
const invalid = parseGeneralSettingsBody({ sttLanguage: "auto" });
assert(!invalid.ok);
assert.equal(translate("ru", invalid.error), "Параметр sttLanguage должен иметь значение «en», «ru» или «multi»");
});
it("pins an explicit supported language in the STT provider request", () => {
for (const language of ["en", "ru"] as const) {
for (const language of ["en", "ru", "multi"] as const) {
const url = new URL(dictationUrl(language));
assert.equal(url.protocol, "wss:");
assert.equal(url.hostname, "api.deepgram.com");
assert.equal(url.searchParams.get("language"), language);
assert.equal(url.searchParams.get("model"), "nova-3");
assert.equal(url.searchParams.get("encoding"), "linear16");
assert.equal(url.searchParams.get("sample_rate"), "16000");
assert.deepEqual(Object.fromEntries(url.searchParams), {
model: "nova-3", language, encoding: "linear16", sample_rate: "16000",
channels: "1", smart_format: "true", punctuate: "true", endpointing: "300",
});
}
});
});