* feat: remove the ModelFooter in the ModelView and add the Delete Model Button from the Footer into the View * forget to run pnpm fix * chore(ui): reorder the model view buttons * Initial plan * Add customizable hotkeys infrastructure with UI Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Fix ESLint issues in HotkeyEditor component Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Fix knip unused export warning Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Add tests for hotkeys slice Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Fix tests to actually call reducer and add documentation Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * docs: add comprehensive hotkeys system documentation - Created new HOTKEYS.md technical documentation for developers explaining architecture, data flow, and implementation details - Added user-facing hotkeys.md guide with features overview and usage instructions - Removed old CUSTOMIZABLE_HOTKEYS.md in favor of new split documentation - Expanded documentation with detailed sections on: - State management and persistence - Component architecture and responsibilities - Developer integration * Behavior changed to hotkey press instead of input + checking for allready used hotkeys --------- Co-authored-by: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
8.2 KiB
Hotkeys System
This document describes the technical implementation of the customizable hotkeys system in InvokeAI.
Note: For user-facing documentation on how to use customizable hotkeys, see Hotkeys Feature Documentation.
Overview
The hotkeys system allows users to customize keyboard shortcuts throughout the application. All hotkeys are:
- Centrally defined and managed
- Customizable by users
- Persisted across sessions
- Type-safe and validated
Architecture
The customizable hotkeys feature is built on top of the existing hotkey system with the following components:
1. Hotkeys State Slice (hotkeysSlice.ts)
Location: invokeai/frontend/web/src/features/system/store/hotkeysSlice.ts
Responsibilities:
- Stores custom hotkey mappings in Redux state
- Persisted to IndexedDB using
redux-remember - Provides actions to change, reset individual, or reset all hotkeys
State Shape:
{
_version: 1,
customHotkeys: {
'app.invoke': ['mod+enter'],
'canvas.undo': ['mod+z'],
// ...
}
}
Actions:
hotkeyChanged(id, hotkeys)- Update a single hotkeyhotkeyReset(id)- Reset a single hotkey to defaultallHotkeysReset()- Reset all hotkeys to defaults
2. useHotkeyData Hook (useHotkeyData.ts)
Location: invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
Responsibilities:
- Defines all default hotkeys
- Merges default hotkeys with custom hotkeys from the store
- Returns the effective hotkeys that should be used throughout the app
- Provides platform-specific key translations (Ctrl/Cmd, Alt/Option)
Key Functions:
useHotkeyData()- Returns all hotkeys organized by categoryuseRegisteredHotkeys()- Hook to register a hotkey in a component
3. HotkeyEditor Component (HotkeyEditor.tsx)
Location: invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeyEditor.tsx
Features:
- Inline editor with input field
- Modifier buttons (Mod, Ctrl, Shift, Alt) for quick insertion
- Live preview of hotkey combinations
- Validation with visual feedback
- Help tooltip with syntax examples
- Save/cancel/reset buttons
Smart Features:
- Automatic
+insertion between modifiers - Cursor position preservation
- Validation prevents invalid combinations (e.g., modifier-only keys)
4. HotkeysModal Component (HotkeysModal.tsx)
Location: invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx
Features:
- View Mode / Edit Mode toggle
- Search functionality
- Category-based organization
- Shows HotkeyEditor components when in edit mode
- "Reset All to Default" button in edit mode
Data Flow
┌─────────────────────────────────────────────────────────────┐
│ 1. User opens Hotkeys Modal │
│ 2. User clicks "Edit Mode" button │
│ 3. User clicks edit icon next to a hotkey │
│ 4. User enters new hotkey(s) using editor │
│ 5. User clicks save or presses Enter │
│ 6. Custom hotkey stored via hotkeyChanged() action │
│ 7. Redux state persisted to IndexedDB (redux-remember) │
│ 8. useHotkeyData() hook picks up the change │
│ 9. All components using useRegisteredHotkeys() get update │
└─────────────────────────────────────────────────────────────┘
Hotkey Format
Hotkeys use the format from react-hotkeys-hook library:
- Modifiers:
mod,ctrl,shift,alt,meta - Keys: Letters, numbers, function keys, special keys
- Separator:
+between keys in a combination - Multiple hotkeys: Comma-separated (e.g.,
mod+a, ctrl+b)
Examples:
mod+enter- Mod key + Entershift+x- Shift + Xctrl+shift+a- Control + Shift + Af1, f2- F1 or F2 (alternatives)
Developer Guide
Using Hotkeys in Components
To use a hotkey in a component:
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
const MyComponent = () => {
const handleAction = useCallback(() => {
// Your action here
}, []);
// This automatically uses custom hotkeys if configured
useRegisteredHotkeys({
id: 'myAction',
category: 'app', // or 'canvas', 'viewer', 'gallery', 'workflows'
callback: handleAction,
options: { enabled: true, preventDefault: true },
dependencies: [handleAction]
});
// ...
};
Options:
enabled- Whether the hotkey is activepreventDefault- Prevent default browser behaviorenableOnFormTags- Allow hotkey in form elements (default: false)
Adding New Hotkeys
To add a new hotkey to the system:
1. Add Translation Strings
In invokeai/frontend/web/public/locales/en.json:
{
"hotkeys": {
"app": {
"myAction": {
"title": "My Action",
"desc": "Description of what this hotkey does"
}
}
}
}
2. Register the Hotkey
In invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts:
// Inside the appropriate category builder function
addHotkey('app', 'myAction', ['mod+k']); // Default binding
3. Use the Hotkey
In your component:
useRegisteredHotkeys({
id: 'myAction',
category: 'app',
callback: handleMyAction,
options: { enabled: true },
dependencies: [handleMyAction]
});
Hotkey Categories
Current categories:
- app - Global application hotkeys
- canvas - Canvas/drawing operations
- viewer - Image viewer operations
- gallery - Gallery/image grid operations
- workflows - Node workflow editor
To add a new category, update useHotkeyData.ts and add translations.
Testing
Tests are located in invokeai/frontend/web/src/features/system/store/hotkeysSlice.test.ts.
Test Coverage:
- Adding custom hotkeys
- Updating existing custom hotkeys
- Resetting individual hotkeys
- Resetting all hotkeys
- State persistence and migration
Run tests with:
cd invokeai/frontend/web
pnpm test:no-watch
Persistence
Custom hotkeys are persisted using the same mechanism as other app settings:
- Stored in Redux state under the
hotkeysslice - Persisted to IndexedDB via
redux-remember - Automatically loaded when the app starts
- Survives page refreshes and browser restarts
- Includes migration support for state schema changes
State Location:
- IndexedDB database:
invoke - Store key:
hotkeys
Dependencies
- react-hotkeys-hook (v4.5.0) - Core hotkey handling
- @reduxjs/toolkit - State management
- redux-remember - Persistence
- zod - State validation
Best Practices
- Use
modinstead ofctrl- Automatically maps to Cmd on Mac, Ctrl elsewhere - Provide descriptive translations - Help users understand what each hotkey does
- Avoid conflicts - Check existing hotkeys before adding new ones
- Use preventDefault - Prevent browser default behavior when appropriate
- Check enabled state - Only activate hotkeys when the action is available
- Use dependencies correctly - Ensure callbacks are stable with useCallback
Common Patterns
Conditional Hotkeys
useRegisteredHotkeys({
id: 'save',
category: 'app',
callback: handleSave,
options: {
enabled: hasUnsavedChanges && !isLoading, // Only when valid
preventDefault: true
},
dependencies: [hasUnsavedChanges, isLoading, handleSave]
});
Multiple Hotkeys for Same Action
// In useHotkeyData.ts
addHotkey('canvas', 'redo', ['mod+shift+z', 'mod+y']); // Two alternatives
Focus-Scoped Hotkeys
import { useFocusRegion } from 'common/hooks/focus';
const MyComponent = () => {
const focusRegionRef = useFocusRegion('myRegion');
// Hotkey only works when this region has focus
useRegisteredHotkeys({
id: 'myAction',
category: 'app',
callback: handleAction,
options: { enabled: true }
});
return <div ref={focusRegionRef}>...</div>;
};