fix(voice): allow local provider selection before install (#2257)

This commit is contained in:
Aqil Aziz
2026-05-19 16:49:23 -07:00
committed by GitHub
parent a1c74f1539
commit 8dbec71b80
2 changed files with 23 additions and 8 deletions
@@ -416,8 +416,9 @@ const VoicePanel = ({ embedded = false }: VoicePanelProps = {}) => {
</h3>
<p className="text-xs text-stone-500 dark:text-neutral-400 mt-1">
Choose where transcription and synthesis run. Use the Install locally buttons to
download the binaries and models into your workspace no manual{' '}
<code>WHISPER_BIN</code> or <code>PIPER_BIN</code> setup required.
download the binaries and models into your workspace. Local providers can be saved
before the install finishes no manual <code>WHISPER_BIN</code> or{' '}
<code>PIPER_BIN</code> setup required.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -433,7 +434,7 @@ const VoicePanel = ({ embedded = false }: VoicePanelProps = {}) => {
onChange={e => onSttProviderChange(e.target.value as 'cloud' | 'whisper')}
className="w-full rounded-md border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 focus:outline-none focus:ring-1 focus:ring-primary-400">
<option value="cloud">Cloud (Whisper proxy)</option>
<option value="whisper" disabled={!whisperReady}>
<option value="whisper">
Local Whisper{whisperReady ? '' : ' (install required)'}
</option>
</select>
@@ -523,7 +524,7 @@ const VoicePanel = ({ embedded = false }: VoicePanelProps = {}) => {
onChange={e => onTtsProviderChange(e.target.value as 'cloud' | 'piper')}
className="w-full rounded-md border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 focus:outline-none focus:ring-1 focus:ring-primary-400">
<option value="cloud">Cloud (ElevenLabs proxy)</option>
<option value="piper" disabled={!piperReady}>
<option value="piper">
Local Piper{piperReady ? '' : ' (install required)'}
</option>
</select>
@@ -332,7 +332,7 @@ describe('VoicePanel', () => {
expect(screen.getByTestId('whisper-install-state')).toHaveTextContent('Not installed');
});
it('disables the Local Whisper STT option when the engine is missing', async () => {
it('keeps the Local Whisper STT option selectable when the engine is missing', async () => {
runtime.whisperStatus = makeInstallStatus('whisper');
renderWithProviders(<VoicePanel />, { initialEntries: ['/settings/voice'] });
@@ -341,8 +341,15 @@ describe('VoicePanel', () => {
'option[value="whisper"]'
) as HTMLOptionElement | null;
expect(whisperOption).not.toBeNull();
expect(whisperOption!.disabled).toBe(true);
expect(whisperOption!.disabled).toBe(false);
expect(whisperOption!.textContent).toMatch(/install required/i);
fireEvent.change(sttSelect, { target: { value: 'whisper' } });
await waitFor(() =>
expect(vi.mocked(openhumanVoiceSetProviders)).toHaveBeenCalledWith(
expect.objectContaining({ stt_provider: 'whisper' })
)
);
});
it('shows a Reinstall label once Whisper is installed', async () => {
@@ -390,7 +397,7 @@ describe('VoicePanel', () => {
expect(screen.getByTestId('piper-install-state')).toHaveTextContent('Not installed');
});
it('disables the Local Piper TTS option when the engine is missing', async () => {
it('keeps the Local Piper TTS option selectable when the engine is missing', async () => {
runtime.piperStatus = makeInstallStatus('piper');
renderWithProviders(<VoicePanel />, { initialEntries: ['/settings/voice'] });
@@ -399,8 +406,15 @@ describe('VoicePanel', () => {
'option[value="piper"]'
) as HTMLOptionElement | null;
expect(piperOption).not.toBeNull();
expect(piperOption!.disabled).toBe(true);
expect(piperOption!.disabled).toBe(false);
expect(piperOption!.textContent).toMatch(/install required/i);
fireEvent.change(ttsSelect, { target: { value: 'piper' } });
await waitFor(() =>
expect(vi.mocked(openhumanVoiceSetProviders)).toHaveBeenCalledWith(
expect.objectContaining({ tts_provider: 'piper' })
)
);
});
it('triggers installPiper when the user clicks Install', async () => {