diff --git a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx
index a305faf05404..1856a8c5e5c5 100644
--- a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx
+++ b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx
@@ -1,5 +1,6 @@
-import { useState, useEffect, useRef } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { Input } from '../../ui/input';
+import { Button } from '../../ui/button';
import { useConfig } from '../../ConfigContext';
import { ELEVENLABS_API_KEY, isSecretKeyConfigured } from '../../../hooks/dictationConstants';
import { setElevenLabsKeyCache } from '../../../hooks/useDictationSettings';
@@ -8,71 +9,75 @@ export const ElevenLabsKeyInput = () => {
const [elevenLabsApiKey, setElevenLabsApiKey] = useState('');
const [isLoadingKey, setIsLoadingKey] = useState(false);
const [hasElevenLabsKey, setHasElevenLabsKey] = useState(false);
- const elevenLabsApiKeyRef = useRef('');
- const { upsert, read } = useConfig();
+ const [validationError, setValidationError] = useState('');
+ const [isEditing, setIsEditing] = useState(false);
+ const { upsert, read, remove } = useConfig();
- useEffect(() => {
- const loadKey = async () => {
- setIsLoadingKey(true);
- try {
- const response = await read(ELEVENLABS_API_KEY, true);
- if (isSecretKeyConfigured(response)) {
- setHasElevenLabsKey(true);
- setElevenLabsKeyCache(true);
- } else {
- setElevenLabsKeyCache(false);
- }
- } catch (error) {
- console.error('Error checking ElevenLabs API key:', error);
- setElevenLabsKeyCache(false);
- } finally {
- setIsLoadingKey(false);
- }
- };
-
- loadKey();
+ const loadKey = useCallback(async () => {
+ setIsLoadingKey(true);
+ try {
+ const response = await read(ELEVENLABS_API_KEY, true);
+ const hasKey = isSecretKeyConfigured(response);
+ setHasElevenLabsKey(hasKey);
+ setElevenLabsKeyCache(hasKey);
+ } catch (error) {
+ console.error(error);
+ setElevenLabsKeyCache(false);
+ } finally {
+ setIsLoadingKey(false);
+ }
}, [read]);
- // Save key on unmount to avoid losing unsaved changes
useEffect(() => {
- return () => {
- if (elevenLabsApiKeyRef.current) {
- const keyToSave = elevenLabsApiKeyRef.current;
- if (keyToSave.trim()) {
- upsert(ELEVENLABS_API_KEY, keyToSave, true)
- .then(() => setElevenLabsKeyCache(true))
- .catch((error) => {
- console.error('Error saving ElevenLabs API key on unmount:', error);
- });
- }
- }
- };
- }, [upsert]);
+ loadKey();
+ }, [loadKey]);
const handleElevenLabsKeyChange = (key: string) => {
setElevenLabsApiKey(key);
- elevenLabsApiKeyRef.current = key;
- if (key.length > 0) {
- setHasElevenLabsKey(false);
+ if (validationError) {
+ setValidationError('');
}
};
- const saveElevenLabsKey = async () => {
+ const handleSave = async () => {
try {
- if (elevenLabsApiKey.trim()) {
- await upsert(ELEVENLABS_API_KEY, elevenLabsApiKey, true);
- setHasElevenLabsKey(true);
- setElevenLabsKeyCache(true);
- } else {
- await upsert(ELEVENLABS_API_KEY, null, true);
- setHasElevenLabsKey(false);
- setElevenLabsKeyCache(false);
+ const trimmedKey = elevenLabsApiKey.trim();
+
+ if (!trimmedKey) {
+ setValidationError('API key is required');
+ return;
}
+
+ await upsert(ELEVENLABS_API_KEY, trimmedKey, true);
+ setElevenLabsApiKey('');
+ setValidationError('');
+ setIsEditing(false);
+ await loadKey();
} catch (error) {
- console.error('Error saving ElevenLabs API key:', error);
+ console.error(error);
+ setValidationError('Failed to save API key');
}
};
+ const handleRemove = async () => {
+ try {
+ await remove(ELEVENLABS_API_KEY, true);
+ await loadKey();
+ setElevenLabsApiKey('');
+ setValidationError('');
+ setIsEditing(false);
+ } catch (error) {
+ console.error(error);
+ setValidationError('Failed to remove API key');
+ }
+ };
+
+ const handleCancel = () => {
+ setElevenLabsApiKey('');
+ setValidationError('');
+ setIsEditing(false);
+ };
+
return (
@@ -82,17 +87,42 @@ export const ElevenLabsKeyInput = () => {
{hasElevenLabsKey && (Configured)}
-
handleElevenLabsKeyChange(e.target.value)}
- onBlur={saveElevenLabsKey}
- placeholder={
- hasElevenLabsKey ? 'Enter new API key to update' : 'Enter your ElevenLabs API key'
- }
- className="max-w-md"
- disabled={isLoadingKey}
- />
+
+ {!isEditing ? (
+
+ ) : (
+
+
handleElevenLabsKeyChange(e.target.value)}
+ placeholder="Enter your ElevenLabs API key"
+ className="max-w-md"
+ autoFocus
+ />
+ {validationError &&
{validationError}
}
+
+
+
+ {hasElevenLabsKey && (
+
+ )}
+
+
+ )}
);
};
diff --git a/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx b/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx
index 5c8e917d4356..4b3878509a06 100644
--- a/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx
+++ b/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx
@@ -1,7 +1,10 @@
import { useState, useEffect } from 'react';
import { ChevronDown } from 'lucide-react';
import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings';
-import { DICTATION_PROVIDER_ELEVENLABS } from '../../../hooks/dictationConstants';
+import {
+ DICTATION_PROVIDER_OPENAI,
+ DICTATION_PROVIDER_ELEVENLABS,
+} from '../../../hooks/dictationConstants';
import { useConfig } from '../../ConfigContext';
import { ElevenLabsKeyInput } from './ElevenLabsKeyInput';
import { ProviderInfo } from './ProviderInfo';
@@ -56,7 +59,7 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto
const getProviderLabel = (provider: DictationProvider): string => {
switch (provider) {
- case 'openai':
+ case DICTATION_PROVIDER_OPENAI:
return 'OpenAI Whisper';
case DICTATION_PROVIDER_ELEVENLABS:
return 'ElevenLabs';
@@ -84,25 +87,31 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto
{showProviderDropdown && (
-
+
{VOICE_DICTATION_ELEVENLABS_ENABLED && (
)}
diff --git a/ui/desktop/src/components/settings/dictation/VoiceDictationToggle.tsx b/ui/desktop/src/components/settings/dictation/VoiceDictationToggle.tsx
index dcb4fea9cec1..6af1c71c8916 100644
--- a/ui/desktop/src/components/settings/dictation/VoiceDictationToggle.tsx
+++ b/ui/desktop/src/components/settings/dictation/VoiceDictationToggle.tsx
@@ -3,6 +3,7 @@ import { Switch } from '../../ui/switch';
import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings';
import {
DICTATION_SETTINGS_KEY,
+ DICTATION_PROVIDER_OPENAI,
DICTATION_PROVIDER_ELEVENLABS,
getDefaultDictationSettings,
} from '../../../hooks/dictationConstants';
@@ -35,7 +36,7 @@ export const VoiceDictationToggle = () => {
) {
loadedSettings = {
...loadedSettings,
- provider: 'openai',
+ provider: DICTATION_PROVIDER_OPENAI,
};
localStorage.setItem(DICTATION_SETTINGS_KEY, JSON.stringify(loadedSettings));
}
@@ -59,7 +60,7 @@ export const VoiceDictationToggle = () => {
saveSettings({
...settings,
enabled,
- provider: settings.provider === null ? 'openai' : settings.provider,
+ provider: settings.provider === null ? DICTATION_PROVIDER_OPENAI : settings.provider,
});
trackSettingToggled('voice_dictation', enabled);
};
@@ -83,7 +84,7 @@ export const VoiceDictationToggle = () => {
diff --git a/ui/desktop/src/hooks/dictationConstants.ts b/ui/desktop/src/hooks/dictationConstants.ts
index c9c7ee65d9d5..1863c6decace 100644
--- a/ui/desktop/src/hooks/dictationConstants.ts
+++ b/ui/desktop/src/hooks/dictationConstants.ts
@@ -2,6 +2,7 @@ import { DictationSettings, DictationProvider } from './useDictationSettings';
export const DICTATION_SETTINGS_KEY = 'dictation_settings';
export const ELEVENLABS_API_KEY = 'ELEVENLABS_API_KEY';
+export const DICTATION_PROVIDER_OPENAI = 'openai' as const;
export const DICTATION_PROVIDER_ELEVENLABS = 'elevenlabs' as const;
export const isSecretKeyConfigured = (response: unknown): boolean =>
@@ -14,14 +15,12 @@ export const getDefaultDictationSettings = async (
getProviders: (refresh: boolean) => Promise
>
): Promise => {
const providers = await getProviders(false);
-
- // Check if we have an OpenAI API key as primary default
const openAIProvider = providers.find((p) => p.name === 'openai');
if (openAIProvider && openAIProvider.is_configured) {
return {
enabled: true,
- provider: 'openai' as DictationProvider,
+ provider: DICTATION_PROVIDER_OPENAI,
};
} else {
return {
diff --git a/ui/desktop/src/hooks/useDictationSettings.ts b/ui/desktop/src/hooks/useDictationSettings.ts
index 778006f7e6d4..4afe4e0f2574 100644
--- a/ui/desktop/src/hooks/useDictationSettings.ts
+++ b/ui/desktop/src/hooks/useDictationSettings.ts
@@ -3,12 +3,16 @@ import { useConfig } from '../components/ConfigContext';
import {
DICTATION_SETTINGS_KEY,
ELEVENLABS_API_KEY,
+ DICTATION_PROVIDER_OPENAI,
DICTATION_PROVIDER_ELEVENLABS,
getDefaultDictationSettings,
isSecretKeyConfigured,
} from './dictationConstants';
-export type DictationProvider = 'openai' | typeof DICTATION_PROVIDER_ELEVENLABS | null;
+export type DictationProvider =
+ | typeof DICTATION_PROVIDER_OPENAI
+ | typeof DICTATION_PROVIDER_ELEVENLABS
+ | null;
export interface DictationSettings {
enabled: boolean;
@@ -28,7 +32,6 @@ export const useDictationSettings = () => {
useEffect(() => {
const loadSettings = async () => {
- // Load settings from localStorage
const saved = localStorage.getItem(DICTATION_SETTINGS_KEY);
let currentSettings: DictationSettings;
diff --git a/ui/desktop/src/hooks/useWhisper.ts b/ui/desktop/src/hooks/useWhisper.ts
index 0cc557f807e6..caf8a1a3040a 100644
--- a/ui/desktop/src/hooks/useWhisper.ts
+++ b/ui/desktop/src/hooks/useWhisper.ts
@@ -2,7 +2,7 @@ import { useState, useRef, useCallback, useEffect } from 'react';
import { useConfig } from '../components/ConfigContext';
import { getApiUrl } from '../config';
import { useDictationSettings } from './useDictationSettings';
-import { DICTATION_PROVIDER_ELEVENLABS } from './dictationConstants';
+import { DICTATION_PROVIDER_OPENAI, DICTATION_PROVIDER_ELEVENLABS } from './dictationConstants';
import { safeJsonParse } from '../utils/conversionUtils';
interface UseWhisperOptions {
@@ -75,7 +75,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
// Check provider availability
switch (dictationSettings.provider) {
- case 'openai':
+ case DICTATION_PROVIDER_OPENAI:
setCanUseDictation(hasOpenAIKey);
break;
case DICTATION_PROVIDER_ELEVENLABS:
@@ -178,7 +178,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
// Choose endpoint based on provider
switch (dictationSettings.provider) {
- case 'openai':
+ case DICTATION_PROVIDER_OPENAI:
endpoint = '/audio/transcribe';
break;
case DICTATION_PROVIDER_ELEVENLABS: