mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
* ✨ feat: improve Nav/Conversations/Convo/NewChat component performance * ✨ feat: implement cursor-based pagination for conversations API * 🔧 refactor: remove createdAt from conversation selection in API and type definitions * 🔧 refactor: include createdAt in conversation selection and update related types * ✨ fix: search functionality and bugs with loadMoreConversations * feat: move ArchivedChats to cursor and DataTable standard * 🔧 refactor: add InfiniteQueryObserverResult type import in Nav component * feat: enhance conversation listing with pagination, sorting, and search capabilities * 🔧 refactor: remove unnecessary comment regarding lodash/debounce in ArchivedChatsTable * 🔧 refactor: remove unused translation keys for archived chats and search results * 🔧 fix: Archived Chats, Delete Convo, Duplicate Convo * 🔧 refactor: improve conversation components with layout adjustments and new translations * 🔧 refactor: simplify archive conversation mutation and improve unarchive handling; fix: update fork mutation * 🔧 refactor: decode search query parameter in conversation route; improve error handling in unarchive mutation; clean up DataTable component styles * 🔧 refactor: remove unused translation key for empty archived chats * 🚀 fix: `archivedConversation` query key not updated correctly while archiving * 🧠 feat: Bedrock Anthropic Reasoning & Update Endpoint Handling (#6163) * feat: Add thinking and thinkingBudget parameters for Bedrock Anthropic models * chore: Update @librechat/agents to version 2.1.8 * refactor: change region order in params * refactor: Add maxTokens parameter to conversation preset schema * refactor: Update agent client to use bedrockInputSchema and improve error handling for model parameters * refactor: streamline/optimize llmConfig initialization and saving for bedrock * fix: ensure config titleModel is used for all endpoints * refactor: enhance OpenAIClient and agent initialization to support endpoint checks for OpenRouter * chore: bump @google/generative-ai * ✨ feat: improve Nav/Conversations/Convo/NewChat component performance * 🔧 refactor: remove unnecessary comment regarding lodash/debounce in ArchivedChatsTable * 🔧 refactor: update translation keys for clarity; simplify conversation query parameters and improve sorting functionality in SharedLinks component * 🔧 refactor: optimize conversation loading logic and improve search handling in Nav component * fix: package-lock * fix: package-lock 2 * fix: package lock 3 * refactor: remove unused utility files and exports to clean up the codebase * refactor: remove i18n and useAuthRedirect modules to streamline codebase * refactor: optimize Conversations component and remove unused ToggleContext * refactor(Convo): add RenameForm and ConvoLink components; enhance Conversations component with responsive design * fix: add missing @azure/storage-blob dependency in package.json * refactor(Search): add error handling with toast notification for search errors * refactor: make createdAt and updatedAt fields of tConvoUpdateSchema less restrictive if timestamps are missing * chore: update @azure/storage-blob dependency to version 12.27.0, ensure package-lock is correct * refactor(Search): improve conversation handling server side * fix: eslint warning and errors * refactor(Search): improved search loading state and overall UX * Refactors conversation cache management Centralizes conversation mutation logic into dedicated utility functions for adding, updating, and removing conversations from query caches. Improves reliability and maintainability by: - Consolidating duplicate cache manipulation code - Adding type safety for infinite query data structures - Implementing consistent cache update patterns across all conversation operations - Removing obsolete conversation helper functions in favor of standardized utilities * fix: conversation handling and SSE event processing - Optimizes conversation state management with useMemo and proper hook ordering - Improves SSE event handler documentation and error handling - Adds reset guard flag for conversation changes - Removes redundant navigation call - Cleans up cursor handling logic and document structure Improves code maintainability and prevents potential race conditions in conversation state updates * refactor: add type for SearchBar `onChange` * fix: type tags * style: rounded to xl all Header buttons * fix: activeConvo in Convo not working * style(Bookmarks): improved UI * a11y(AccountSettings): fixed hover style not visible when using light theme * style(SettingsTabs): improved tab switchers and dropdowns * feat: add translations keys for Speech * chore: fix package-lock * fix(mutations): legacy import after rebase * feat: refactor conversation navigation for accessibility * fix(search): convo and message create/update date not returned * fix(search): show correct iconURL and endpoint for searched messages * fix: small UI improvements * chore: console.log cleanup * chore: fix tests * fix(ChatForm): improve conversation ID handling and clean up useMemo dependencies * chore: improve typing * chore: improve typing * fix(useSSE): clear conversation ID on submission to prevent draft restoration * refactor(OpenAIClient): clean up abort handler * refactor(abortMiddleware): change handleAbort to use function expression * feat: add PENDING_CONVO constant and update conversation ID checks * fix: final event handling on abort * fix: improve title sync and query cache sync on final event * fix: prevent overwriting cached conversation data if it already exists --------- Co-authored-by: Danny Avila <danny@librechat.ai>
235 lines
8.6 KiB
TypeScript
235 lines
8.6 KiB
TypeScript
import { useRecoilState } from 'recoil';
|
|
import * as Tabs from '@radix-ui/react-tabs';
|
|
import { Lightbulb, Cog } from 'lucide-react';
|
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
import { useGetCustomConfigSpeechQuery } from 'librechat-data-provider/react-query';
|
|
import {
|
|
CloudBrowserVoicesSwitch,
|
|
AutomaticPlaybackSwitch,
|
|
TextToSpeechSwitch,
|
|
EngineTTSDropdown,
|
|
CacheTTSSwitch,
|
|
VoiceDropdown,
|
|
PlaybackRate,
|
|
} from './TTS';
|
|
import {
|
|
AutoTranscribeAudioSwitch,
|
|
LanguageSTTDropdown,
|
|
SpeechToTextSwitch,
|
|
AutoSendTextSelector,
|
|
EngineSTTDropdown,
|
|
DecibelSelector,
|
|
} from './STT';
|
|
import { useOnClickOutside, useMediaQuery, useLocalize } from '~/hooks';
|
|
import ConversationModeSwitch from './ConversationModeSwitch';
|
|
import { cn, logger } from '~/utils';
|
|
import store from '~/store';
|
|
|
|
function Speech() {
|
|
const localize = useLocalize();
|
|
|
|
const [confirmClear, setConfirmClear] = useState(false);
|
|
const { data } = useGetCustomConfigSpeechQuery();
|
|
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
|
|
|
const [sttExternal, setSttExternal] = useState(false);
|
|
const [ttsExternal, setTtsExternal] = useState(false);
|
|
const [advancedMode, setAdvancedMode] = useRecoilState(store.advancedMode);
|
|
const [autoTranscribeAudio, setAutoTranscribeAudio] = useRecoilState(store.autoTranscribeAudio);
|
|
const [conversationMode, setConversationMode] = useRecoilState(store.conversationMode);
|
|
const [speechToText, setSpeechToText] = useRecoilState(store.speechToText);
|
|
const [textToSpeech, setTextToSpeech] = useRecoilState(store.textToSpeech);
|
|
const [cacheTTS, setCacheTTS] = useRecoilState(store.cacheTTS);
|
|
const [engineSTT, setEngineSTT] = useRecoilState<string>(store.engineSTT);
|
|
const [languageSTT, setLanguageSTT] = useRecoilState<string>(store.languageSTT);
|
|
const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue);
|
|
const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText);
|
|
const [engineTTS, setEngineTTS] = useRecoilState<string>(store.engineTTS);
|
|
const [voice, setVoice] = useRecoilState(store.voice);
|
|
const [cloudBrowserVoices, setCloudBrowserVoices] = useRecoilState<boolean>(
|
|
store.cloudBrowserVoices,
|
|
);
|
|
const [languageTTS, setLanguageTTS] = useRecoilState<string>(store.languageTTS);
|
|
const [automaticPlayback, setAutomaticPlayback] = useRecoilState(store.automaticPlayback);
|
|
const [playbackRate, setPlaybackRate] = useRecoilState(store.playbackRate);
|
|
|
|
const updateSetting = useCallback(
|
|
(key: string, newValue: string | number) => {
|
|
const settings = {
|
|
sttExternal: { value: sttExternal, setFunc: setSttExternal },
|
|
ttsExternal: { value: ttsExternal, setFunc: setTtsExternal },
|
|
conversationMode: { value: conversationMode, setFunc: setConversationMode },
|
|
advancedMode: { value: advancedMode, setFunc: setAdvancedMode },
|
|
speechToText: { value: speechToText, setFunc: setSpeechToText },
|
|
textToSpeech: { value: textToSpeech, setFunc: setTextToSpeech },
|
|
cacheTTS: { value: cacheTTS, setFunc: setCacheTTS },
|
|
engineSTT: { value: engineSTT, setFunc: setEngineSTT },
|
|
languageSTT: { value: languageSTT, setFunc: setLanguageSTT },
|
|
autoTranscribeAudio: { value: autoTranscribeAudio, setFunc: setAutoTranscribeAudio },
|
|
decibelValue: { value: decibelValue, setFunc: setDecibelValue },
|
|
autoSendText: { value: autoSendText, setFunc: setAutoSendText },
|
|
engineTTS: { value: engineTTS, setFunc: setEngineTTS },
|
|
voice: { value: voice, setFunc: setVoice },
|
|
cloudBrowserVoices: { value: cloudBrowserVoices, setFunc: setCloudBrowserVoices },
|
|
languageTTS: { value: languageTTS, setFunc: setLanguageTTS },
|
|
automaticPlayback: { value: automaticPlayback, setFunc: setAutomaticPlayback },
|
|
playbackRate: { value: playbackRate, setFunc: setPlaybackRate },
|
|
};
|
|
|
|
if (
|
|
(settings[key].value !== newValue || settings[key].value === newValue || !settings[key]) &&
|
|
settings[key].value === 'sttExternal' &&
|
|
settings[key].value === 'ttsExternal'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const setting = settings[key];
|
|
setting.setFunc(newValue);
|
|
},
|
|
[
|
|
sttExternal,
|
|
ttsExternal,
|
|
conversationMode,
|
|
advancedMode,
|
|
speechToText,
|
|
textToSpeech,
|
|
cacheTTS,
|
|
engineSTT,
|
|
languageSTT,
|
|
autoTranscribeAudio,
|
|
decibelValue,
|
|
autoSendText,
|
|
engineTTS,
|
|
voice,
|
|
cloudBrowserVoices,
|
|
languageTTS,
|
|
automaticPlayback,
|
|
playbackRate,
|
|
setSttExternal,
|
|
setTtsExternal,
|
|
setConversationMode,
|
|
setAdvancedMode,
|
|
setSpeechToText,
|
|
setTextToSpeech,
|
|
setCacheTTS,
|
|
setEngineSTT,
|
|
setLanguageSTT,
|
|
setAutoTranscribeAudio,
|
|
setDecibelValue,
|
|
setAutoSendText,
|
|
setEngineTTS,
|
|
setVoice,
|
|
setCloudBrowserVoices,
|
|
setLanguageTTS,
|
|
setAutomaticPlayback,
|
|
setPlaybackRate,
|
|
],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (data && data.message !== 'not_found') {
|
|
Object.entries(data).forEach(([key, value]) => {
|
|
updateSetting(key, value);
|
|
});
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [data]);
|
|
|
|
logger.log({ sttExternal, ttsExternal });
|
|
|
|
const contentRef = useRef(null);
|
|
useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []);
|
|
|
|
return (
|
|
<Tabs.Root
|
|
defaultValue={'simple'}
|
|
orientation="horizontal"
|
|
value={advancedMode ? 'advanced' : 'simple'}
|
|
>
|
|
<div className="sticky -top-1 z-50 mb-4 bg-white dark:bg-gray-700">
|
|
<Tabs.List className="flex justify-center bg-background">
|
|
<Tabs.Trigger
|
|
onClick={() => setAdvancedMode(false)}
|
|
className={cn(
|
|
'group m-1 flex items-center justify-center gap-2 bg-transparent px-4 py-2 text-sm text-text-secondary transition-all duration-200 ease-in-out radix-state-active:bg-secondary radix-state-active:text-foreground radix-state-active:shadow-lg',
|
|
isSmallScreen ? 'flex-row rounded-lg' : 'rounded-xl',
|
|
'w-full',
|
|
)}
|
|
value="simple"
|
|
style={{ userSelect: 'none' }}
|
|
>
|
|
<Lightbulb />
|
|
{localize('com_ui_simple')}
|
|
</Tabs.Trigger>
|
|
<Tabs.Trigger
|
|
onClick={() => setAdvancedMode(true)}
|
|
className={cn(
|
|
'group m-1 flex items-center justify-center gap-2 bg-transparent px-4 py-2 text-sm text-text-secondary transition-all duration-200 ease-in-out radix-state-active:bg-secondary radix-state-active:text-foreground radix-state-active:shadow-lg',
|
|
isSmallScreen ? 'flex-row rounded-lg' : 'rounded-xl',
|
|
'w-full',
|
|
)}
|
|
value="advanced"
|
|
style={{ userSelect: 'none' }}
|
|
>
|
|
<Cog />
|
|
{localize('com_ui_advanced')}
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
</div>
|
|
|
|
<Tabs.Content value={'simple'}>
|
|
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
|
<SpeechToTextSwitch />
|
|
<EngineSTTDropdown external={sttExternal} />
|
|
<LanguageSTTDropdown />
|
|
<div className="h-px bg-border-medium" role="none" />
|
|
<TextToSpeechSwitch />
|
|
<EngineTTSDropdown external={ttsExternal} />
|
|
<VoiceDropdown />
|
|
</div>
|
|
</Tabs.Content>
|
|
|
|
<Tabs.Content value={'advanced'}>
|
|
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
|
<ConversationModeSwitch />
|
|
<div className="mt-2 h-px bg-border-medium" role="none" />
|
|
<SpeechToTextSwitch />
|
|
|
|
<EngineSTTDropdown external={sttExternal} />
|
|
|
|
<LanguageSTTDropdown />
|
|
<div className="pb-2">
|
|
<AutoTranscribeAudioSwitch />
|
|
</div>
|
|
{autoTranscribeAudio && (
|
|
<div className="pb-2">
|
|
<DecibelSelector />
|
|
</div>
|
|
)}
|
|
<div className="pb-2">
|
|
<AutoSendTextSelector />
|
|
</div>
|
|
<div className="h-px bg-border-medium" role="none" />
|
|
<div className="pb-3">
|
|
<TextToSpeechSwitch />
|
|
</div>
|
|
<AutomaticPlaybackSwitch />
|
|
<EngineTTSDropdown external={ttsExternal} />
|
|
<VoiceDropdown />
|
|
{engineTTS === 'browser' && (
|
|
<div className="pb-2">
|
|
<CloudBrowserVoicesSwitch />
|
|
</div>
|
|
)}
|
|
<div className="pb-2">
|
|
<PlaybackRate />
|
|
</div>
|
|
<CacheTTSSwitch />
|
|
</div>
|
|
</Tabs.Content>
|
|
</Tabs.Root>
|
|
);
|
|
}
|
|
|
|
export default React.memo(Speech);
|