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:
Marco Beretta 2024-07-17 16:07:11 +02:00 committed by GitHub
parent d5d188eebf
commit d5782ac66c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 737 additions and 749 deletions

View file

@ -38,8 +38,8 @@ const ChatForm = ({ index = 0 }) => {
const submitButtonRef = useRef<HTMLButtonElement>(null); const submitButtonRef = useRef<HTMLButtonElement>(null);
const textAreaRef = useRef<HTMLTextAreaElement | null>(null); const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
const SpeechToText = useRecoilValue(store.speechToText); const SpeechToText = useRecoilState<boolean>(store.speechToText);
const TextToSpeech = useRecoilValue(store.textToSpeech); const TextToSpeech = useRecoilState<boolean>(store.textToSpeech);
const automaticPlayback = useRecoilValue(store.automaticPlayback); const automaticPlayback = useRecoilValue(store.automaticPlayback);
const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index)); const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index));

View file

@ -10,15 +10,15 @@ export default function ConversationModeSwitch({
}) { }) {
const localize = useLocalize(); const localize = useLocalize();
const [conversationMode, setConversationMode] = useRecoilState<boolean>(store.conversationMode); const [conversationMode, setConversationMode] = useRecoilState<boolean>(store.conversationMode);
const [speechToText] = useRecoilState<boolean>(store.speechToText); const speechToText = useRecoilState<boolean>(store.speechToText);
const [textToSpeech] = useRecoilState<boolean>(store.textToSpeech); const textToSpeech = useRecoilState<boolean>(store.textToSpeech);
const [, setAutoSendText] = useRecoilState<boolean>(store.autoSendText); const [, setAutoSendText] = useRecoilState(store.autoSendText);
const [, setDecibelValue] = useRecoilState(store.decibelValue); const [, setDecibelValue] = useRecoilState(store.decibelValue);
const [, setAutoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio); const [, setAutoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
const handleCheckedChange = (value: boolean) => { const handleCheckedChange = (value: boolean) => {
setAutoTranscribeAudio(value); setAutoTranscribeAudio(value);
setAutoSendText(value); setAutoSendText(3);
setDecibelValue(-45); setDecibelValue(-45);
setConversationMode(value); setConversationMode(value);
if (onCheckedChange) { if (onCheckedChange) {

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -12,7 +12,7 @@ export default function AutoTranscribeAudioSwitch({
const [autoTranscribeAudio, setAutoTranscribeAudio] = useRecoilState<boolean>( const [autoTranscribeAudio, setAutoTranscribeAudio] = useRecoilState<boolean>(
store.autoTranscribeAudio, store.autoTranscribeAudio,
); );
const [speechToText] = useRecoilState<boolean>(store.speechToText); const speechToText = useRecoilState<boolean>(store.speechToText);
const handleCheckedChange = (value: boolean) => { const handleCheckedChange = (value: boolean) => {
setAutoTranscribeAudio(value); setAutoTranscribeAudio(value);

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState } from 'recoil';
import { Slider, InputNumber } from '~/components/ui'; import { Slider, InputNumber } from '~/components/ui';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import store from '~/store'; import store from '~/store';
@ -7,7 +7,7 @@ import { cn, defaultTextProps, optionText } from '~/utils/';
export default function DecibelSelector() { export default function DecibelSelector() {
const localize = useLocalize(); const localize = useLocalize();
const speechToText = useRecoilValue(store.speechToText); const speechToText = useRecoilState<boolean>(store.speechToText);
const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue); const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue);
return ( return (

View file

@ -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);
});
});

View file

@ -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 SpeechToTextSwitch } from './SpeechToTextSwitch';
export { default as EngineSTTDropdown } from './EngineSTTDropdown'; export { default as EngineSTTDropdown } from './EngineSTTDropdown';
export { default as DecibelSelector } from './DecibelSelector'; export { default as DecibelSelector } from './DecibelSelector';

View file

@ -20,7 +20,7 @@ import {
AutoTranscribeAudioSwitch, AutoTranscribeAudioSwitch,
LanguageSTTDropdown, LanguageSTTDropdown,
SpeechToTextSwitch, SpeechToTextSwitch,
AutoSendTextSwitch, AutoSendTextSelector,
EngineSTTDropdown, EngineSTTDropdown,
DecibelSelector, DecibelSelector,
} from './STT'; } from './STT';
@ -220,8 +220,8 @@ function Speech() {
<DecibelSelector /> <DecibelSelector />
</div> </div>
)} )}
<div className="border-b last-of-type:border-b-0 dark:border-gray-700"> <div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoSendTextSwitch /> <AutoSendTextSelector />
</div> </div>
<div className="h-px bg-black/20 bg-white/20" role="none" /> <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"> <div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">

View file

@ -10,7 +10,7 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void
const { externalSpeechToText } = useGetAudioSettings(); const { externalSpeechToText } = useGetAudioSettings();
const [speechToText] = useRecoilState<boolean>(store.speechToText); const [speechToText] = useRecoilState<boolean>(store.speechToText);
const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio); const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
const [autoSendText] = useRecoilState<boolean>(store.autoSendText); const [autoSendText] = useRecoilState(store.autoSendText);
const [text, setText] = useState<string>(''); const [text, setText] = useState<string>('');
const [isListening, setIsListening] = useState(false); const [isListening, setIsListening] = useState(false);
const [permission, setPermission] = useState(false); const [permission, setPermission] = useState(false);
@ -27,10 +27,11 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void
const extractedText = data.text; const extractedText = data.text;
setText(extractedText); setText(extractedText);
setIsRequestBeingMade(false); setIsRequestBeingMade(false);
if (autoSendText && speechToText && extractedText.length > 0) {
if (autoSendText > -1 && speechToText && extractedText.length > 0) {
setTimeout(() => { setTimeout(() => {
onTranscriptionComplete(extractedText); onTranscriptionComplete(extractedText);
}, 3000); }, autoSendText * 1000);
} }
}, },
onError: () => { onError: () => {

View file

@ -628,7 +628,8 @@ export default {
com_nav_delete_warning: 'WARNING: This will permanently delete your account.', com_nav_delete_warning: 'WARNING: This will permanently delete your account.',
com_nav_delete_data_info: 'All your data will be deleted.', com_nav_delete_data_info: 'All your data will be deleted.',
com_nav_conversation_mode: 'Conversation Mode', 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_auto_transcribe_audio: 'Auto transcribe audio',
com_nav_db_sensitivity: 'Decibel sensitivity', com_nav_db_sensitivity: 'Decibel sensitivity',
com_nav_playback_rate: 'Audio Playback Rate', com_nav_playback_rate: 'Audio Playback Rate',

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,7 @@ const localStorageAtoms = {
languageSTT: atomWithLocalStorage('languageSTT', ''), languageSTT: atomWithLocalStorage('languageSTT', ''),
autoTranscribeAudio: atomWithLocalStorage('autoTranscribeAudio', false), autoTranscribeAudio: atomWithLocalStorage('autoTranscribeAudio', false),
decibelValue: atomWithLocalStorage('decibelValue', -45), decibelValue: atomWithLocalStorage('decibelValue', -45),
autoSendText: atomWithLocalStorage('autoSendText', false), autoSendText: atomWithLocalStorage('autoSendText', -1),
textToSpeech: atomWithLocalStorage('textToSpeech', true), textToSpeech: atomWithLocalStorage('textToSpeech', true),
engineTTS: atomWithLocalStorage('engineTTS', 'browser'), engineTTS: atomWithLocalStorage('engineTTS', 'browser'),

View file

@ -304,7 +304,7 @@ const speechTab = z
languageSTT: z.string().optional(), languageSTT: z.string().optional(),
autoTranscribeAudio: z.boolean().optional(), autoTranscribeAudio: z.boolean().optional(),
decibelValue: z.number().optional(), decibelValue: z.number().optional(),
autoSendText: z.boolean().optional(), autoSendText: z.number().optional(),
}), }),
) )
.optional(), .optional(),