diff --git a/packages/ui/src/hooks/useKeyboardShortcuts.ts b/packages/ui/src/hooks/useKeyboardShortcuts.ts index 44212abf..01ea470c 100644 --- a/packages/ui/src/hooks/useKeyboardShortcuts.ts +++ b/packages/ui/src/hooks/useKeyboardShortcuts.ts @@ -290,6 +290,49 @@ export const useKeyboardShortcuts = () => { return; } + // Ctrl+] / Ctrl+[: Cycle through starred models (same gating as Shift+M) + if ( + eventMatchesShortcut(e, combo('cycle_favorite_model_forward')) || + eventMatchesShortcut(e, combo('cycle_favorite_model_backward')) + ) { + const { + isSettingsDialogOpen, + isCommandPaletteOpen, + isHelpDialogOpen, + isSessionSwitcherOpen, + isAboutDialogOpen, + activeMainTab, + favoriteModels, + addRecentModel, + } = useUIStore.getState(); + + if (isSettingsDialogOpen) { + return; + } + + const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen; + const isChatActive = activeMainTab === 'chat'; + + if (hasOverlay || !isChatActive || favoriteModels.length === 0) { + return; + } + + e.preventDefault(); + + const { currentProviderId, currentModelId, setProvider, setModel } = useConfigStore.getState(); + const len = favoriteModels.length; + const currentIdx = favoriteModels.findIndex( + (f) => f.providerID === currentProviderId && f.modelID === currentModelId, + ); + const delta = eventMatchesShortcut(e, combo('cycle_favorite_model_forward')) ? 1 : -1; + const next = favoriteModels[(currentIdx + delta + len) % len]; + + setProvider(next.providerID); + setModel(next.modelID); + addRecentModel(next.providerID, next.modelID); + return; + } + if (eventMatchesShortcut(e, combo('expand_input'))) { if (isMobile) { return; diff --git a/packages/ui/src/lib/shortcuts.ts b/packages/ui/src/lib/shortcuts.ts index 16e696cd..f2d2f373 100644 --- a/packages/ui/src/lib/shortcuts.ts +++ b/packages/ui/src/lib/shortcuts.ts @@ -291,6 +291,20 @@ const SHORTCUT_ACTIONS: ReadonlyArray = [ label: 'Cycle thinking variant', description: 'Cycle thinking variant while in chat', }, + { + id: 'cycle_favorite_model_forward', + defaultCombo: 'ctrl+]', + label: 'Cycle favorite model forward', + description: 'Cycle forward through starred models without opening the picker', + customizable: true, + }, + { + id: 'cycle_favorite_model_backward', + defaultCombo: 'ctrl+[', + label: 'Cycle favorite model backward', + description: 'Cycle backward through starred models without opening the picker', + customizable: true, + }, { id: 'expand_input', defaultCombo: 'mod+shift+e',