mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
✨ feat: auto send text slider (#3312)
* feat: convert main component to float * feat: convert the remaining components * feat: use `recoilState` instead of `recoilValue` * feat: replaced `AutoSendTextSwitch` to `AutoSendTextSelector` * feat: use `autoSendText` in the `useSpeechToTextExternal` hook * fix: `autoSendText` timeout
This commit is contained in:
parent
d5d188eebf
commit
d5782ac66c
14 changed files with 737 additions and 749 deletions
|
|
@ -38,8 +38,8 @@ const ChatForm = ({ index = 0 }) => {
|
|||
const submitButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const SpeechToText = useRecoilValue(store.speechToText);
|
||||
const TextToSpeech = useRecoilValue(store.textToSpeech);
|
||||
const SpeechToText = useRecoilState<boolean>(store.speechToText);
|
||||
const TextToSpeech = useRecoilState<boolean>(store.textToSpeech);
|
||||
const automaticPlayback = useRecoilValue(store.automaticPlayback);
|
||||
|
||||
const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index));
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ export default function ConversationModeSwitch({
|
|||
}) {
|
||||
const localize = useLocalize();
|
||||
const [conversationMode, setConversationMode] = useRecoilState<boolean>(store.conversationMode);
|
||||
const [speechToText] = useRecoilState<boolean>(store.speechToText);
|
||||
const [textToSpeech] = useRecoilState<boolean>(store.textToSpeech);
|
||||
const [, setAutoSendText] = useRecoilState<boolean>(store.autoSendText);
|
||||
const speechToText = useRecoilState<boolean>(store.speechToText);
|
||||
const textToSpeech = useRecoilState<boolean>(store.textToSpeech);
|
||||
const [, setAutoSendText] = useRecoilState(store.autoSendText);
|
||||
const [, setDecibelValue] = useRecoilState(store.decibelValue);
|
||||
const [, setAutoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setAutoTranscribeAudio(value);
|
||||
setAutoSendText(value);
|
||||
setAutoSendText(3);
|
||||
setDecibelValue(-45);
|
||||
setConversationMode(value);
|
||||
if (onCheckedChange) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { cn, defaultTextProps, optionText } from '~/utils/';
|
||||
import { Slider, InputNumber } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
export default function AutoSendTextSelector() {
|
||||
const localize = useLocalize();
|
||||
|
||||
const speechToText = useRecoilState<boolean>(store.speechToText);
|
||||
const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_auto_send_text')}</div>
|
||||
<div className="w-2" />
|
||||
<small className="opacity-40">({localize('com_nav_auto_send_text_disabled')})</small>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Slider
|
||||
value={[autoSendText ?? -1]}
|
||||
onValueChange={(value) => setAutoSendText(value[0])}
|
||||
doubleClickHandler={() => setAutoSendText(-1)}
|
||||
min={-1}
|
||||
max={60}
|
||||
step={1}
|
||||
className="ml-4 flex h-4 w-24"
|
||||
disabled={!speechToText}
|
||||
/>
|
||||
<div className="w-2" />
|
||||
<InputNumber
|
||||
value={`${autoSendText} s`}
|
||||
disabled={!speechToText}
|
||||
onChange={(value) => setAutoSendText(value ? value[0] : 0)}
|
||||
min={-1}
|
||||
max={60}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
export default function AutoSendTextSwitch({
|
||||
onCheckedChange,
|
||||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [autoSendText, setAutoSendText] = useRecoilState<boolean>(store.autoSendText);
|
||||
const [SpeechToText] = useRecoilState<boolean>(store.speechToText);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setAutoSendText(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_auto_send_text')}</div>
|
||||
<Switch
|
||||
id="AutoSendText"
|
||||
checked={autoSendText}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="AutoSendText"
|
||||
disabled={!SpeechToText}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ export default function AutoTranscribeAudioSwitch({
|
|||
const [autoTranscribeAudio, setAutoTranscribeAudio] = useRecoilState<boolean>(
|
||||
store.autoTranscribeAudio,
|
||||
);
|
||||
const [speechToText] = useRecoilState<boolean>(store.speechToText);
|
||||
const speechToText = useRecoilState<boolean>(store.speechToText);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setAutoTranscribeAudio(value);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Slider, InputNumber } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
|
@ -7,7 +7,7 @@ import { cn, defaultTextProps, optionText } from '~/utils/';
|
|||
|
||||
export default function DecibelSelector() {
|
||||
const localize = useLocalize();
|
||||
const speechToText = useRecoilValue(store.speechToText);
|
||||
const speechToText = useRecoilState<boolean>(store.speechToText);
|
||||
const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { render, fireEvent } from 'test/layout-test-utils';
|
||||
import AutoSendTextSwitch from '../AutoSendTextSwitch';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
describe('AutoSendTextSwitch', () => {
|
||||
/**
|
||||
* Mock function to set the auto-send-text state.
|
||||
*/
|
||||
let mockSetAutoSendText: jest.Mock<void, [boolean]> | ((value: boolean) => void) | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSetAutoSendText = jest.fn();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<RecoilRoot>
|
||||
<AutoSendTextSwitch />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByTestId('AutoSendText')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onCheckedChange when the switch is toggled', () => {
|
||||
const { getByTestId } = render(
|
||||
<RecoilRoot>
|
||||
<AutoSendTextSwitch onCheckedChange={mockSetAutoSendText} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
const switchElement = getByTestId('AutoSendText');
|
||||
fireEvent.click(switchElement);
|
||||
|
||||
expect(mockSetAutoSendText).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export { default as AutoSendTextSwitch } from './AutoSendTextSwitch';
|
||||
export { default as AutoSendTextSelector } from './AutoSendTextSelector';
|
||||
export { default as SpeechToTextSwitch } from './SpeechToTextSwitch';
|
||||
export { default as EngineSTTDropdown } from './EngineSTTDropdown';
|
||||
export { default as DecibelSelector } from './DecibelSelector';
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
AutoTranscribeAudioSwitch,
|
||||
LanguageSTTDropdown,
|
||||
SpeechToTextSwitch,
|
||||
AutoSendTextSwitch,
|
||||
AutoSendTextSelector,
|
||||
EngineSTTDropdown,
|
||||
DecibelSelector,
|
||||
} from './STT';
|
||||
|
|
@ -220,8 +220,8 @@ function Speech() {
|
|||
<DecibelSelector />
|
||||
</div>
|
||||
)}
|
||||
<div className="border-b last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutoSendTextSwitch />
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutoSendTextSelector />
|
||||
</div>
|
||||
<div className="h-px bg-black/20 bg-white/20" role="none" />
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void
|
|||
const { externalSpeechToText } = useGetAudioSettings();
|
||||
const [speechToText] = useRecoilState<boolean>(store.speechToText);
|
||||
const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
|
||||
const [autoSendText] = useRecoilState<boolean>(store.autoSendText);
|
||||
const [autoSendText] = useRecoilState(store.autoSendText);
|
||||
const [text, setText] = useState<string>('');
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const [permission, setPermission] = useState(false);
|
||||
|
|
@ -27,10 +27,11 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void
|
|||
const extractedText = data.text;
|
||||
setText(extractedText);
|
||||
setIsRequestBeingMade(false);
|
||||
if (autoSendText && speechToText && extractedText.length > 0) {
|
||||
|
||||
if (autoSendText > -1 && speechToText && extractedText.length > 0) {
|
||||
setTimeout(() => {
|
||||
onTranscriptionComplete(extractedText);
|
||||
}, 3000);
|
||||
}, autoSendText * 1000);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
|
|
|
|||
|
|
@ -628,7 +628,8 @@ export default {
|
|||
com_nav_delete_warning: 'WARNING: This will permanently delete your account.',
|
||||
com_nav_delete_data_info: 'All your data will be deleted.',
|
||||
com_nav_conversation_mode: 'Conversation Mode',
|
||||
com_nav_auto_send_text: 'Auto send text (after 3 sec)',
|
||||
com_nav_auto_send_text: 'Auto send text',
|
||||
com_nav_auto_send_text_disabled: 'set -1 to disable',
|
||||
com_nav_auto_transcribe_audio: 'Auto transcribe audio',
|
||||
com_nav_db_sensitivity: 'Decibel sensitivity',
|
||||
com_nav_playback_rate: 'Audio Playback Rate',
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -45,7 +45,7 @@ const localStorageAtoms = {
|
|||
languageSTT: atomWithLocalStorage('languageSTT', ''),
|
||||
autoTranscribeAudio: atomWithLocalStorage('autoTranscribeAudio', false),
|
||||
decibelValue: atomWithLocalStorage('decibelValue', -45),
|
||||
autoSendText: atomWithLocalStorage('autoSendText', false),
|
||||
autoSendText: atomWithLocalStorage('autoSendText', -1),
|
||||
|
||||
textToSpeech: atomWithLocalStorage('textToSpeech', true),
|
||||
engineTTS: atomWithLocalStorage('engineTTS', 'browser'),
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ const speechTab = z
|
|||
languageSTT: z.string().optional(),
|
||||
autoTranscribeAudio: z.boolean().optional(),
|
||||
decibelValue: z.number().optional(),
|
||||
autoSendText: z.boolean().optional(),
|
||||
autoSendText: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue