mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-06 22:38:23 +08:00
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:
committed by
GitHub
parent
7cca23571a
commit
90d76fc3dc
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5129,6 +5129,7 @@
|
||||
"reset_to_default": "重置为默认",
|
||||
"search_message": "搜索消息",
|
||||
"search_message_in_chat": "在当前对话中搜索消息",
|
||||
"select_model": "选择模型",
|
||||
"selection_assistant_select_text": "划词助手:取词",
|
||||
"selection_assistant_toggle": "开关划词助手",
|
||||
"show_app": "显示 / 隐藏应用",
|
||||
|
||||
@@ -5129,6 +5129,7 @@
|
||||
"reset_to_default": "重設為預設",
|
||||
"search_message": "搜尋訊息",
|
||||
"search_message_in_chat": "在目前對話中搜尋訊息",
|
||||
"select_model": "選擇模型",
|
||||
"selection_assistant_select_text": "劃詞助手:取詞",
|
||||
"selection_assistant_toggle": "開關劃詞助手",
|
||||
"show_app": "顯示 / 隱藏應用程式",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Εμφάνιση εφαρμογής",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "アプリを表示/非表示",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Показать/скрыть приложение",
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -86,7 +86,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 198,
|
||||
version: 199,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user