Merge PR114 dictation foundation for approved provider-row finishing
Preserve original contributor commit 3bb28e5f29. Resolve only the settings-dialog conflict by retaining current General/update behavior: the donor General-only language placement is explicitly superseded by the human-approved row BELOW the full-width Deepgram key. The assigned implementation worker will implement that placement and approved shared dropdown finishing. Foundation only, not a checked candidate.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/** Nova-3 monolingual language codes, including documented locale aliases.
|
||||
* Source: https://developers.deepgram.com/docs/models-languages-overview
|
||||
* Multilingual is a separate mode; its supported language set differs.
|
||||
*/
|
||||
const NOVA_3_LANGUAGES = [
|
||||
["Afrikaans", ["af", "af-ZA"]],
|
||||
["Arabic", ["ar", "ar-AE", "ar-SA", "ar-QA", "ar-KW", "ar-SY", "ar-LB", "ar-PS", "ar-JO", "ar-EG", "ar-SD", "ar-TD", "ar-MA", "ar-DZ", "ar-TN", "ar-IQ", "ar-IR"]],
|
||||
["Armenian", ["hy"]],
|
||||
["Assamese", ["as", "as-IN"]],
|
||||
["Belarusian", ["be"]],
|
||||
["Bengali", ["bn"]],
|
||||
["Bosnian", ["bs"]],
|
||||
["Bulgarian", ["bg"]],
|
||||
["Catalan", ["ca"]],
|
||||
["Chinese (Cantonese, Traditional)", ["zh-HK"]],
|
||||
["Chinese (Mandarin, Simplified)", ["zh", "zh-CN", "zh-Hans"]],
|
||||
["Chinese (Mandarin, Traditional)", ["zh-TW", "zh-Hant"]],
|
||||
["Croatian", ["hr"]],
|
||||
["Czech", ["cs", "cs-CZ"]],
|
||||
["Danish", ["da", "da-DK"]],
|
||||
["Dutch", ["nl"]],
|
||||
["English", ["en", "en-US", "en-AU", "en-GB", "en-IN", "en-NZ"]],
|
||||
["Estonian", ["et"]],
|
||||
["Finnish", ["fi"]],
|
||||
["Flemish", ["nl-BE"]],
|
||||
["French", ["fr", "fr-CA"]],
|
||||
["Georgian", ["ka", "ka-GE"]],
|
||||
["German", ["de"]],
|
||||
["German (Switzerland)", ["de-CH"]],
|
||||
["Greek", ["el"]],
|
||||
["Gujarati", ["gu", "gu-IN"]],
|
||||
["Hebrew", ["he"]],
|
||||
["Hindi", ["hi"]],
|
||||
["Hungarian", ["hu"]],
|
||||
["Indonesian", ["id"]],
|
||||
["Italian", ["it"]],
|
||||
["Japanese", ["ja"]],
|
||||
["Kannada", ["kn"]],
|
||||
["Kazakh", ["kk", "kk-KZ"]],
|
||||
["Korean", ["ko", "ko-KR"]],
|
||||
["Latvian", ["lv"]],
|
||||
["Lithuanian", ["lt"]],
|
||||
["Macedonian", ["mk"]],
|
||||
["Malay", ["ms"]],
|
||||
["Marathi", ["mr"]],
|
||||
["Mongolian", ["mn"]],
|
||||
["Nepali", ["ne"]],
|
||||
["Norwegian", ["no"]],
|
||||
["Pashto", ["ps", "ps-AF"]],
|
||||
["Persian", ["fa"]],
|
||||
["Polish", ["pl"]],
|
||||
["Portuguese", ["pt", "pt-BR", "pt-PT"]],
|
||||
["Punjabi", ["pa", "pa-IN"]],
|
||||
["Romanian", ["ro"]],
|
||||
["Russian", ["ru"]],
|
||||
["Serbian", ["sr"]],
|
||||
["Slovak", ["sk"]],
|
||||
["Slovenian", ["sl"]],
|
||||
["Spanish", ["es", "es-419"]],
|
||||
["Swedish", ["sv", "sv-SE"]],
|
||||
["Tagalog", ["tl"]],
|
||||
["Tamil", ["ta"]],
|
||||
["Telugu", ["te"]],
|
||||
["Thai", ["th", "th-TH"]],
|
||||
["Turkish", ["tr", "tr-TR"]],
|
||||
["Ukrainian", ["uk"]],
|
||||
["Urdu", ["ur"]],
|
||||
["Vietnamese", ["vi"]],
|
||||
] as const;
|
||||
|
||||
export type DictationLanguage = "multi" | (typeof NOVA_3_LANGUAGES)[number][1][number];
|
||||
|
||||
export const DICTATION_LANGUAGES: readonly { code: DictationLanguage; label: string }[] = [
|
||||
{ code: "multi", label: "Multilingual" },
|
||||
...NOVA_3_LANGUAGES.flatMap(([name, codes]) =>
|
||||
codes.map((code, i) => ({ code, label: i === 0 ? name : `${name} (${code})` })),
|
||||
).sort((a, b) => a.label.localeCompare(b.label, "en")),
|
||||
];
|
||||
|
||||
export function isDictationLanguage(value: unknown): value is DictationLanguage {
|
||||
return DICTATION_LANGUAGES.some(({ code }) => code === value);
|
||||
}
|
||||
|
||||
/** Only language varies. Keep the owner's tuned streaming parameters. */
|
||||
export function deepgramListenUrl(language: DictationLanguage): string {
|
||||
return "wss://api.deepgram.com/v1/listen" +
|
||||
"?model=nova-3&encoding=linear16&sample_rate=16000&channels=1" +
|
||||
"&smart_format=true&punctuate=true&endpointing=300" +
|
||||
`&language=${encodeURIComponent(language)}`;
|
||||
}
|
||||
@@ -59,6 +59,7 @@ export * from "./fuzzy.ts";
|
||||
// the user-settings record's shape/sanitizer (pure) and its fully-resolved
|
||||
// view. Browser-safe — the Wave-4 panel imports these directly.
|
||||
export * from "./settings.ts";
|
||||
export * from "./dictation.ts";
|
||||
// graph-deletion.ts (wave 6): the whole module is PURE but storage-port
|
||||
// driven — the deletion RUNS server-side only. The shell needs exactly one
|
||||
// two type-only things from it: the ChatDeleteMode union (the delete
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
type BackgroundModelRole,
|
||||
type FactorySettings,
|
||||
type GeneralSettings,
|
||||
type DictationSettings,
|
||||
type CompactionSettings,
|
||||
type GraphSettings,
|
||||
type ModelSlot,
|
||||
@@ -69,6 +70,7 @@ export type EffectiveAppearance = FactorySettings["appearance"];
|
||||
/** The concrete view: every knob resolved, no absent fields. */
|
||||
export interface EffectiveSettings {
|
||||
general: Required<GeneralSettings>;
|
||||
dictation: Required<DictationSettings>;
|
||||
agent: {
|
||||
models: Record<BackgroundModelRole, EffectiveModelSpec | undefined>;
|
||||
/** Delta map as stored — consumers resolve absent key → enabled. */
|
||||
@@ -176,6 +178,9 @@ export function resolveSettings(record: UserSettings | null): EffectiveSettings
|
||||
general: {
|
||||
autoUpdate: record?.general?.autoUpdate ?? FACTORY_SETTINGS.general.autoUpdate,
|
||||
},
|
||||
dictation: {
|
||||
language: record?.dictation?.language ?? FACTORY_SETTINGS.dictation.language,
|
||||
},
|
||||
agent: {
|
||||
models,
|
||||
tools: agent.tools ?? {},
|
||||
|
||||
@@ -32,6 +32,7 @@ import { AUTH_PROVIDER_KEYS } from "./auth-keys.ts";
|
||||
import type { ModelRole } from "../../config.ts";
|
||||
import type { ChatSettings } from "./contracts/chat.ts";
|
||||
import type { SearchEngineId } from "./search.ts";
|
||||
import { isDictationLanguage, type DictationLanguage } from "./dictation.ts";
|
||||
|
||||
/** The schema version this code understands. Bump ONLY for breaking shape
|
||||
* changes (a field renamed/meaning changed); pure additions never bump. */
|
||||
@@ -235,6 +236,11 @@ export interface GeneralSettings {
|
||||
autoUpdate?: boolean;
|
||||
}
|
||||
|
||||
/** One app-wide dictation preference, shared by every composer. */
|
||||
export interface DictationSettings {
|
||||
language?: DictationLanguage;
|
||||
}
|
||||
|
||||
/** Agent settings — everything about the non-conversation machinery. */
|
||||
export interface AgentSettings {
|
||||
/** Background-role model/thinking overrides (absent role = config pin). */
|
||||
@@ -361,6 +367,7 @@ export interface CompactionSettings {
|
||||
export interface UserSettings {
|
||||
version: number;
|
||||
general?: GeneralSettings;
|
||||
dictation?: DictationSettings;
|
||||
agent?: AgentSettings;
|
||||
/** The strong/cheap/vision model slots (model slots wave, 2026-09-20). */
|
||||
models?: ModelsSettings;
|
||||
@@ -378,6 +385,7 @@ export interface UserSettings {
|
||||
export interface FactorySettings {
|
||||
version: number;
|
||||
general: Required<GeneralSettings>;
|
||||
dictation: Required<DictationSettings>;
|
||||
appearance: {
|
||||
mode: "dark" | "light";
|
||||
background: string | null;
|
||||
@@ -397,6 +405,7 @@ export interface FactorySettings {
|
||||
export const FACTORY_SETTINGS: FactorySettings = {
|
||||
version: CURRENT_SETTINGS_VERSION,
|
||||
general: { autoUpdate: true },
|
||||
dictation: { language: "en" },
|
||||
appearance: {
|
||||
mode: "dark",
|
||||
background: null, // the dark default (#141414) lives in the mode's derivation curve
|
||||
@@ -615,6 +624,13 @@ export function sanitizeUserSettings(raw: unknown): UserSettings | null {
|
||||
if (r.general !== null && typeof r.general === "object" && !Array.isArray(r.general)) {
|
||||
out.general = sanitizeGeneral(r.general as Record<string, unknown>);
|
||||
}
|
||||
if (r.dictation !== undefined) {
|
||||
const raw = r.dictation;
|
||||
out.dictation = raw !== null && typeof raw === "object" && !Array.isArray(raw) &&
|
||||
isDictationLanguage((raw as Record<string, unknown>).language)
|
||||
? { language: (raw as DictationSettings).language }
|
||||
: {};
|
||||
}
|
||||
if (r.agent !== null && typeof r.agent === "object" && !Array.isArray(r.agent)) {
|
||||
out.agent = sanitizeAgent(r.agent as Record<string, unknown>);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user