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
>