server settings keyed (352 -> 267)
ServerSettings reaches zero: OCR, STT, TTS, SMTP, maintenance and the AI harnesses. Two more hand-rolled plurals became real plural entries -- maintenance said "commit" with a ternary on length === 1, and both read correctly at 0, 1 and 2 in both locales now. The three Cancel buttons in AIHarnessesSection take ui.button.cancel rather than adding a fourth app-level copy of the word, which is what phase 1 put it there for. Scanner: a single token carrying digits and punctuation is a name, not a sentence -- the OCR placeholder is the example model file Qwen2.5-VL-7B-Instruct-q4_k_m.gguf. Prose has spaces, and requiring whitespace to be absent is what keeps this from eating real one-word labels. Verified the delta is exactly that one string. Note for the owner: another session has src/servers/api/sub-agents/router.ts in flight with three type errors. Untouched by me and excluded from my gate; the full tree will not typecheck clean until they land it.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
|
||||
type Provider = 'openai' | 'elevenlabs';
|
||||
|
||||
@@ -33,6 +34,7 @@ const PROVIDER_DEFAULTS: Record<Provider, Partial<TtsConfig>> = {
|
||||
};
|
||||
|
||||
export const TTSSection = () => {
|
||||
const { t } = useTranslation();
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
@@ -110,9 +112,9 @@ export const TTSSection = () => {
|
||||
try {
|
||||
await client.put('/server-settings/tts', buildConfig());
|
||||
queryClient.invalidateQueries({ queryKey: ['TTS_CONFIG'] });
|
||||
toast.success('TTS settings saved');
|
||||
toast.success(t('settings.server.tts.saved'));
|
||||
} catch {
|
||||
toast.error('Failed to save TTS settings');
|
||||
toast.error(t('settings.server.tts.saveFailed'));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -143,27 +145,27 @@ export const TTSSection = () => {
|
||||
const audio = new Audio(audioUrl);
|
||||
audioRef.current = audio;
|
||||
audio.play();
|
||||
toast.success('Playing test audio');
|
||||
toast.success(t('settings.server.tts.playing'));
|
||||
} catch {
|
||||
toast.error('Test failed — could not reach TTS server');
|
||||
toast.error(t('settings.server.tts.testFailed'));
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) return <p className="text-sm text-duck-dark/40">Loading...</p>;
|
||||
if (isLoading) return <p className="text-sm text-duck-dark/40">{t('settings.server.loading')}</p>;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSave} className="grid gap-4">
|
||||
<Label className="grid gap-2">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">Provider</span>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.server.provider')}</span>
|
||||
<Select value={provider} onValueChange={(v) => handleProviderChange(v as Provider)}>
|
||||
<SelectTrigger className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark dark:text-foreground">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="z-[600]">
|
||||
<SelectItem value="openai">OpenAI-compatible (Kokoro, OpenAI, etc.)</SelectItem>
|
||||
<SelectItem value="elevenlabs">ElevenLabs</SelectItem>
|
||||
<SelectItem value="openai">{t('settings.server.tts.openaiCompatible')}</SelectItem>
|
||||
<SelectItem value="elevenlabs">{t('settings.server.tts.elevenLabs')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Label>
|
||||
@@ -195,7 +197,7 @@ export const TTSSection = () => {
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Label className="grid gap-2">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">Model</span>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.server.model')}</span>
|
||||
<Input
|
||||
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark dark:text-foreground placeholder:text-duck-dark/40"
|
||||
placeholder={provider === 'openai' ? 'kokoro' : 'eleven_multilingual_v2'}
|
||||
@@ -205,13 +207,13 @@ export const TTSSection = () => {
|
||||
</Label>
|
||||
<Label className="grid gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">Voice</span>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.server.voice')}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fetchVoices(provider, url, apiKey, model)}
|
||||
disabled={voicesLoading}
|
||||
className="p-0.5 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors disabled:opacity-40"
|
||||
title="Refresh voices"
|
||||
title={t('settings.server.tts.refreshVoices')}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`h-3 w-3 text-duck-dark/50 dark:text-foreground/50 ${voicesLoading ? 'animate-spin' : ''}`}
|
||||
@@ -221,7 +223,7 @@ export const TTSSection = () => {
|
||||
{voices.length > 0 ? (
|
||||
<Select value={voice} onValueChange={setVoice}>
|
||||
<SelectTrigger className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark dark:text-foreground">
|
||||
<SelectValue placeholder="Select a voice" />
|
||||
<SelectValue placeholder={t('settings.server.tts.selectVoice')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="z-[600] max-h-[300px]">
|
||||
{voiceGroups.length > 0
|
||||
|
||||
Reference in New Issue
Block a user