feat: Added keyboard shortcut for model selection (#12922)

### What this PR does

Before this PR:
There was no keyboard shortcut to open the model selector. Users had to
click the model selector button in the chat navbar to switch models.

After this PR:
Pressing `Cmd+Shift+M` (or `Ctrl+Shift+M` on Windows/Linux) opens the
model selector popup, allowing users to switch models without leaving
the keyboard. The shortcut appears in the Keyboard Shortcuts settings
page and is editable/toggleable like other shortcuts.

### Why we need it and why it was done in this way

Model switching is a frequent action during chat sessions. A keyboard
shortcut makes this faster and more accessible for power users.

The following tradeoffs were made:
- The shortcut handler is placed in `Chat.tsx` alongside other chat
shortcuts (`search_message_in_chat`, `rename_topic`, `new_topic`),
keeping all chat-scoped shortcuts co-located.
- The handler reuses the same `SelectModelPopup.show()` API and model
filter logic as `SelectModelButton`, ensuring identical behavior to the
click-based flow.

The following alternatives were considered:
- Placing the handler in a shared hook or the `SelectModelButton`
component itself. This was rejected because the shortcut needs access to
the assistant context from `Chat.tsx`, and the existing pattern places
all chat shortcuts directly in that component.

### Breaking changes

None. This is an additive feature with no changes to existing behavior
or APIs.

### Special notes for your reviewer

- This PR does **not** alter Redux data models or IndexedDB schemas. The
store migration (v199) only appends a new entry to the existing
`shortcuts` array, which is the standard pattern used by all prior
shortcut additions.
- The migration inserts `select_model` after the `toggle_new_context`
shortcut.
- i18n labels were added to all 11 locale/translation files.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. You want a
user-guide update if it's a user facing feature.

### Release note

```release-note
feat: add Cmd+Shift+M keyboard shortcut to open the model selector in chat
```

Signed-off-by: Yuxi Long <longyuxi@live.com>
This commit is contained in:
Yuxi Long / Jaden Long
2026-02-24 07:05:19 -05:00
committed by GitHub
parent 7cca23571a
commit 90d76fc3dc
16 changed files with 46 additions and 3 deletions

View File

@@ -213,6 +213,7 @@ const shortcutKeyMap = {
reset_to_default: 'settings.shortcuts.reset_to_default',
search_message: 'settings.shortcuts.search_message',
search_message_in_chat: 'settings.shortcuts.search_message_in_chat',
select_model: 'settings.shortcuts.select_model',
selection_assistant_select_text: 'settings.shortcuts.selection_assistant_select_text',
selection_assistant_toggle: 'settings.shortcuts.selection_assistant_toggle',
show_app: 'settings.shortcuts.show_app',

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Reset to Default",
"search_message": "Search Message",
"search_message_in_chat": "Search Message in Current Chat",
"select_model": "Select Model",
"selection_assistant_select_text": "Selection Assistant: Select Text",
"selection_assistant_toggle": "Toggle Selection Assistant",
"show_app": "Show/Hide App",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "重置为默认",
"search_message": "搜索消息",
"search_message_in_chat": "在当前对话中搜索消息",
"select_model": "选择模型",
"selection_assistant_select_text": "划词助手:取词",
"selection_assistant_toggle": "开关划词助手",
"show_app": "显示 / 隐藏应用",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "重設為預設",
"search_message": "搜尋訊息",
"search_message_in_chat": "在目前對話中搜尋訊息",
"select_model": "選擇模型",
"selection_assistant_select_text": "劃詞助手:取詞",
"selection_assistant_toggle": "開關劃詞助手",
"show_app": "顯示 / 隱藏應用程式",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Auf Standard zurücksetzen",
"search_message": "Nachricht suchen",
"search_message_in_chat": "In aktuellem Chat suchen",
"select_model": "Select Model",
"selection_assistant_select_text": "Textauswahl-Assistent: Text erfassen",
"selection_assistant_toggle": "Textauswahl-Assistent umschalten",
"show_app": "App anzeigen/ausblenden",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Επαναφορά στις προεπιλεγμένες",
"search_message": "Αναζήτηση μηνυμάτων",
"search_message_in_chat": "Αναζήτηση μηνύματος στην τρέχουσα συνομιλία",
"select_model": "Select Model",
"selection_assistant_select_text": "Βοηθός επιλογής κειμένου: επιλογή λέξης",
"selection_assistant_toggle": "Εναλλαγή βοηθού επιλογής κειμένου",
"show_app": "Εμφάνιση εφαρμογής",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Restablecer a predeterminado",
"search_message": "Buscar mensaje",
"search_message_in_chat": "Buscar mensajes en la conversación actual",
"select_model": "Select Model",
"selection_assistant_select_text": "Asistente de selección de texto: obtener palabras",
"selection_assistant_toggle": "Activar/desactivar el asistente de selección de texto",
"show_app": "Mostrar aplicación",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Réinitialiser aux valeurs par défaut",
"search_message": "Rechercher un message",
"search_message_in_chat": "Rechercher un message dans la conversation actuelle",
"select_model": "Select Model",
"selection_assistant_select_text": "Assistant de sélection de texte : extraire le texte",
"selection_assistant_toggle": "Activer/désactiver l'assistant de sélection de texte",
"show_app": "Afficher l'application",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "デフォルトにリセット",
"search_message": "メッセージを検索",
"search_message_in_chat": "現在のチャットでメッセージを検索",
"select_model": "Select Model",
"selection_assistant_select_text": "選択アシスタント:テキストを選択",
"selection_assistant_toggle": "選択アシスタントを切り替え",
"show_app": "アプリを表示/非表示",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Redefinir para padrão",
"search_message": "Pesquisar mensagem",
"search_message_in_chat": "Pesquisar mensagens nesta conversa",
"select_model": "Select Model",
"selection_assistant_select_text": "Assistente de seleção de texto: selecionar texto",
"selection_assistant_toggle": "Ativar/desativar assistente de seleção de texto",
"show_app": "Exibir aplicativo",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Resetează la implicit",
"search_message": "Caută mesaj",
"search_message_in_chat": "Caută mesaj în chat-ul curent",
"select_model": "Select Model",
"selection_assistant_select_text": "Asistent de selecție: Selectează text",
"selection_assistant_toggle": "Comută Asistentul de selecție",
"show_app": "Afișează/Ascunde aplicația",

View File

@@ -5129,6 +5129,7 @@
"reset_to_default": "Сбросить настройки по умолчанию",
"search_message": "Поиск сообщения",
"search_message_in_chat": "Поиск сообщения в текущем диалоге",
"select_model": "Select Model",
"selection_assistant_select_text": "Помощник выделения: выделить текст",
"selection_assistant_toggle": "Переключить помощник выделения",
"show_app": "Показать/скрыть приложение",

View File

@@ -4,7 +4,9 @@ import { ContentSearch } from '@renderer/components/ContentSearch'
import { HStack } from '@renderer/components/Layout'
import MultiSelectActionPopup from '@renderer/components/Popups/MultiSelectionPopup'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { SelectModelPopup } from '@renderer/components/Popups/SelectModelPopup'
import { QuickPanelProvider } from '@renderer/components/QuickPanel'
import { isEmbeddingModel, isRerankModel, isWebSearchModel } from '@renderer/config/models'
import { useCreateDefaultSession } from '@renderer/hooks/agents/useCreateDefaultSession'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useChatContext } from '@renderer/hooks/useChatContext'
@@ -14,7 +16,7 @@ import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowTopics } from '@renderer/hooks/useStore'
import { useTimer } from '@renderer/hooks/useTimer'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import type { Assistant, Topic } from '@renderer/types'
import type { Assistant, Model, Topic } from '@renderer/types'
import { classNames } from '@renderer/utils'
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
import { Alert, Flex } from 'antd'
@@ -45,7 +47,7 @@ interface Props {
}
const Chat: FC<Props> = (props) => {
const { assistant, updateTopic } = useAssistant(props.assistant.id)
const { assistant, updateAssistant, updateTopic } = useAssistant(props.assistant.id)
const { t } = useTranslation()
const { topicPosition, messageStyle, messageNavigation } = useSettings()
const { showTopics } = useShowTopics()
@@ -110,6 +112,19 @@ const Chat: FC<Props> = (props) => {
}
)
useShortcut('select_model', async () => {
const modelFilter = (m: Model) => !isEmbeddingModel(m) && !isRerankModel(m)
const selectedModel = await SelectModelPopup.show({ model: assistant?.model, filter: modelFilter })
if (selectedModel) {
const enabledWebSearch = isWebSearchModel(selectedModel)
updateAssistant({
...props.assistant,
model: selectedModel,
enableWebSearch: enabledWebSearch && props.assistant.enableWebSearch
})
}
})
const contentSearchFilter: NodeFilter = {
acceptNode(node) {
const container = node.parentElement?.closest('.message-content-container')

View File

@@ -86,7 +86,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 198,
version: 199,
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
migrate
},

View File

@@ -3244,6 +3244,15 @@ const migrateConfig = {
logger.error('migrate 198 error', error as Error)
return state
}
},
'199': (state: RootState) => {
try {
addShortcuts(state, ['select_model'], 'toggle_new_context')
return state
} catch (error) {
logger.error('migrate 199 error', error as Error)
return state
}
}
}

View File

@@ -134,6 +134,13 @@ const initialState: ShortcutsState = {
enabled: true,
system: false
},
{
key: 'select_model',
shortcut: ['CommandOrControl', 'Shift', 'M'],
editable: true,
enabled: true,
system: false
},
{
key: 'exit_fullscreen',
shortcut: ['Escape'],