Optimise load config in UI (#6662)
This commit is contained in:
@@ -17,10 +17,10 @@ import { AlertType, useAlerts } from './alerts';
|
|||||||
import { useConfig } from './ConfigContext';
|
import { useConfig } from './ConfigContext';
|
||||||
import { useModelAndProvider } from './ModelAndProviderContext';
|
import { useModelAndProvider } from './ModelAndProviderContext';
|
||||||
import { useWhisper } from '../hooks/useWhisper';
|
import { useWhisper } from '../hooks/useWhisper';
|
||||||
|
import { DICTATION_PROVIDER_ELEVENLABS } from '../hooks/dictationConstants';
|
||||||
import { WaveformVisualizer } from './WaveformVisualizer';
|
import { WaveformVisualizer } from './WaveformVisualizer';
|
||||||
import { toastError } from '../toasts';
|
import { toastError } from '../toasts';
|
||||||
import MentionPopover, { DisplayItemWithMatch } from './MentionPopover';
|
import MentionPopover, { DisplayItemWithMatch } from './MentionPopover';
|
||||||
import { useDictationSettings } from '../hooks/useDictationSettings';
|
|
||||||
import { COST_TRACKING_ENABLED, VOICE_DICTATION_ELEVENLABS_ENABLED } from '../updates';
|
import { COST_TRACKING_ENABLED, VOICE_DICTATION_ELEVENLABS_ENABLED } from '../updates';
|
||||||
import { CostTracker } from './bottom_menu/CostTracker';
|
import { CostTracker } from './bottom_menu/CostTracker';
|
||||||
import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
|
import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
|
||||||
@@ -265,6 +265,7 @@ export default function ChatInput({
|
|||||||
stopRecording,
|
stopRecording,
|
||||||
recordingDuration,
|
recordingDuration,
|
||||||
estimatedSize,
|
estimatedSize,
|
||||||
|
dictationSettings,
|
||||||
} = useWhisper({
|
} = useWhisper({
|
||||||
onTranscription: (text) => {
|
onTranscription: (text) => {
|
||||||
trackVoiceDictation('transcribed');
|
trackVoiceDictation('transcribed');
|
||||||
@@ -289,8 +290,6 @@ export default function ChatInput({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { settings: dictationSettings } = useDictationSettings();
|
|
||||||
const internalTextAreaRef = useRef<HTMLTextAreaElement>(null);
|
const internalTextAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const textAreaRef = inputRef || internalTextAreaRef;
|
const textAreaRef = inputRef || internalTextAreaRef;
|
||||||
const timeoutRefsRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
|
const timeoutRefsRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
|
||||||
@@ -618,17 +617,20 @@ export default function ChatInput({
|
|||||||
return [...pastedImageData, ...droppedImageData];
|
return [...pastedImageData, ...droppedImageData];
|
||||||
}, [pastedImages, allDroppedFiles]);
|
}, [pastedImages, allDroppedFiles]);
|
||||||
|
|
||||||
const appendDroppedFilePaths = useCallback((text: string): string => {
|
const appendDroppedFilePaths = useCallback(
|
||||||
const droppedFilePaths = allDroppedFiles
|
(text: string): string => {
|
||||||
.filter((file) => !file.isImage && !file.error && !file.isLoading)
|
const droppedFilePaths = allDroppedFiles
|
||||||
.map((file) => file.path);
|
.filter((file) => !file.isImage && !file.error && !file.isLoading)
|
||||||
|
.map((file) => file.path);
|
||||||
|
|
||||||
if (droppedFilePaths.length > 0) {
|
if (droppedFilePaths.length > 0) {
|
||||||
const pathsString = droppedFilePaths.join(' ');
|
const pathsString = droppedFilePaths.join(' ');
|
||||||
return text ? `${text} ${pathsString}` : pathsString;
|
return text ? `${text} ${pathsString}` : pathsString;
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}, [allDroppedFiles]);
|
},
|
||||||
|
[allDroppedFiles]
|
||||||
|
);
|
||||||
|
|
||||||
const clearInputState = useCallback(() => {
|
const clearInputState = useCallback(() => {
|
||||||
setDisplayValue('');
|
setDisplayValue('');
|
||||||
@@ -1189,7 +1191,13 @@ export default function ChatInput({
|
|||||||
onDrop={handleLocalDrop}
|
onDrop={handleLocalDrop}
|
||||||
onDragOver={handleLocalDragOver}
|
onDragOver={handleLocalDragOver}
|
||||||
>
|
>
|
||||||
<input ref={fileInputRef} type="file" onChange={handleFileInputChange} style={{ display: 'none' }} accept="*/*" />
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileInputChange}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
accept="*/*"
|
||||||
|
/>
|
||||||
{/* Message Queue Display */}
|
{/* Message Queue Display */}
|
||||||
{queuedMessages.length > 0 && (
|
{queuedMessages.length > 0 && (
|
||||||
<MessageQueue
|
<MessageQueue
|
||||||
@@ -1270,7 +1278,7 @@ export default function ChatInput({
|
|||||||
<b>Models.</b>
|
<b>Models.</b>
|
||||||
</p>
|
</p>
|
||||||
) : VOICE_DICTATION_ELEVENLABS_ENABLED &&
|
) : VOICE_DICTATION_ELEVENLABS_ENABLED &&
|
||||||
dictationSettings.provider === 'elevenlabs' ? (
|
dictationSettings.provider === DICTATION_PROVIDER_ELEVENLABS ? (
|
||||||
<p>
|
<p>
|
||||||
ElevenLabs API key is not configured. Set it up in <b>Settings</b> {'>'}{' '}
|
ElevenLabs API key is not configured. Set it up in <b>Settings</b> {'>'}{' '}
|
||||||
<b>Chat</b> {'>'} <b>Voice Dictation.</b>
|
<b>Chat</b> {'>'} <b>Voice Dictation.</b>
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
|||||||
const [extensionsList, setExtensionsList] = useState<FixedExtensionEntry[]>([]);
|
const [extensionsList, setExtensionsList] = useState<FixedExtensionEntry[]>([]);
|
||||||
const [extensionWarnings, setExtensionWarnings] = useState<string[]>([]);
|
const [extensionWarnings, setExtensionWarnings] = useState<string[]>([]);
|
||||||
|
|
||||||
|
// Ref to access providersList in getProviders without recreating the callback
|
||||||
|
const providersListRef = React.useRef<ProviderDetails[]>(providersList);
|
||||||
|
providersListRef.current = providersList;
|
||||||
|
|
||||||
const reloadConfig = useCallback(async () => {
|
const reloadConfig = useCallback(async () => {
|
||||||
const response = await readAllConfig();
|
const response = await readAllConfig();
|
||||||
setConfig(response.data?.config || {});
|
setConfig(response.data?.config || {});
|
||||||
@@ -168,23 +172,20 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
|||||||
[addExtension, getExtensions]
|
[addExtension, getExtensions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getProviders = useCallback(
|
const getProviders = useCallback(async (forceRefresh = false): Promise<ProviderDetails[]> => {
|
||||||
async (forceRefresh = false): Promise<ProviderDetails[]> => {
|
if (forceRefresh || providersListRef.current.length === 0) {
|
||||||
if (forceRefresh || providersList.length === 0) {
|
try {
|
||||||
try {
|
const response = await providers();
|
||||||
const response = await providers();
|
const providersData = response.data || [];
|
||||||
const providersData = response.data || [];
|
setProvidersList(providersData);
|
||||||
setProvidersList(providersData);
|
return providersData;
|
||||||
return providersData;
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error('Failed to fetch providers:', error);
|
||||||
console.error('Failed to fetch providers:', error);
|
return [];
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return providersList;
|
}
|
||||||
},
|
return providersListRef.current;
|
||||||
[providersList]
|
}, []);
|
||||||
);
|
|
||||||
|
|
||||||
const getProviderModels = useCallback(async (providerName: string): Promise<string[]> => {
|
const getProviderModels = useCallback(async (providerName: string): Promise<string[]> => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
const [pendingSort, setPendingSort] = useState(false);
|
const [pendingSort, setPendingSort] = useState(false);
|
||||||
const [togglingExtension, setTogglingExtension] = useState<string | null>(null);
|
const [togglingExtension, setTogglingExtension] = useState<string | null>(null);
|
||||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||||
|
const [isSessionExtensionsLoaded, setIsSessionExtensionsLoaded] = useState(false);
|
||||||
const sortTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const sortTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const { extensionsList: allExtensions } = useConfig();
|
const { extensionsList: allExtensions } = useConfig();
|
||||||
const isHubView = !sessionId;
|
const isHubView = !sessionId;
|
||||||
@@ -70,12 +71,15 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
|
|
||||||
if (response.data?.extensions) {
|
if (response.data?.extensions) {
|
||||||
setSessionExtensions(response.data.extensions);
|
setSessionExtensions(response.data.extensions);
|
||||||
|
setIsSessionExtensionsLoaded(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch session extensions:', error);
|
console.error('Failed to fetch session extensions:', error);
|
||||||
|
setIsSessionExtensionsLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setIsSessionExtensionsLoaded(false);
|
||||||
fetchExtensions();
|
fetchExtensions();
|
||||||
}, [sessionId, isOpen, refreshTrigger]);
|
}, [sessionId, isOpen, refreshTrigger]);
|
||||||
|
|
||||||
@@ -225,7 +229,7 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
|||||||
>
|
>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className="flex items-center cursor-pointer [&_svg]:size-4 text-text-default/70 hover:text-text-default hover:scale-100 hover:bg-transparent text-xs"
|
className={`flex items-center [&_svg]:size-4 text-text-default/70 hover:text-text-default hover:scale-100 hover:bg-transparent text-xs cursor-pointer ${allExtensions.length === 0 || (!isHubView && !isSessionExtensionsLoaded) ? 'invisible' : ''}`}
|
||||||
title="manage extensions"
|
title="manage extensions"
|
||||||
>
|
>
|
||||||
<Puzzle className="mr-1 h-4 w-4" />
|
<Puzzle className="mr-1 h-4 w-4" />
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Input } from '../../ui/input';
|
import { Input } from '../../ui/input';
|
||||||
import { useConfig } from '../../ConfigContext';
|
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 = () => {
|
export const ElevenLabsKeyInput = () => {
|
||||||
const [elevenLabsApiKey, setElevenLabsApiKey] = useState('');
|
const [elevenLabsApiKey, setElevenLabsApiKey] = useState('');
|
||||||
@@ -14,12 +15,16 @@ export const ElevenLabsKeyInput = () => {
|
|||||||
const loadKey = async () => {
|
const loadKey = async () => {
|
||||||
setIsLoadingKey(true);
|
setIsLoadingKey(true);
|
||||||
try {
|
try {
|
||||||
const keyExists = await read(ELEVENLABS_API_KEY, true);
|
const response = await read(ELEVENLABS_API_KEY, true);
|
||||||
if (keyExists === true) {
|
if (isSecretKeyConfigured(response)) {
|
||||||
setHasElevenLabsKey(true);
|
setHasElevenLabsKey(true);
|
||||||
|
setElevenLabsKeyCache(true);
|
||||||
|
} else {
|
||||||
|
setElevenLabsKeyCache(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking ElevenLabs API key:', error);
|
console.error('Error checking ElevenLabs API key:', error);
|
||||||
|
setElevenLabsKeyCache(false);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingKey(false);
|
setIsLoadingKey(false);
|
||||||
}
|
}
|
||||||
@@ -34,9 +39,11 @@ export const ElevenLabsKeyInput = () => {
|
|||||||
if (elevenLabsApiKeyRef.current) {
|
if (elevenLabsApiKeyRef.current) {
|
||||||
const keyToSave = elevenLabsApiKeyRef.current;
|
const keyToSave = elevenLabsApiKeyRef.current;
|
||||||
if (keyToSave.trim()) {
|
if (keyToSave.trim()) {
|
||||||
upsert(ELEVENLABS_API_KEY, keyToSave, true).catch((error) => {
|
upsert(ELEVENLABS_API_KEY, keyToSave, true)
|
||||||
console.error('Error saving ElevenLabs API key on unmount:', error);
|
.then(() => setElevenLabsKeyCache(true))
|
||||||
});
|
.catch((error) => {
|
||||||
|
console.error('Error saving ElevenLabs API key on unmount:', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -53,15 +60,13 @@ export const ElevenLabsKeyInput = () => {
|
|||||||
const saveElevenLabsKey = async () => {
|
const saveElevenLabsKey = async () => {
|
||||||
try {
|
try {
|
||||||
if (elevenLabsApiKey.trim()) {
|
if (elevenLabsApiKey.trim()) {
|
||||||
console.log('Saving ElevenLabs API key to secure storage...');
|
|
||||||
await upsert(ELEVENLABS_API_KEY, elevenLabsApiKey, true);
|
await upsert(ELEVENLABS_API_KEY, elevenLabsApiKey, true);
|
||||||
setHasElevenLabsKey(true);
|
setHasElevenLabsKey(true);
|
||||||
console.log('ElevenLabs API key saved successfully');
|
setElevenLabsKeyCache(true);
|
||||||
} else {
|
} else {
|
||||||
console.log('Removing ElevenLabs API key from secure storage...');
|
|
||||||
await upsert(ELEVENLABS_API_KEY, null, true);
|
await upsert(ELEVENLABS_API_KEY, null, true);
|
||||||
setHasElevenLabsKey(false);
|
setHasElevenLabsKey(false);
|
||||||
console.log('ElevenLabs API key removed successfully');
|
setElevenLabsKeyCache(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving ElevenLabs API key:', error);
|
console.error('Error saving ElevenLabs API key:', error);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DictationProvider } from '../../../hooks/useDictationSettings';
|
import { DictationProvider } from '../../../hooks/useDictationSettings';
|
||||||
|
import { DICTATION_PROVIDER_ELEVENLABS } from '../../../hooks/dictationConstants';
|
||||||
import { VOICE_DICTATION_ELEVENLABS_ENABLED } from '../../../updates';
|
import { VOICE_DICTATION_ELEVENLABS_ENABLED } from '../../../updates';
|
||||||
|
|
||||||
interface ProviderInfoProps {
|
interface ProviderInfoProps {
|
||||||
@@ -16,7 +17,7 @@ export const ProviderInfo = ({ provider }: ProviderInfoProps) => {
|
|||||||
configured in the Models section.
|
configured in the Models section.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{VOICE_DICTATION_ELEVENLABS_ENABLED && provider === 'elevenlabs' && (
|
{VOICE_DICTATION_ELEVENLABS_ENABLED && provider === DICTATION_PROVIDER_ELEVENLABS && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-text-muted">
|
<p className="text-xs text-text-muted">
|
||||||
Uses ElevenLabs speech-to-text API for high-quality transcription.
|
Uses ElevenLabs speech-to-text API for high-quality transcription.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings';
|
import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings';
|
||||||
|
import { DICTATION_PROVIDER_ELEVENLABS } from '../../../hooks/dictationConstants';
|
||||||
import { useConfig } from '../../ConfigContext';
|
import { useConfig } from '../../ConfigContext';
|
||||||
import { ElevenLabsKeyInput } from './ElevenLabsKeyInput';
|
import { ElevenLabsKeyInput } from './ElevenLabsKeyInput';
|
||||||
import { ProviderInfo } from './ProviderInfo';
|
import { ProviderInfo } from './ProviderInfo';
|
||||||
@@ -57,7 +58,7 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto
|
|||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'openai':
|
case 'openai':
|
||||||
return 'OpenAI Whisper';
|
return 'OpenAI Whisper';
|
||||||
case 'elevenlabs':
|
case DICTATION_PROVIDER_ELEVENLABS:
|
||||||
return 'ElevenLabs';
|
return 'ElevenLabs';
|
||||||
default:
|
default:
|
||||||
return 'None (disabled)';
|
return 'None (disabled)';
|
||||||
@@ -95,11 +96,13 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto
|
|||||||
|
|
||||||
{VOICE_DICTATION_ELEVENLABS_ENABLED && (
|
{VOICE_DICTATION_ELEVENLABS_ENABLED && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleProviderChange('elevenlabs')}
|
onClick={() => handleProviderChange(DICTATION_PROVIDER_ELEVENLABS)}
|
||||||
className="w-full px-3 py-2 text-left text-sm hover:bg-background-subtle transition-colors text-text-default last:rounded-b-md"
|
className="w-full px-3 py-2 text-left text-sm hover:bg-background-subtle transition-colors text-text-default last:rounded-b-md"
|
||||||
>
|
>
|
||||||
ElevenLabs
|
ElevenLabs
|
||||||
{settings.provider === 'elevenlabs' && <span className="float-right">✓</span>}
|
{settings.provider === DICTATION_PROVIDER_ELEVENLABS && (
|
||||||
|
<span className="float-right">✓</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -107,9 +110,8 @@ export const ProviderSelector = ({ settings, onProviderChange }: ProviderSelecto
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{VOICE_DICTATION_ELEVENLABS_ENABLED && settings.provider === 'elevenlabs' && (
|
{VOICE_DICTATION_ELEVENLABS_ENABLED &&
|
||||||
<ElevenLabsKeyInput />
|
settings.provider === DICTATION_PROVIDER_ELEVENLABS && <ElevenLabsKeyInput />}
|
||||||
)}
|
|
||||||
|
|
||||||
<ProviderInfo provider={settings.provider} />
|
<ProviderInfo provider={settings.provider} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Switch } from '../../ui/switch';
|
|||||||
import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings';
|
import { DictationProvider, DictationSettings } from '../../../hooks/useDictationSettings';
|
||||||
import {
|
import {
|
||||||
DICTATION_SETTINGS_KEY,
|
DICTATION_SETTINGS_KEY,
|
||||||
|
DICTATION_PROVIDER_ELEVENLABS,
|
||||||
getDefaultDictationSettings,
|
getDefaultDictationSettings,
|
||||||
} from '../../../hooks/dictationConstants';
|
} from '../../../hooks/dictationConstants';
|
||||||
import { useConfig } from '../../ConfigContext';
|
import { useConfig } from '../../ConfigContext';
|
||||||
@@ -28,7 +29,10 @@ export const VoiceDictationToggle = () => {
|
|||||||
loadedSettings = parsed;
|
loadedSettings = parsed;
|
||||||
|
|
||||||
// If ElevenLabs is disabled and user has it selected, reset to OpenAI
|
// 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 = {
|
||||||
...loadedSettings,
|
...loadedSettings,
|
||||||
provider: 'openai',
|
provider: 'openai',
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import { DictationSettings, DictationProvider } from './useDictationSettings';
|
|||||||
|
|
||||||
export const DICTATION_SETTINGS_KEY = 'dictation_settings';
|
export const DICTATION_SETTINGS_KEY = 'dictation_settings';
|
||||||
export const ELEVENLABS_API_KEY = 'ELEVENLABS_API_KEY';
|
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 (
|
export const getDefaultDictationSettings = async (
|
||||||
getProviders: (refresh: boolean) => Promise<Array<{ name: string; is_configured: boolean }>>
|
getProviders: (refresh: boolean) => Promise<Array<{ name: string; is_configured: boolean }>>
|
||||||
|
|||||||
@@ -3,19 +3,27 @@ import { useConfig } from '../components/ConfigContext';
|
|||||||
import {
|
import {
|
||||||
DICTATION_SETTINGS_KEY,
|
DICTATION_SETTINGS_KEY,
|
||||||
ELEVENLABS_API_KEY,
|
ELEVENLABS_API_KEY,
|
||||||
|
DICTATION_PROVIDER_ELEVENLABS,
|
||||||
getDefaultDictationSettings,
|
getDefaultDictationSettings,
|
||||||
|
isSecretKeyConfigured,
|
||||||
} from './dictationConstants';
|
} from './dictationConstants';
|
||||||
|
|
||||||
export type DictationProvider = 'openai' | 'elevenlabs' | null;
|
export type DictationProvider = 'openai' | typeof DICTATION_PROVIDER_ELEVENLABS | null;
|
||||||
|
|
||||||
export interface DictationSettings {
|
export interface DictationSettings {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
provider: DictationProvider;
|
provider: DictationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let elevenLabsKeyCache: boolean | null = null;
|
||||||
|
|
||||||
|
export const setElevenLabsKeyCache = (value: boolean) => {
|
||||||
|
elevenLabsKeyCache = value;
|
||||||
|
};
|
||||||
|
|
||||||
export const useDictationSettings = () => {
|
export const useDictationSettings = () => {
|
||||||
const [settings, setSettings] = useState<DictationSettings | null>(null);
|
const [settings, setSettings] = useState<DictationSettings | null>(null);
|
||||||
const [hasElevenLabsKey, setHasElevenLabsKey] = useState<boolean>(false);
|
const [hasElevenLabsKey, setHasElevenLabsKey] = useState<boolean>(elevenLabsKeyCache ?? false);
|
||||||
const { read, getProviders } = useConfig();
|
const { read, getProviders } = useConfig();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -23,22 +31,27 @@ export const useDictationSettings = () => {
|
|||||||
// Load settings from localStorage
|
// Load settings from localStorage
|
||||||
const saved = localStorage.getItem(DICTATION_SETTINGS_KEY);
|
const saved = localStorage.getItem(DICTATION_SETTINGS_KEY);
|
||||||
|
|
||||||
|
let currentSettings: DictationSettings;
|
||||||
if (saved) {
|
if (saved) {
|
||||||
const parsedSettings = JSON.parse(saved);
|
currentSettings = JSON.parse(saved);
|
||||||
setSettings(parsedSettings);
|
|
||||||
} else {
|
} else {
|
||||||
const defaultSettings = await getDefaultDictationSettings(getProviders);
|
currentSettings = await getDefaultDictationSettings(getProviders);
|
||||||
setSettings(defaultSettings);
|
|
||||||
}
|
}
|
||||||
|
setSettings(currentSettings);
|
||||||
// Load ElevenLabs API key from storage (non-secret for frontend access)
|
if (
|
||||||
try {
|
currentSettings.provider === DICTATION_PROVIDER_ELEVENLABS &&
|
||||||
const keyExists = await read(ELEVENLABS_API_KEY, true);
|
elevenLabsKeyCache === null
|
||||||
if (keyExists === true) {
|
) {
|
||||||
setHasElevenLabsKey(true);
|
try {
|
||||||
|
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 checking ElevenLabs API key:', error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('[useDictationSettings] Error loading ElevenLabs API key:', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useState, useRef, useCallback, useEffect } from 'react';
|
|||||||
import { useConfig } from '../components/ConfigContext';
|
import { useConfig } from '../components/ConfigContext';
|
||||||
import { getApiUrl } from '../config';
|
import { getApiUrl } from '../config';
|
||||||
import { useDictationSettings } from './useDictationSettings';
|
import { useDictationSettings } from './useDictationSettings';
|
||||||
|
import { DICTATION_PROVIDER_ELEVENLABS } from './dictationConstants';
|
||||||
import { safeJsonParse } from '../utils/conversionUtils';
|
import { safeJsonParse } from '../utils/conversionUtils';
|
||||||
|
|
||||||
interface UseWhisperOptions {
|
interface UseWhisperOptions {
|
||||||
@@ -77,7 +78,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
|
|||||||
case 'openai':
|
case 'openai':
|
||||||
setCanUseDictation(hasOpenAIKey);
|
setCanUseDictation(hasOpenAIKey);
|
||||||
break;
|
break;
|
||||||
case 'elevenlabs':
|
case DICTATION_PROVIDER_ELEVENLABS:
|
||||||
setCanUseDictation(hasElevenLabsKey);
|
setCanUseDictation(hasElevenLabsKey);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -180,7 +181,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
|
|||||||
case 'openai':
|
case 'openai':
|
||||||
endpoint = '/audio/transcribe';
|
endpoint = '/audio/transcribe';
|
||||||
break;
|
break;
|
||||||
case 'elevenlabs':
|
case DICTATION_PROVIDER_ELEVENLABS:
|
||||||
endpoint = '/audio/transcribe/elevenlabs';
|
endpoint = '/audio/transcribe/elevenlabs';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -373,5 +374,6 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
|
|||||||
stopRecording,
|
stopRecording,
|
||||||
recordingDuration,
|
recordingDuration,
|
||||||
estimatedSize,
|
estimatedSize,
|
||||||
|
dictationSettings,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user