From ceceded0ff2196c39f47ad0a2a0d8dde7cc81a51 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Fri, 23 Jan 2026 17:28:16 +1100 Subject: [PATCH 1/8] removed duplicated call in chatinput and useWhisper --- .../src/routes/config_management.rs | 5 +++ ui/desktop/src/components/ChatInput.tsx | 35 +++++++++++-------- ui/desktop/src/hooks/useWhisper.ts | 1 + 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 05b0d013269c..4149d766436b 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -222,6 +222,11 @@ fn mask_secret(secret: Value) -> String { pub async fn read_config( Json(query): Json, ) -> Result, StatusCode> { + // TODO: Remove this - simulating slow keychain access for debugging + if query.is_secret { + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + } + if query.key == "model-limits" { let limits = ModelConfig::get_all_model_limits(); return Ok(Json(ConfigValueResponse::Value( diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index a53df80e474d..c9daa2b08b30 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -20,7 +20,6 @@ import { useWhisper } from '../hooks/useWhisper'; import { WaveformVisualizer } from './WaveformVisualizer'; import { toastError } from '../toasts'; import MentionPopover, { DisplayItemWithMatch } from './MentionPopover'; -import { useDictationSettings } from '../hooks/useDictationSettings'; import { COST_TRACKING_ENABLED, VOICE_DICTATION_ELEVENLABS_ENABLED } from '../updates'; import { CostTracker } from './bottom_menu/CostTracker'; import { DroppedFile, useFileDrop } from '../hooks/useFileDrop'; @@ -265,6 +264,7 @@ export default function ChatInput({ stopRecording, recordingDuration, estimatedSize, + dictationSettings, } = useWhisper({ onTranscription: (text) => { trackVoiceDictation('transcribed'); @@ -289,8 +289,6 @@ export default function ChatInput({ }); }, }); - - const { settings: dictationSettings } = useDictationSettings(); const internalTextAreaRef = useRef(null); const textAreaRef = inputRef || internalTextAreaRef; const timeoutRefsRef = useRef>>(new Set()); @@ -618,17 +616,20 @@ export default function ChatInput({ return [...pastedImageData, ...droppedImageData]; }, [pastedImages, allDroppedFiles]); - const appendDroppedFilePaths = useCallback((text: string): string => { - const droppedFilePaths = allDroppedFiles - .filter((file) => !file.isImage && !file.error && !file.isLoading) - .map((file) => file.path); + const appendDroppedFilePaths = useCallback( + (text: string): string => { + const droppedFilePaths = allDroppedFiles + .filter((file) => !file.isImage && !file.error && !file.isLoading) + .map((file) => file.path); - if (droppedFilePaths.length > 0) { - const pathsString = droppedFilePaths.join(' '); - return text ? `${text} ${pathsString}` : pathsString; - } - return text; - }, [allDroppedFiles]); + if (droppedFilePaths.length > 0) { + const pathsString = droppedFilePaths.join(' '); + return text ? `${text} ${pathsString}` : pathsString; + } + return text; + }, + [allDroppedFiles] + ); const clearInputState = useCallback(() => { setDisplayValue(''); @@ -1189,7 +1190,13 @@ export default function ChatInput({ onDrop={handleLocalDrop} onDragOver={handleLocalDragOver} > - + {/* Message Queue Display */} {queuedMessages.length > 0 && ( Date: Fri, 23 Jan 2026 17:29:01 +1100 Subject: [PATCH 2/8] created cache for hasElevenLabsKey --- .../settings/dictation/ElevenLabsKeyInput.tsx | 11 +++++--- ui/desktop/src/hooks/useDictationSettings.ts | 27 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx index 4cce6d21f6b4..b819f9caa01e 100644 --- a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx +++ b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'; import { Input } from '../../ui/input'; import { useConfig } from '../../ConfigContext'; import { ELEVENLABS_API_KEY } from '../../../hooks/dictationConstants'; +import { setElevenLabsKeyCache } from '../../../hooks/useDictationSettings'; export const ElevenLabsKeyInput = () => { const [elevenLabsApiKey, setElevenLabsApiKey] = useState(''); @@ -34,9 +35,11 @@ export const ElevenLabsKeyInput = () => { if (elevenLabsApiKeyRef.current) { const keyToSave = elevenLabsApiKeyRef.current; if (keyToSave.trim()) { - upsert(ELEVENLABS_API_KEY, keyToSave, true).catch((error) => { - console.error('Error saving ElevenLabs API key on unmount:', error); - }); + upsert(ELEVENLABS_API_KEY, keyToSave, true) + .then(() => setElevenLabsKeyCache(true)) + .catch((error) => { + console.error('Error saving ElevenLabs API key on unmount:', error); + }); } } }; @@ -56,11 +59,13 @@ export const ElevenLabsKeyInput = () => { console.log('Saving ElevenLabs API key to secure storage...'); await upsert(ELEVENLABS_API_KEY, elevenLabsApiKey, true); setHasElevenLabsKey(true); + setElevenLabsKeyCache(true); console.log('ElevenLabs API key saved successfully'); } else { console.log('Removing ElevenLabs API key from secure storage...'); await upsert(ELEVENLABS_API_KEY, null, true); setHasElevenLabsKey(false); + setElevenLabsKeyCache(false); console.log('ElevenLabs API key removed successfully'); } } catch (error) { diff --git a/ui/desktop/src/hooks/useDictationSettings.ts b/ui/desktop/src/hooks/useDictationSettings.ts index d6fe046b5a4a..1b2accd2c9a3 100644 --- a/ui/desktop/src/hooks/useDictationSettings.ts +++ b/ui/desktop/src/hooks/useDictationSettings.ts @@ -13,9 +13,17 @@ export interface DictationSettings { provider: DictationProvider; } +// Module-level cache for ElevenLabs key check (avoids repeated slow keychain calls) +let elevenLabsKeyCache: boolean | null = null; + +// Export setter for ElevenLabsKeyInput to update cache when saving +export const setElevenLabsKeyCache = (value: boolean) => { + elevenLabsKeyCache = value; +}; + export const useDictationSettings = () => { const [settings, setSettings] = useState(null); - const [hasElevenLabsKey, setHasElevenLabsKey] = useState(false); + const [hasElevenLabsKey, setHasElevenLabsKey] = useState(elevenLabsKeyCache ?? false); const { read, getProviders } = useConfig(); useEffect(() => { @@ -31,14 +39,17 @@ export const useDictationSettings = () => { setSettings(defaultSettings); } - // Load ElevenLabs API key from storage (non-secret for frontend access) - try { - const keyExists = await read(ELEVENLABS_API_KEY, true); - if (keyExists === true) { - setHasElevenLabsKey(true); + if (elevenLabsKeyCache === null) { + try { + const keyExists = await read(ELEVENLABS_API_KEY, true); + const hasKey = keyExists === true; + elevenLabsKeyCache = hasKey; + setHasElevenLabsKey(hasKey); + } catch (error) { + elevenLabsKeyCache = false; + setHasElevenLabsKey(false); + console.error('[useDictationSettings] Error loading ElevenLabs API key:', error); } - } catch (error) { - console.error('[useDictationSettings] Error loading ElevenLabs API key:', error); } }; From b7d8d39120867bfd5725c22114ff3c36c9dd94df Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Fri, 23 Jan 2026 17:43:47 +1100 Subject: [PATCH 3/8] only check ElevenLabsKey via api call when provider is elevenlabs --- .../settings/dictation/ElevenLabsKeyInput.tsx | 4 ++++ ui/desktop/src/hooks/useDictationSettings.ts | 12 +++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx index b819f9caa01e..eba6393fbb08 100644 --- a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx +++ b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx @@ -18,9 +18,13 @@ export const ElevenLabsKeyInput = () => { const keyExists = await read(ELEVENLABS_API_KEY, true); if (keyExists === true) { setHasElevenLabsKey(true); + setElevenLabsKeyCache(true); + } else { + setElevenLabsKeyCache(false); } } catch (error) { console.error('Error checking ElevenLabs API key:', error); + setElevenLabsKeyCache(false); } finally { setIsLoadingKey(false); } diff --git a/ui/desktop/src/hooks/useDictationSettings.ts b/ui/desktop/src/hooks/useDictationSettings.ts index 1b2accd2c9a3..182b995bbd4c 100644 --- a/ui/desktop/src/hooks/useDictationSettings.ts +++ b/ui/desktop/src/hooks/useDictationSettings.ts @@ -13,10 +13,8 @@ export interface DictationSettings { provider: DictationProvider; } -// Module-level cache for ElevenLabs key check (avoids repeated slow keychain calls) let elevenLabsKeyCache: boolean | null = null; -// Export setter for ElevenLabsKeyInput to update cache when saving export const setElevenLabsKeyCache = (value: boolean) => { elevenLabsKeyCache = value; }; @@ -31,15 +29,15 @@ export const useDictationSettings = () => { // Load settings from localStorage const saved = localStorage.getItem(DICTATION_SETTINGS_KEY); + let currentSettings: DictationSettings; if (saved) { - const parsedSettings = JSON.parse(saved); - setSettings(parsedSettings); + currentSettings = JSON.parse(saved); } else { - const defaultSettings = await getDefaultDictationSettings(getProviders); - setSettings(defaultSettings); + currentSettings = await getDefaultDictationSettings(getProviders); } + setSettings(currentSettings); - if (elevenLabsKeyCache === null) { + if (currentSettings.provider === 'elevenlabs' && elevenLabsKeyCache === null) { try { const keyExists = await read(ELEVENLABS_API_KEY, true); const hasKey = keyExists === true; From 1e9ab87f02febf20ca7fdb4fcb2e54bd4f404e3b Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Fri, 23 Jan 2026 17:55:05 +1100 Subject: [PATCH 4/8] extract to constant --- ui/desktop/src/components/ChatInput.tsx | 3 ++- .../components/settings/dictation/ProviderInfo.tsx | 3 ++- .../settings/dictation/ProviderSelector.tsx | 14 ++++++++------ .../settings/dictation/VoiceDictationToggle.tsx | 6 +++++- ui/desktop/src/hooks/dictationConstants.ts | 1 + ui/desktop/src/hooks/useDictationSettings.ts | 8 ++++++-- ui/desktop/src/hooks/useWhisper.ts | 5 +++-- 7 files changed, 27 insertions(+), 13 deletions(-) diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index c9daa2b08b30..5bd04d03962a 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -17,6 +17,7 @@ import { AlertType, useAlerts } from './alerts'; import { useConfig } from './ConfigContext'; import { useModelAndProvider } from './ModelAndProviderContext'; import { useWhisper } from '../hooks/useWhisper'; +import { DICTATION_PROVIDER_ELEVENLABS } from '../hooks/dictationConstants'; import { WaveformVisualizer } from './WaveformVisualizer'; import { toastError } from '../toasts'; import MentionPopover, { DisplayItemWithMatch } from './MentionPopover'; @@ -1277,7 +1278,7 @@ export default function ChatInput({ Models.

) : VOICE_DICTATION_ELEVENLABS_ENABLED && - dictationSettings.provider === 'elevenlabs' ? ( + dictationSettings.provider === DICTATION_PROVIDER_ELEVENLABS ? (

ElevenLabs API key is not configured. Set it up in Settings {'>'}{' '} Chat {'>'} Voice Dictation. diff --git a/ui/desktop/src/components/settings/dictation/ProviderInfo.tsx b/ui/desktop/src/components/settings/dictation/ProviderInfo.tsx index 5104502d206c..187f790d1f2f 100644 --- a/ui/desktop/src/components/settings/dictation/ProviderInfo.tsx +++ b/ui/desktop/src/components/settings/dictation/ProviderInfo.tsx @@ -1,4 +1,5 @@ import { DictationProvider } from '../../../hooks/useDictationSettings'; +import { DICTATION_PROVIDER_ELEVENLABS } from '../../../hooks/dictationConstants'; import { VOICE_DICTATION_ELEVENLABS_ENABLED } from '../../../updates'; interface ProviderInfoProps { @@ -16,7 +17,7 @@ export const ProviderInfo = ({ provider }: ProviderInfoProps) => { configured in the Models section.

)} - {VOICE_DICTATION_ELEVENLABS_ENABLED && provider === 'elevenlabs' && ( + {VOICE_DICTATION_ELEVENLABS_ENABLED && provider === DICTATION_PROVIDER_ELEVENLABS && (

Uses ElevenLabs speech-to-text API for high-quality transcription. diff --git a/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx b/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx index 479cd7b581b9..5c8e917d4356 100644 --- a/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx +++ b/ui/desktop/src/components/settings/dictation/ProviderSelector.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { ChevronDown } from 'lucide-react'; import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings'; +import { DICTATION_PROVIDER_ELEVENLABS } from '../../../hooks/dictationConstants'; import { useConfig } from '../../ConfigContext'; import { ElevenLabsKeyInput } from './ElevenLabsKeyInput'; import { ProviderInfo } from './ProviderInfo'; @@ -57,7 +58,7 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto switch (provider) { case 'openai': return 'OpenAI Whisper'; - case 'elevenlabs': + case DICTATION_PROVIDER_ELEVENLABS: return 'ElevenLabs'; default: return 'None (disabled)'; @@ -95,11 +96,13 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto {VOICE_DICTATION_ELEVENLABS_ENABLED && ( )}

@@ -107,9 +110,8 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto - {VOICE_DICTATION_ELEVENLABS_ENABLED && settings.provider === 'elevenlabs' && ( - - )} + {VOICE_DICTATION_ELEVENLABS_ENABLED && + settings.provider === DICTATION_PROVIDER_ELEVENLABS && } diff --git a/ui/desktop/src/components/settings/dictation/VoiceDictationToggle.tsx b/ui/desktop/src/components/settings/dictation/VoiceDictationToggle.tsx index 57d59be4fb0a..dcb4fea9cec1 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_ELEVENLABS, getDefaultDictationSettings, } from '../../../hooks/dictationConstants'; import { useConfig } from '../../ConfigContext'; @@ -28,7 +29,10 @@ export const VoiceDictationToggle = () => { loadedSettings = parsed; // If ElevenLabs is disabled and user has it selected, reset to OpenAI - if (!VOICE_DICTATION_ELEVENLABS_ENABLED && loadedSettings.provider === 'elevenlabs') { + if ( + !VOICE_DICTATION_ELEVENLABS_ENABLED && + loadedSettings.provider === DICTATION_PROVIDER_ELEVENLABS + ) { loadedSettings = { ...loadedSettings, provider: 'openai', diff --git a/ui/desktop/src/hooks/dictationConstants.ts b/ui/desktop/src/hooks/dictationConstants.ts index 972ba08b6cfa..fdb7e801f78b 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_ELEVENLABS = 'elevenlabs' as const; export const getDefaultDictationSettings = async ( getProviders: (refresh: boolean) => Promise> diff --git a/ui/desktop/src/hooks/useDictationSettings.ts b/ui/desktop/src/hooks/useDictationSettings.ts index 182b995bbd4c..085adb9bcb63 100644 --- a/ui/desktop/src/hooks/useDictationSettings.ts +++ b/ui/desktop/src/hooks/useDictationSettings.ts @@ -3,10 +3,11 @@ import { useConfig } from '../components/ConfigContext'; import { DICTATION_SETTINGS_KEY, ELEVENLABS_API_KEY, + DICTATION_PROVIDER_ELEVENLABS, getDefaultDictationSettings, } from './dictationConstants'; -export type DictationProvider = 'openai' | 'elevenlabs' | null; +export type DictationProvider = 'openai' | typeof DICTATION_PROVIDER_ELEVENLABS | null; export interface DictationSettings { enabled: boolean; @@ -37,7 +38,10 @@ export const useDictationSettings = () => { } setSettings(currentSettings); - if (currentSettings.provider === 'elevenlabs' && elevenLabsKeyCache === null) { + if ( + currentSettings.provider === DICTATION_PROVIDER_ELEVENLABS && + elevenLabsKeyCache === null + ) { try { const keyExists = await read(ELEVENLABS_API_KEY, true); const hasKey = keyExists === true; diff --git a/ui/desktop/src/hooks/useWhisper.ts b/ui/desktop/src/hooks/useWhisper.ts index a60d97225738..0cc557f807e6 100644 --- a/ui/desktop/src/hooks/useWhisper.ts +++ b/ui/desktop/src/hooks/useWhisper.ts @@ -2,6 +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 { safeJsonParse } from '../utils/conversionUtils'; interface UseWhisperOptions { @@ -77,7 +78,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp case 'openai': setCanUseDictation(hasOpenAIKey); break; - case 'elevenlabs': + case DICTATION_PROVIDER_ELEVENLABS: setCanUseDictation(hasElevenLabsKey); break; default: @@ -180,7 +181,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp case 'openai': endpoint = '/audio/transcribe'; break; - case 'elevenlabs': + case DICTATION_PROVIDER_ELEVENLABS: endpoint = '/audio/transcribe/elevenlabs'; break; default: From dc343934107d2d061749e6e32658fcec55795cce Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Fri, 23 Jan 2026 19:46:08 +1100 Subject: [PATCH 5/8] added correct check for api key is set --- .../settings/dictation/ElevenLabsKeyInput.tsx | 10 +++------- ui/desktop/src/hooks/dictationConstants.ts | 6 ++++++ ui/desktop/src/hooks/useDictationSettings.ts | 8 ++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx index eba6393fbb08..a305faf05404 100644 --- a/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx +++ b/ui/desktop/src/components/settings/dictation/ElevenLabsKeyInput.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { Input } from '../../ui/input'; import { useConfig } from '../../ConfigContext'; -import { ELEVENLABS_API_KEY } from '../../../hooks/dictationConstants'; +import { ELEVENLABS_API_KEY, isSecretKeyConfigured } from '../../../hooks/dictationConstants'; import { setElevenLabsKeyCache } from '../../../hooks/useDictationSettings'; export const ElevenLabsKeyInput = () => { @@ -15,8 +15,8 @@ export const ElevenLabsKeyInput = () => { const loadKey = async () => { setIsLoadingKey(true); try { - const keyExists = await read(ELEVENLABS_API_KEY, true); - if (keyExists === true) { + const response = await read(ELEVENLABS_API_KEY, true); + if (isSecretKeyConfigured(response)) { setHasElevenLabsKey(true); setElevenLabsKeyCache(true); } else { @@ -60,17 +60,13 @@ export const ElevenLabsKeyInput = () => { const saveElevenLabsKey = async () => { try { if (elevenLabsApiKey.trim()) { - console.log('Saving ElevenLabs API key to secure storage...'); await upsert(ELEVENLABS_API_KEY, elevenLabsApiKey, true); setHasElevenLabsKey(true); setElevenLabsKeyCache(true); - console.log('ElevenLabs API key saved successfully'); } else { - console.log('Removing ElevenLabs API key from secure storage...'); await upsert(ELEVENLABS_API_KEY, null, true); setHasElevenLabsKey(false); setElevenLabsKeyCache(false); - console.log('ElevenLabs API key removed successfully'); } } catch (error) { console.error('Error saving ElevenLabs API key:', error); diff --git a/ui/desktop/src/hooks/dictationConstants.ts b/ui/desktop/src/hooks/dictationConstants.ts index fdb7e801f78b..c9c7ee65d9d5 100644 --- a/ui/desktop/src/hooks/dictationConstants.ts +++ b/ui/desktop/src/hooks/dictationConstants.ts @@ -4,6 +4,12 @@ export const DICTATION_SETTINGS_KEY = 'dictation_settings'; export const ELEVENLABS_API_KEY = 'ELEVENLABS_API_KEY'; export const DICTATION_PROVIDER_ELEVENLABS = 'elevenlabs' as const; +export const isSecretKeyConfigured = (response: unknown): boolean => + typeof response === 'object' && + response !== null && + 'maskedValue' in response && + !!(response as { maskedValue: string }).maskedValue; + export const getDefaultDictationSettings = async ( getProviders: (refresh: boolean) => Promise> ): Promise => { diff --git a/ui/desktop/src/hooks/useDictationSettings.ts b/ui/desktop/src/hooks/useDictationSettings.ts index 085adb9bcb63..778006f7e6d4 100644 --- a/ui/desktop/src/hooks/useDictationSettings.ts +++ b/ui/desktop/src/hooks/useDictationSettings.ts @@ -5,6 +5,7 @@ import { ELEVENLABS_API_KEY, DICTATION_PROVIDER_ELEVENLABS, getDefaultDictationSettings, + isSecretKeyConfigured, } from './dictationConstants'; export type DictationProvider = 'openai' | typeof DICTATION_PROVIDER_ELEVENLABS | null; @@ -37,20 +38,19 @@ export const useDictationSettings = () => { currentSettings = await getDefaultDictationSettings(getProviders); } setSettings(currentSettings); - if ( currentSettings.provider === DICTATION_PROVIDER_ELEVENLABS && elevenLabsKeyCache === null ) { try { - const keyExists = await read(ELEVENLABS_API_KEY, true); - const hasKey = keyExists === true; + const response = await read(ELEVENLABS_API_KEY, true); + const hasKey = isSecretKeyConfigured(response); elevenLabsKeyCache = hasKey; setHasElevenLabsKey(hasKey); } catch (error) { elevenLabsKeyCache = false; setHasElevenLabsKey(false); - console.error('[useDictationSettings] Error loading ElevenLabs API key:', error); + console.error('[useDictationSettings] Error checking ElevenLabs API key:', error); } } }; From 65f8c9e902d3c6787c2db39e42a929bfeadace6a Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Fri, 23 Jan 2026 23:00:46 +1100 Subject: [PATCH 6/8] used a react ref for providerList to avoid multiple call --- ui/desktop/src/components/ConfigContext.tsx | 33 +++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/ui/desktop/src/components/ConfigContext.tsx b/ui/desktop/src/components/ConfigContext.tsx index add93c0cc00a..e2de27f30233 100644 --- a/ui/desktop/src/components/ConfigContext.tsx +++ b/ui/desktop/src/components/ConfigContext.tsx @@ -66,6 +66,10 @@ export const ConfigProvider: React.FC = ({ children }) => { const [extensionsList, setExtensionsList] = useState([]); const [extensionWarnings, setExtensionWarnings] = useState([]); + // Ref to access providersList in getProviders without recreating the callback + const providersListRef = React.useRef(providersList); + providersListRef.current = providersList; + const reloadConfig = useCallback(async () => { const response = await readAllConfig(); setConfig(response.data?.config || {}); @@ -168,23 +172,20 @@ export const ConfigProvider: React.FC = ({ children }) => { [addExtension, getExtensions] ); - const getProviders = useCallback( - async (forceRefresh = false): Promise => { - if (forceRefresh || providersList.length === 0) { - try { - const response = await providers(); - const providersData = response.data || []; - setProvidersList(providersData); - return providersData; - } catch (error) { - console.error('Failed to fetch providers:', error); - return []; - } + const getProviders = useCallback(async (forceRefresh = false): Promise => { + if (forceRefresh || providersListRef.current.length === 0) { + try { + const response = await providers(); + const providersData = response.data || []; + setProvidersList(providersData); + return providersData; + } catch (error) { + console.error('Failed to fetch providers:', error); + return []; } - return providersList; - }, - [providersList] - ); + } + return providersListRef.current; + }, []); const getProviderModels = useCallback(async (providerName: string): Promise => { try { From a3ac05a2eb8bc7f994fd42cdb313d1fc17b6ec65 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Fri, 23 Jan 2026 23:29:25 +1100 Subject: [PATCH 7/8] only show session extension config when the extensions are loaded --- .../components/bottom_menu/BottomMenuExtensionSelection.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/desktop/src/components/bottom_menu/BottomMenuExtensionSelection.tsx b/ui/desktop/src/components/bottom_menu/BottomMenuExtensionSelection.tsx index 3de8f3e3b82c..f1fa663ec8b8 100644 --- a/ui/desktop/src/components/bottom_menu/BottomMenuExtensionSelection.tsx +++ b/ui/desktop/src/components/bottom_menu/BottomMenuExtensionSelection.tsx @@ -28,6 +28,7 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS const [pendingSort, setPendingSort] = useState(false); const [togglingExtension, setTogglingExtension] = useState(null); const [refreshTrigger, setRefreshTrigger] = useState(0); + const [isSessionExtensionsLoaded, setIsSessionExtensionsLoaded] = useState(false); const sortTimeoutRef = useRef | null>(null); const { extensionsList: allExtensions } = useConfig(); const isHubView = !sessionId; @@ -70,12 +71,15 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS if (response.data?.extensions) { setSessionExtensions(response.data.extensions); + setIsSessionExtensionsLoaded(true); } } catch (error) { console.error('Failed to fetch session extensions:', error); + setIsSessionExtensionsLoaded(true); } }; + setIsSessionExtensionsLoaded(false); fetchExtensions(); }, [sessionId, isOpen, refreshTrigger]); @@ -225,7 +229,7 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS >