Skip to content

Commit b54d918

Browse files
luongndclaude
andcommitted
fix(local-stt): disable cabin translation in Local mode
Cabin translation is Nvidia NMT (cloud) with no offline model, so when the STT provider is Local (offline) the translate toggle is now disabled with a hint and forced off — preventing the silent no-op where the toggle stayed on but produced nothing. RecordingBar tracks the saved provider reactively (refreshed on a settings-saved event dispatched by SettingsPanel). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent de7d6d2 commit b54d918

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

src/components/RecordingBar.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export function RecordingBar() {
3333
const [audioSource, setAudioSource] = useState<'mic' | 'system' | 'both'>('mic');
3434
const [barHeights, setBarHeights] = useState(['4px', '4px', '4px']);
3535
const sttProviderRef = useRef('nvidia');
36+
// Reactive copy of the saved STT provider for UI gating. Cabin translation
37+
// is Nvidia NMT (cloud) — there is no offline translate model — so when the
38+
// user picks Local (offline) we disable the toggle entirely.
39+
const [sttProvider, setSttProvider] = useState('nvidia');
40+
const cabinDisabled = sttProvider === 'local';
3641
const { showToast } = useToast();
3742

3843
// AI is OPTIONAL — `aiConfigured` (resolved in App.tsx, shared via store)
@@ -85,6 +90,26 @@ export function RecordingBar() {
8590
setBarHeightsFn.current = setBarHeights;
8691
}, [setBarHeightsFn]);
8792

93+
// Keep the reactive STT provider in sync with saved settings: fetch on
94+
// mount and whenever Settings is saved (SettingsPanel dispatches the event).
95+
useEffect(() => {
96+
let alive = true;
97+
const refresh = () => {
98+
getSettings()
99+
.then((s) => { if (alive) setSttProvider((s.stt_provider || 'nvidia').toLowerCase()); })
100+
.catch(() => {});
101+
};
102+
refresh();
103+
window.addEventListener('scribble:settings-saved', refresh);
104+
return () => { alive = false; window.removeEventListener('scribble:settings-saved', refresh); };
105+
}, []);
106+
107+
// Local (offline) has no translation model — force cabin translation off so
108+
// no stale TRANSLATE command/lang is sent on the local WS.
109+
useEffect(() => {
110+
if (cabinDisabled && translationEnabled) setTranslationEnabled(false);
111+
}, [cabinDisabled, translationEnabled, setTranslationEnabled]);
112+
88113
// ── Mid-session translation toggle ──
89114
const translationToggleRef = useRef({ enabled: translationEnabled, lang: translationLang });
90115
useEffect(() => {
@@ -806,18 +831,27 @@ export function RecordingBar() {
806831
/>
807832
</div>
808833
<div className="bar-divider" />
809-
<div className="translation-toggle-wrap">
834+
<div
835+
className="translation-toggle-wrap"
836+
title={cabinDisabled
837+
? (lang === 'vi'
838+
? 'Dịch cabin cần Nvidia (chưa hỗ trợ dịch offline) — không khả dụng ở chế độ Local'
839+
: 'Cabin translation needs Nvidia (no offline translate model) — unavailable in Local mode')
840+
: undefined}
841+
style={cabinDisabled ? { opacity: 0.45 } : undefined}
842+
>
810843
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
811844
<path d="m5 8 6 6" /><path d="m4 14 6-6 2-3" /><path d="M2 5h12" /><path d="M7 2h1" /><path d="m22 22-5-10-5 10" /><path d="M14 18h6" />
812845
</svg>
813846
<span>{lang === 'vi' ? 'Dịch cabin' : 'Translate'}</span>
814847
<label className="toggle toggle-sm">
815-
<input type="checkbox" checked={translationEnabled} onChange={(e) => setTranslationEnabled(e.target.checked)} />
848+
<input type="checkbox" checked={translationEnabled && !cabinDisabled} disabled={cabinDisabled}
849+
onChange={(e) => setTranslationEnabled(e.target.checked)} />
816850
<span className="toggle-slider" />
817851
</label>
818852
</div>
819853
<CustomSelect className="translation-lang-select" value={translationLang}
820-
onChange={setTranslationLang} disabled={!translationEnabled}
854+
onChange={setTranslationLang} disabled={!translationEnabled || cabinDisabled}
821855
options={[
822856
{ value: 'vi', label: 'Vietnamese' }, { value: 'en', label: 'English' },
823857
{ value: 'ja', label: 'Japanese' }, { value: 'ko', label: 'Korean' },

src/components/SettingsPanel.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,12 @@ export function SettingsPanel() {
153153

154154
const handleSave = async () => {
155155
setSaving(true);
156-
try { await saveSettings(buildSettingsBody()); } catch (e) { console.warn('[settings] Save failed:', e); }
156+
try {
157+
await saveSettings(buildSettingsBody());
158+
// Notify other views (e.g. RecordingBar gates cabin translation by
159+
// the saved STT provider) that settings changed.
160+
window.dispatchEvent(new CustomEvent('scribble:settings-saved'));
161+
} catch (e) { console.warn('[settings] Save failed:', e); }
157162
setSaving(false);
158163
setSettingsOpen(false);
159164
};

0 commit comments

Comments
 (0)