diff --git a/src/apps/chat/components/StatusBar.tsx b/src/apps/chat/components/StatusBar.tsx
index f03f0470e..1d1785275 100644
--- a/src/apps/chat/components/StatusBar.tsx
+++ b/src/apps/chat/components/StatusBar.tsx
@@ -114,6 +114,7 @@ function ShortcutItem(props: { shortcut: ShortcutObject }) {
{/*{!!props.shortcut.altForNonMac && {_platformAwareModifier('Alt')}}*/}
{props.shortcut.key === 'Escape' ? 'Esc' : props.shortcut.key.toUpperCase()}
{props.shortcut.description}
+ {props.shortcut.endDecoratorIcon && }
);
}
@@ -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)
diff --git a/src/apps/chat/components/composer/Composer.tsx b/src/apps/chat/components/composer/Composer.tsx
index 412adaf36..d96cd9102 100644
--- a/src/apps/chat/components/composer/Composer.tsx
+++ b/src/apps/chat/components/composer/Composer.tsx
@@ -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;
diff --git a/src/common/components/shortcuts/globalShortcutsHandler.ts b/src/common/components/shortcuts/globalShortcutsHandler.ts
index 0216d9caa..0552b0124 100644
--- a/src/common/components/shortcuts/globalShortcutsHandler.ts
+++ b/src/common/components/shortcuts/globalShortcutsHandler.ts
@@ -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;
diff --git a/src/common/components/shortcuts/useGlobalShortcuts.ts b/src/common/components/shortcuts/useGlobalShortcuts.ts
index d7455ecc4..24c24d146 100644
--- a/src/common/components/shortcuts/useGlobalShortcuts.ts
+++ b/src/common/components/shortcuts/useGlobalShortcuts.ts
@@ -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
}