stt leaves the platform for the transcribe plugin
Speech-to-text is now plugins/transcribe — sidecar, its own config, its own skill and a task for the file browser's context menu. The transcription logic moved BYTE FOR BYTE: 416 lines whose every decision has a reason attached, from the two-pass detect-then-transcribe to the 25 language priming prompts to timeout:false. Verified identical rather than assumed. Removed here: api/stt/, server-settings/stt.ts and its mount, POST /chat/stt, POST /file-browser/transcribe, the FileViewer handler behind the button hidden last release, the Settings screen, the dead useAudioRecording hook and the orphaned locale keys across all six locales. blobToWav stayed. It lived in the same file as the dead hook and is still used by DictateDialog — unrelated to STT, and deleting by file rather than by use is how that would have gone. No permission entry to remove: STT never had one, living under server-admin, chat and files. The registry count is unchanged and docs:check is green. The plugin ships a task that will not appear in the menu yet — the platform loads a plugin's skills, affordances and notifications from its own directory but not its tasks. That loader is the remaining piece of docs/plugin-capabilities.md and is deliberately not built here. 1426 pass, tsgo clean, 18 plugins discovered with none broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
LayoutGrid,
|
||||
Lock,
|
||||
Mail,
|
||||
Mic,
|
||||
ScanText,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
@@ -156,7 +155,6 @@ const SETTINGS_ITEMS: SettingsItem[] = [
|
||||
},
|
||||
// System — SystemSettings.tsx
|
||||
{ id: 'set:tts', labelKey: 'settings.sections.tts.label', to: '/settings/system/tts', icon: Volume2 },
|
||||
{ id: 'set:stt', labelKey: 'settings.sections.stt.label', to: '/settings/system/stt', icon: Mic },
|
||||
{ id: 'set:ocr', labelKey: 'settings.sections.ocr.label', to: '/settings/system/ocr', icon: ScanText },
|
||||
{
|
||||
id: 'set:maintenance',
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import type { FormEvent } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
|
||||
type SttConfig = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const STTSection = () => {
|
||||
const { t } = useTranslation();
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: config, isLoading } = useQuery({
|
||||
queryKey: ['STT_CONFIG'],
|
||||
queryFn: () => client.get<SttConfig | null>('/server-settings/stt'),
|
||||
});
|
||||
|
||||
const [url, setUrl] = useState('http://localhost:64201');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!config) return;
|
||||
setUrl(config.url);
|
||||
}, [config]);
|
||||
|
||||
const handleSave = async (ev?: FormEvent) => {
|
||||
ev?.preventDefault();
|
||||
if (isSaving) return;
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await client.put('/server-settings/stt', { url });
|
||||
queryClient.invalidateQueries({ queryKey: ['STT_CONFIG'] });
|
||||
toast.success(t('settings.server.stt.saved'));
|
||||
} catch {
|
||||
toast.error(t('settings.server.stt.saveFailed'));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTest = async () => {
|
||||
if (isTesting) return;
|
||||
setIsTesting(true);
|
||||
try {
|
||||
const res = await client.post<{ success?: boolean; error?: string }>('/server-settings/stt/test', { url });
|
||||
if (res.error) {
|
||||
toast.error(res.error);
|
||||
} else {
|
||||
toast.success(t('settings.server.stt.reachable'));
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const raw = (err as { message?: string })?.message;
|
||||
let msg = 'Connection failed';
|
||||
try {
|
||||
if (raw) msg = JSON.parse(raw).error ?? msg;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
toast.error(msg);
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
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">{t('settings.server.stt.serverUrl')}</span>
|
||||
<Input
|
||||
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark dark:text-foreground placeholder:text-duck-dark/40"
|
||||
placeholder="http://localhost:64201"
|
||||
value={url}
|
||||
onChange={(ev) => setUrl(ev.target.value)}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="flex-1 h-11 bg-duck-yellow hover:bg-duck-yellow/90 text-duck-teal font-bold transition-all duration-200 hover:scale-105 cursor-pointer disabled:opacity-50 disabled:hover:scale-100"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleTest}
|
||||
disabled={isTesting}
|
||||
className="h-11 cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{isTesting ? 'Testing...' : 'Test'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Bot, Hammer, Mail, Mic, ScanText, Settings, Volume2 } from 'lucide-react';
|
||||
import { Bot, Hammer, Mail, ScanText, Settings, Volume2 } from 'lucide-react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceLayout } from 'officerdev';
|
||||
|
||||
@@ -7,7 +7,6 @@ import { SettingsRoute, createSettingsPanelComponents, type SettingsSectionGroup
|
||||
import { SMTPSection } from './ServerSettings/SMTPSection';
|
||||
import { MaintenanceSection } from './ServerSettings/MaintenanceSection';
|
||||
import { TTSSection } from './ServerSettings/TTSSection';
|
||||
import { STTSection } from './ServerSettings/STTSection';
|
||||
import { OCRSection } from './ServerSettings/OCRSection';
|
||||
|
||||
const groups: SettingsSectionGroup[] = [
|
||||
@@ -22,13 +21,6 @@ const groups: SettingsSectionGroup[] = [
|
||||
descriptionKey: 'settings.sections.tts.description',
|
||||
content: <TTSSection />,
|
||||
},
|
||||
{
|
||||
key: 'stt',
|
||||
icon: Mic,
|
||||
titleKey: 'settings.sections.stt.label',
|
||||
descriptionKey: 'settings.sections.stt.description',
|
||||
content: <STTSection />,
|
||||
},
|
||||
{
|
||||
key: 'ocr',
|
||||
icon: ScanText,
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
"label": "Text-zu-Sprache",
|
||||
"description": "TTS-Anbieter und Stimme"
|
||||
},
|
||||
"stt": {
|
||||
"label": "Sprache-zu-Text",
|
||||
"description": "Whisper-Server-URL"
|
||||
},
|
||||
"ocr": {
|
||||
"label": "OCR",
|
||||
"description": "Vision-Modell für Textextraktion"
|
||||
@@ -234,12 +230,6 @@
|
||||
"saveFailed": "OCR-Einstellungen konnten nicht gespeichert werden",
|
||||
"reachable": "Vision-Modell-Server ist erreichbar"
|
||||
},
|
||||
"stt": {
|
||||
"serverUrl": "Whisper-Server-URL",
|
||||
"saved": "STT-Einstellungen gespeichert",
|
||||
"saveFailed": "STT-Einstellungen konnten nicht gespeichert werden",
|
||||
"reachable": "Whisper-Server ist erreichbar"
|
||||
},
|
||||
"tts": {
|
||||
"openaiCompatible": "OpenAI-kompatibel (Kokoro, OpenAI usw.)",
|
||||
"elevenLabs": "ElevenLabs",
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
"label": "Text to Speech",
|
||||
"description": "TTS provider and voice"
|
||||
},
|
||||
"stt": {
|
||||
"label": "Speech to Text",
|
||||
"description": "Whisper server URL"
|
||||
},
|
||||
"ocr": {
|
||||
"label": "OCR",
|
||||
"description": "Vision model for text extraction"
|
||||
@@ -234,12 +230,6 @@
|
||||
"saveFailed": "Failed to save OCR settings",
|
||||
"reachable": "Vision model server is reachable"
|
||||
},
|
||||
"stt": {
|
||||
"serverUrl": "Whisper Server URL",
|
||||
"saved": "STT settings saved",
|
||||
"saveFailed": "Failed to save STT settings",
|
||||
"reachable": "Whisper server is reachable"
|
||||
},
|
||||
"tts": {
|
||||
"openaiCompatible": "OpenAI-compatible (Kokoro, OpenAI, etc.)",
|
||||
"elevenLabs": "ElevenLabs",
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
"label": "Texto a voz",
|
||||
"description": "Proveedor de TTS y voz"
|
||||
},
|
||||
"stt": {
|
||||
"label": "Voz a texto",
|
||||
"description": "URL del servidor Whisper"
|
||||
},
|
||||
"ocr": {
|
||||
"label": "OCR",
|
||||
"description": "Modelo de visión para extracción de texto"
|
||||
@@ -234,12 +230,6 @@
|
||||
"saveFailed": "No se pudieron guardar los ajustes de OCR",
|
||||
"reachable": "El servidor del modelo de visión está accesible"
|
||||
},
|
||||
"stt": {
|
||||
"serverUrl": "URL del servidor Whisper",
|
||||
"saved": "Ajustes de STT guardados",
|
||||
"saveFailed": "No se pudieron guardar los ajustes de STT",
|
||||
"reachable": "El servidor Whisper está accesible"
|
||||
},
|
||||
"tts": {
|
||||
"openaiCompatible": "Compatible con OpenAI (Kokoro, OpenAI, etc.)",
|
||||
"elevenLabs": "ElevenLabs",
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
"label": "Synthèse vocale",
|
||||
"description": "Fournisseur et voix pour la synthèse vocale"
|
||||
},
|
||||
"stt": {
|
||||
"label": "Reconnaissance vocale",
|
||||
"description": "URL du serveur Whisper"
|
||||
},
|
||||
"ocr": {
|
||||
"label": "OCR",
|
||||
"description": "Modèle de vision pour l'extraction de texte"
|
||||
@@ -234,12 +230,6 @@
|
||||
"saveFailed": "Échec de l'enregistrement des paramètres OCR",
|
||||
"reachable": "Le serveur du modèle de vision est accessible"
|
||||
},
|
||||
"stt": {
|
||||
"serverUrl": "URL du serveur Whisper",
|
||||
"saved": "Paramètres de reconnaissance vocale enregistrés",
|
||||
"saveFailed": "Échec de l'enregistrement des paramètres de reconnaissance vocale",
|
||||
"reachable": "Le serveur Whisper est accessible"
|
||||
},
|
||||
"tts": {
|
||||
"openaiCompatible": "Compatible OpenAI (Kokoro, OpenAI, etc.)",
|
||||
"elevenLabs": "ElevenLabs",
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
"label": "Texto para fala",
|
||||
"description": "Fornecedor de TTS e voz"
|
||||
},
|
||||
"stt": {
|
||||
"label": "Fala para texto",
|
||||
"description": "URL do servidor Whisper"
|
||||
},
|
||||
"ocr": {
|
||||
"label": "OCR",
|
||||
"description": "Modelo de visão para extração de texto"
|
||||
@@ -234,12 +230,6 @@
|
||||
"saveFailed": "Falha ao guardar as definições de OCR",
|
||||
"reachable": "O servidor do modelo de visão está acessível"
|
||||
},
|
||||
"stt": {
|
||||
"serverUrl": "URL do servidor Whisper",
|
||||
"saved": "Definições de STT guardadas",
|
||||
"saveFailed": "Falha ao guardar as definições de STT",
|
||||
"reachable": "O servidor Whisper está acessível"
|
||||
},
|
||||
"tts": {
|
||||
"openaiCompatible": "Compatível com OpenAI (Kokoro, OpenAI, etc.)",
|
||||
"elevenLabs": "ElevenLabs",
|
||||
|
||||
@@ -145,10 +145,6 @@
|
||||
"label": "Text till tal",
|
||||
"description": "TTS-leverantör och röst"
|
||||
},
|
||||
"stt": {
|
||||
"label": "Tal till text",
|
||||
"description": "Whisper-serverns URL"
|
||||
},
|
||||
"ocr": {
|
||||
"label": "OCR",
|
||||
"description": "Synmodell för textextrahering"
|
||||
@@ -234,12 +230,6 @@
|
||||
"saveFailed": "Det gick inte att spara OCR-inställningarna",
|
||||
"reachable": "Synmodellsservern är nåbar"
|
||||
},
|
||||
"stt": {
|
||||
"serverUrl": "URL till Whisper-server",
|
||||
"saved": "STT-inställningarna sparade",
|
||||
"saveFailed": "Det gick inte att spara STT-inställningarna",
|
||||
"reachable": "Whisper-servern är nåbar"
|
||||
},
|
||||
"tts": {
|
||||
"openaiCompatible": "OpenAI-kompatibel (Kokoro, OpenAI, m.fl.)",
|
||||
"elevenLabs": "ElevenLabs",
|
||||
|
||||
Reference in New Issue
Block a user