Perfect Composer Mic shortcuts, focus, logic.

This commit is contained in:
Enrico Ros
2024-08-02 16:36:59 -07:00
parent d06f9e17e1
commit 421c586adb
4 changed files with 34 additions and 7 deletions
+5 -1
View File
@@ -114,6 +114,7 @@ function ShortcutItem(props: { shortcut: ShortcutObject }) {
{/*{!!props.shortcut.altForNonMac && <ShortcutKey onClick={handleClicked}>{_platformAwareModifier('Alt')}</ShortcutKey>}*/}
<ShortcutKey onClick={handleClicked}>{props.shortcut.key === 'Escape' ? 'Esc' : props.shortcut.key.toUpperCase()}</ShortcutKey>
&nbsp;<Typography level='body-xs'>{props.shortcut.description}</Typography>
{props.shortcut.endDecoratorIcon && <props.shortcut.endDecoratorIcon sx={{ fontSize: 'md' }} />}
</ShortcutContainer>
);
}
@@ -128,7 +129,10 @@ export function StatusBar() {
// external state
const labsShowShortcutBar = useUXLabsStore(state => state.labsShowShortcutBar);
const shortcuts = useGlobalShortcutsStore(useShallow(state => {
const visibleShortcuts = !labsShowShortcutBar ? [] : state.getAllShortcuts().filter(shortcut => !!shortcut.description);
let visibleShortcuts = !labsShowShortcutBar ? [] : state.getAllShortcuts().filter(shortcut => !!shortcut.description);
const maxLevel = Math.max(...visibleShortcuts.map(s => s.level ?? 0));
if (maxLevel > 0)
visibleShortcuts = visibleShortcuts.filter(s => s.level === maxLevel);
visibleShortcuts.sort((a, b) => {
// if they don't have a 'shift', they are sorted first
if (a.shift !== b.shift)
+23 -5
View File
@@ -42,7 +42,7 @@ import { supportsScreenCapture } from '~/common/util/screenCaptureUtils';
import { useAppStateStore } from '~/common/state/store-appstate';
import { useChatOverlayStore } from '~/common/chats/store-chat-overlay';
import { useDebouncer } from '~/common/components/useDebouncer';
import { useGlobalShortcuts } from '~/common/components/shortcuts/useGlobalShortcuts';
import { ShortcutKey, ShortcutObject, useGlobalShortcuts } from '~/common/components/shortcuts/useGlobalShortcuts';
import { useUICounter, useUIPreferencesStore } from '~/common/state/store-ui';
import { useUXLabsStore } from '~/common/state/store-ux-labs';
@@ -419,10 +419,28 @@ export function Composer(props: {
// useMediaSessionCallbacks({ play: toggleRecognition, pause: toggleRecognition });
useGlobalShortcuts('Composer', React.useMemo(() => [
...(browserSpeechRecognitionCapability().mayWork ? [{ key: 'm', ctrl: true, action: () => toggleRecognition(true), description: recognitionState.isActive ? 'Microphone · Stop & Send' : 'Microphone' }] : []),
...(supportsClipboardRead ? [{ key: 'v', ctrl: true, shift: true, action: attachAppendClipboardItems, description: 'Attach Clipboard' }] : []),
], [attachAppendClipboardItems, recognitionState.isActive, toggleRecognition]));
useGlobalShortcuts('Composer', React.useMemo(() => {
const composerShortcuts: ShortcutObject[] = [];
if (supportsClipboardRead)
composerShortcuts.push({ key: 'v', ctrl: true, shift: true, action: attachAppendClipboardItems, description: 'Attach Clipboard' });
if (recognitionState.isActive) {
composerShortcuts.push({ key: 'm', ctrl: true, action: () => toggleRecognition(true), description: 'Mic · Send', disabled: !recognitionState.hasSpeech, endDecoratorIcon: TelegramIcon as any, level: 1 });
composerShortcuts.push({
key: ShortcutKey.Esc, action: () => {
setMicContinuation(false);
toggleRecognition(false);
}, description: 'Mic · Stop', level: 1,
});
} else if (browserSpeechRecognitionCapability().mayWork)
composerShortcuts.push({
key: 'm', ctrl: true, action: () => {
// steal focus from the textarea, in case it has - so that enter cannot work against us
(document.activeElement as HTMLElement)?.blur?.();
toggleRecognition(false);
}, description: 'Microphone',
});
return composerShortcuts;
}, [attachAppendClipboardItems, recognitionState.hasSpeech, recognitionState.isActive, toggleRecognition]));
const micIsRunning = !!speechInterimResult;
const micContinuationTrigger = micContinuation && !micIsRunning && !assistantAbortible && !recognitionState.errorMessage;
@@ -20,7 +20,7 @@ function _handleGlobalShortcutKeyDown(event: KeyboardEvent) {
// Quick-out: either the key is escape/left/right, or we have a modifier key pressed -- otherwise we exit
const lcEventKey = event.key.toLowerCase();
if (lcEventKey !== 'escape' && lcEventKey !== 'arrowleft' && lcEventKey !== 'arrowright' &&
!event.ctrlKey && !event.shiftKey && !event.altKey)
!event.ctrlKey && !event.shiftKey && !event.altKey && lcEventKey !== 'enter')
return;
@@ -1,11 +1,14 @@
import * as React from 'react';
import { SvgIcon } from '@mui/joy';
import { useGlobalShortcutsStore } from './store-global-shortcuts';
import { ensureGlobalShortcutHandler } from './globalShortcutsHandler';
export const ShortcutKey = {
Enter: 'Enter',
Esc: 'Escape',
Left: 'ArrowLeft',
Right: 'ArrowRight',
@@ -19,6 +22,8 @@ export interface ShortcutObject {
disabled?: boolean;
action: (() => void) | '_specialPrintShortcuts';
description?: string;
endDecoratorIcon?: typeof SvgIcon;
level?: number; // if set, it will exclusively show icons at that level of priority and hide the others
}