mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Merge branch 'fork/mapringg/mac-shortcuts'
This commit is contained in:
@@ -17,7 +17,7 @@ import { useCapabilityTextToImage } from '~/modules/t2i/t2i.client';
|
||||
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { ConversationsManager } from '~/common/chats/ConversationsManager';
|
||||
import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
|
||||
import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
|
||||
import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler';
|
||||
import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
|
||||
import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom';
|
||||
@@ -397,7 +397,7 @@ export function AppChat() {
|
||||
openLlmOptions(chatLLMId);
|
||||
}, [openLlmOptions]);
|
||||
|
||||
const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
|
||||
const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
|
||||
// focused conversation
|
||||
['b', true, true, false, handleMessageBeamLastInFocusedPane],
|
||||
['r', true, true, false, handleMessageRegenerateLastInFocusedPane],
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BeamStoreApi, useBeamStore } from '~/modules/beam/store-beam.hooks';
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { GoodTooltip } from '~/common/components/GoodTooltip';
|
||||
import { KeyStroke } from '~/common/components/KeyStroke';
|
||||
import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut';
|
||||
import { ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
|
||||
import { animationBackgroundBeamGather, animationColorBeamScatterINV, animationEnterBelow } from '~/common/util/animUtils';
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export function ChatBarAltBeam(props: {
|
||||
|
||||
|
||||
// intercept esc this beam is focused
|
||||
useGlobalShortcut(ShortcutKeyName.Esc, false, false, false, handleCloseBeam);
|
||||
useGlobalShortcuts([[ShortcutKeyName.Esc, false, false, false, handleCloseBeam]]);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { DiagramConfig } from '~/modules/aifn/digrams/DiagramsModal';
|
||||
import type { ConversationHandler } from '~/common/chats/ConversationHandler';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
|
||||
import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut';
|
||||
import { ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
|
||||
import { createDMessage, DConversationId, DMessage, DMessageUserFlag, getConversation, messageToggleUserFlag, useChatStore } from '~/common/state/store-chats';
|
||||
import { useBrowserTranslationWarning } from '~/common/components/useIsBrowserTranslating';
|
||||
import { useCapabilityElevenLabs } from '~/common/components/useCapabilities';
|
||||
@@ -186,9 +186,9 @@ export function ChatMessageList(props: {
|
||||
setSelectedMessages(new Set());
|
||||
};
|
||||
|
||||
useGlobalShortcut(props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => {
|
||||
useGlobalShortcuts([[props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => {
|
||||
props.setIsMessageSelectionMode(false);
|
||||
});
|
||||
}]]);
|
||||
|
||||
|
||||
// text-diff functionality: only diff the last message and when it's complete (not typing), and they're similar in size
|
||||
|
||||
@@ -39,7 +39,7 @@ import { supportsScreenCapture } from '~/common/util/screenCaptureUtils';
|
||||
import { useAppStateStore } from '~/common/state/store-appstate';
|
||||
import { useChatOverlayStore } from '~/common/chats/store-chat-overlay-vanilla';
|
||||
import { useDebouncer } from '~/common/components/useDebouncer';
|
||||
import { useGlobalShortcut } from '~/common/components/useGlobalShortcut';
|
||||
import { useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
|
||||
import { useUICounter, useUIPreferencesStore } from '~/common/state/store-ui';
|
||||
import { useUXLabsStore } from '~/common/state/store-ux-labs';
|
||||
|
||||
@@ -399,7 +399,7 @@ export function Composer(props: {
|
||||
const { isSpeechEnabled, isSpeechError, isRecordingAudio, isRecordingSpeech, toggleRecording } =
|
||||
useSpeechRecognition(onSpeechResultCallback, chatMicTimeoutMs || 2000);
|
||||
|
||||
useGlobalShortcut('m', true, false, false, toggleRecording);
|
||||
useGlobalShortcuts([['m', true, false, false, toggleRecording]]);
|
||||
|
||||
const micIsRunning = !!speechInterimResult;
|
||||
const micContinuationTrigger = micContinuation && !micIsRunning && !assistantAbortible && !isSpeechError;
|
||||
@@ -451,7 +451,7 @@ export function Composer(props: {
|
||||
}
|
||||
}, [attachAppendFile]);
|
||||
|
||||
useGlobalShortcut(supportsClipboardRead ? 'v' : false, true, true, false, attachAppendClipboardItems);
|
||||
useGlobalShortcuts([[supportsClipboardRead ? 'v' : false, true, true, false, attachAppendClipboardItems]]);
|
||||
|
||||
const handleAttachmentInlineText = React.useCallback((attachmentId: AttachmentId) => {
|
||||
setComposeText(currentText => {
|
||||
|
||||
@@ -3,37 +3,38 @@ import * as React from 'react';
|
||||
import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer';
|
||||
|
||||
import { GoodModal } from '~/common/components/GoodModal';
|
||||
import { isMacUser } from '~/common/util/pwaUtils';
|
||||
import { platformAwareKeystrokes } from '~/common/components/KeyStroke';
|
||||
import { useIsMobile } from '~/common/components/useMatchMedia';
|
||||
|
||||
|
||||
const shortcutsMd = platformAwareKeystrokes(`
|
||||
|
||||
| Shortcut | Description |
|
||||
|---------------------|-------------------------------------------------|
|
||||
| **Edit** | |
|
||||
| Shift + Enter | Newline |
|
||||
| Alt + Enter | Append (no response) |
|
||||
| Ctrl + Shift + B | **Beam** last message |
|
||||
| Ctrl + Shift + R | **Regenerate** last message |
|
||||
| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) |
|
||||
| Ctrl + M | Microphone (voice typing) |
|
||||
| **Chats** | |
|
||||
| Ctrl + O | Open Chat File ... |
|
||||
| Ctrl + S | Save Chat File ... |
|
||||
| Ctrl + Alt + N | **New** chat |
|
||||
| Ctrl + Alt + X | **Reset** chat |
|
||||
| Ctrl + Alt + D | **Delete** chat |
|
||||
| Ctrl + Alt + B | **Branch** chat |
|
||||
| Ctrl + Alt + Left | **Previous** chat (in history) |
|
||||
| Ctrl + Alt + Right | **Next** chat (in history) |
|
||||
| **Settings** | |
|
||||
| Ctrl + Shift + P | ⚙️ Preferences |
|
||||
| Ctrl + Shift + M | 🧠 Models |
|
||||
| Ctrl + Shift + O | 💬 Options (current Chat Model) |
|
||||
| Ctrl + Shift + + | Increase Text Size |
|
||||
| Ctrl + Shift + - | Decrease Text Size |
|
||||
| Ctrl + Shift + ? | Shortcuts |
|
||||
| Shortcut | Description |
|
||||
|-----------------------------------------|-------------------------------------------------|
|
||||
| **Edit** | |
|
||||
| Shift + Enter | Newline |
|
||||
| Alt + Enter | Append (no response) |
|
||||
| Ctrl + Shift + B | **Beam** last message |
|
||||
| Ctrl + Shift + R | **Regenerate** last message |
|
||||
| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) |
|
||||
| Ctrl + M | Microphone (voice typing) |
|
||||
| **Chats** | |
|
||||
| Ctrl + O | Open Chat ... |
|
||||
| Ctrl + S | Save Chat ... |
|
||||
| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat |
|
||||
| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat |
|
||||
| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat |
|
||||
| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat |
|
||||
| Ctrl + Alt + Left | **Previous** chat (in history) |
|
||||
| Ctrl + Alt + Right | **Next** chat (in history) |
|
||||
| **Settings** | |
|
||||
| Ctrl + Shift + P | ⚙️ Preferences |
|
||||
| Ctrl + Shift + M | 🧠 Models |
|
||||
| Ctrl + Shift + O | 💬 Options (current Chat Model) |
|
||||
| Ctrl + Shift + + | Increase Text Size |
|
||||
| Ctrl + Shift + - | Decrease Text Size |
|
||||
| Ctrl + Shift + ${isMacUser ? '/' : '?'} | Shortcuts |
|
||||
|
||||
`).trim();
|
||||
|
||||
@@ -55,4 +56,4 @@ export function ShortcutsModal(props: { onClose: () => void }) {
|
||||
/>
|
||||
</GoodModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer';
|
||||
|
||||
import { AgiSquircleIcon } from '~/common/components/icons/AgiSquircleIcon';
|
||||
import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon';
|
||||
import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
|
||||
import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
|
||||
import { hasGoogleAnalytics } from '~/common/components/GoogleAnalytics';
|
||||
import { useIsMobile } from '~/common/components/useMatchMedia';
|
||||
import { animationTextShadowLimey } from '~/common/util/animUtils';
|
||||
@@ -159,7 +159,7 @@ export function ExplainerCarousel(props: {
|
||||
}, [props.explainerId]);
|
||||
|
||||
|
||||
const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
|
||||
const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
|
||||
[ShortcutKeyName.Left, false, false, false, handlePrevPage],
|
||||
[ShortcutKeyName.Right, false, false, false, handleNextPage],
|
||||
], [handleNextPage, handlePrevPage]);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isMacUser } from '~/common/util/pwaUtils';
|
||||
export function platformAwareKeystrokes(text: string) {
|
||||
return isMacUser
|
||||
? text
|
||||
.replaceAll('Ctrl', '⌘' /* Command */)
|
||||
.replaceAll('Ctrl', '⌃' /* Control */)
|
||||
.replaceAll('Alt', '⌥' /* Option */)
|
||||
.replaceAll('Shift', '⇧')
|
||||
// Optional: Replace "Enter" with "Return" if you want to align with Mac keyboard labeling
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export const ShortcutKeyName = {
|
||||
Esc: 'Escape',
|
||||
Left: 'ArrowLeft',
|
||||
Right: 'ArrowRight',
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Registers a global keyboard shortcut (if not undefined) to activate a callback.
|
||||
*
|
||||
* @param shortcutKey If undefined, the shortcut will not be registered.
|
||||
* @param useCtrl If true, the Ctrl key must be pressed for the shortcut to be activated.
|
||||
* @param useShift If true, the Shift key must be pressed for the shortcut to be activated.
|
||||
* @param useAlt If true, the Alt key must be pressed for the shortcut to be activated.
|
||||
* @param callback Make sure this is a memoized callback, otherwise the effect will be re-registered every time.
|
||||
*/
|
||||
export const useGlobalShortcut = (shortcutKey: string | false, useCtrl: boolean, useShift: boolean, useAlt: boolean, callback: () => void) => {
|
||||
React.useEffect(() => {
|
||||
if (!shortcutKey) return;
|
||||
const lcShortcut = shortcutKey.toLowerCase();
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey);
|
||||
if (
|
||||
(useCtrl === isCtrlOrCmd) &&
|
||||
(useShift === event.shiftKey) &&
|
||||
(useAlt === event.altKey) &&
|
||||
event.key.toLowerCase() === lcShortcut
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
callback();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [callback, shortcutKey, useAlt, useCtrl, useShift]);
|
||||
};
|
||||
|
||||
|
||||
export type GlobalShortcutItem = [key: string, ctrl: boolean, shift: boolean, alt: boolean, action: () => void];
|
||||
|
||||
|
||||
/**
|
||||
* Registers multiple global keyboard shortcuts to activate callbacks.
|
||||
*
|
||||
* @param shortcuts An array of shortcut objects.
|
||||
*/
|
||||
export const useGlobalShortcuts = (shortcuts: GlobalShortcutItem[]) => {
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
for (const [key, useCtrl, useShift, useAlt, action] of shortcuts) {
|
||||
const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey);
|
||||
if (
|
||||
key &&
|
||||
(useCtrl === isCtrlOrCmd) &&
|
||||
(useShift === event.shiftKey) &&
|
||||
(useAlt === event.altKey) &&
|
||||
event.key.toLowerCase() === key.toLowerCase()
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
action();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [shortcuts]);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { isMacUser } from '../util/pwaUtils';
|
||||
|
||||
|
||||
export const ShortcutKeyName = {
|
||||
Esc: 'Escape',
|
||||
Left: 'ArrowLeft',
|
||||
Right: 'ArrowRight',
|
||||
};
|
||||
|
||||
export type GlobalShortcutDefinition = [key: string | false, useCtrl: boolean, useShift: boolean, useAltForNonMac: boolean, action: () => void];
|
||||
|
||||
/**
|
||||
* Registers multiple global keyboard shortcuts -> function mappings.
|
||||
*
|
||||
* Important notes below:
|
||||
* - [MAC only] the Alt key is ignored even if defined in the shortcut
|
||||
* - [MAC only] are not using the command key at the moment, as it interfered with browser shortcuts
|
||||
* - stabilize the shortcuts definition (e.g. React.useMemo()) to avoid re-registering the shortcuts at every render
|
||||
*
|
||||
*/
|
||||
export const useGlobalShortcuts = (shortcuts: GlobalShortcutDefinition[]) => {
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
for (const [key, useCtrl, useShift, useAltForNonMac, action] of shortcuts) {
|
||||
if (
|
||||
key &&
|
||||
(useCtrl === event.ctrlKey) &&
|
||||
(useShift === event.shiftKey) &&
|
||||
(isMacUser /* Mac users won't need the Alt keys */ || useAltForNonMac === event.altKey) &&
|
||||
event.key.toLowerCase() === key.toLowerCase()
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
action();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [shortcuts]);
|
||||
};
|
||||
@@ -2,7 +2,8 @@ import * as React from 'react';
|
||||
|
||||
import type { DLLMId } from '~/modules/llms/store-llms';
|
||||
|
||||
import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
|
||||
import { GlobalShortcutDefinition, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
|
||||
import { isMacUser } from '~/common/util/pwaUtils';
|
||||
|
||||
|
||||
const DEBUG_OPTIMA_LAYOUT_PLUGGING = false;
|
||||
@@ -114,8 +115,8 @@ export function OptimaLayoutProvider(props: { children: React.ReactNode }) {
|
||||
|
||||
|
||||
// global shortcuts for Optima
|
||||
const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
|
||||
['?', true, true, false, actions.openShortcuts],
|
||||
const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
|
||||
[isMacUser ? '/' : '?', true, true, false, actions.openShortcuts],
|
||||
['m', true, true, false, actions.openModelsSetup],
|
||||
['p', true, true, false, actions.openPreferencesTab],
|
||||
], [actions]);
|
||||
|
||||
Reference in New Issue
Block a user