feat: add OpenRouter dictation and speech recognition settings

This commit is contained in:
2026-10-26 23:42:44 +03:00
parent 168cad7470
commit ecbd2b0bb9
24 changed files with 867 additions and 72 deletions
+2 -1
View File
@@ -703,7 +703,8 @@ export function AgentSection({
<button type="button" data-testid={`slot-fast-${slot}`} aria-label={t("{v0} fast mode", { v0: t(SLOT_META[slot].label) })}
aria-pressed={provider === "chatgpt" && override?.fastMode === true} disabled={provider !== "chatgpt" || !chosen || savingModels}
onClick={() => { setOpenMenu(null); void updateModels(composeSlotFast(models, slot, !override?.fastMode)); }}
className={cn("flex h-7 items-center gap-1 rounded-sm border border-border px-1.5 font-ui text-xs transition-colors disabled:cursor-not-allowed disabled:opacity-40",
className={cn("flex h-7 items-center gap-1 rounded-sm border border-border px-1.5 font-ui text-xs transition-colors disabled:opacity-40",
provider === "chatgpt" && chosen ? "disabled:cursor-default" : "disabled:cursor-not-allowed",
provider === "chatgpt" && override?.fastMode ? "bg-muted text-foreground" : "bg-background text-muted-foreground hover:border-muted-foreground/50")}>
<ZapIcon className="size-3.5" aria-hidden /><span>{t("Fast")}</span>
</button>
+11
View File
@@ -335,6 +335,7 @@ export function KeysSection({
/** The paste editor for a freshly chosen Add-provider target. */
const [adding, setAdding] = useState<ProviderKeyInfo | null>(null);
const [addError, setAddError] = useState<string | null>(null);
const followedProvider = useRef<string | null>(null);
// Escape folds any open editor (unfolded row or the Add-provider paste)
// WITHOUT closing the dialog. Claimed at document CAPTURE phase — the
@@ -372,7 +373,17 @@ export function KeysSection({
// named row; a fresh mount seeds it via the useState initializer.
useEffect(() => {
if (initialRow !== undefined) setOpenRow(initialRow);
followedProvider.current = null;
}, [initialRow]);
// A missing model-provider key has no table row yet. Deep-link into the
// existing Add-provider editor instead (for example, OpenRouter STT setup).
useEffect(() => {
if (!initialRow || !statuses || !providers || followedProvider.current === initialRow) return;
followedProvider.current = initialRow;
if (statuses.some((status) => status.id === initialRow)) return;
const provider = providers.find((provider) => provider.envVar === initialRow);
if (provider) { setAdding(provider); setAddError(null); setOpenRow(null); }
}, [initialRow, statuses, providers]);
async function saveKey(id: string, value: string): Promise<string | null> {
const res = await fetch(`/api/keys/${id}`, {
+11 -5
View File
@@ -39,7 +39,7 @@ import {
type GraphSettings,
type UserSettings,
} from "@/src/core";
import { LanguageSelect, SttPreferredLanguages, useI18n } from "@/components/i18n";
import { LanguageSelect, SttModelSelect, SttPreferredLanguages, useI18n } from "@/components/i18n";
import { useAppearance } from "./appearance.tsx";
import { SettingsSection, SettingsRow, Toggle } from "./controls.tsx";
import { AgentSection } from "./agent-section.tsx";
@@ -62,6 +62,7 @@ const SECTIONS = [
{ id: "appearance", label: "Appearance" },
{ id: "graph", label: "Graph" },
{ id: "keyboard", label: "Keyboard", hint: "Stored on this device" },
{ id: "stt", label: "Speech recognition" },
{ id: "general", label: "General" },
] as const;
type SectionId = (typeof SECTIONS)[number]["id"];
@@ -367,10 +368,6 @@ export function SettingsDialogBody({
testid="section-general"
>
<SettingsRow label={t("Interface language")} testid="row-language"><LanguageSelect field="language" /></SettingsRow>
<SettingsRow label={t("Speech recognition language")} testid="row-stt-language"><LanguageSelect field="sttLanguage" /></SettingsRow>
<SettingsRow label={t("Preferred languages")} hint={t("Used with Multilingual. Favors selected languages; other languages may still be recognized.")} testid="row-stt-preferred-languages" className="flex-col items-start gap-2">
<SttPreferredLanguages />
</SettingsRow>
<SettingsRow
label={t("Auto-update")}
hint={AUTO_UPDATE_HINT}
@@ -386,6 +383,15 @@ export function SettingsDialogBody({
</SettingsRow>
</SettingsSection>
)}
{active === "stt" && (
<SettingsSection title={t("Speech recognition")} testid="section-stt">
<SettingsRow label={t("Speech recognition model")} testid="row-stt-model" className="flex-wrap items-start gap-y-2 [&>span:first-child]:flex-[1_1_12rem]"><SttModelSelect /></SettingsRow>
<SettingsRow label={t("Speech recognition language")} testid="row-stt-language"><LanguageSelect field="sttLanguage" /></SettingsRow>
<SettingsRow label={t("Preferred languages")} testid="row-stt-preferred-languages">
<SttPreferredLanguages />
</SettingsRow>
</SettingsSection>
)}
{active === "keys" && (
<SettingsSection title={t("Providers")} testid="section-keys">
<KeysSection initialRow={initialRow ?? undefined} />