feat: add shortcuts to rename topic and edit last user message (#9466)

* feat: add rename topic shortcut (Ctrl/Cmd+T)

* fix: add migration for rename topic shortcut

* feat: add shortcut to edit last user message (Ctrl/Cmd+Shift+E)
- 用于在用户提示词生成的响应不符合预期时,便捷地激活提示词编辑,从而配合编辑器编辑支持的Enter键提交绑定来生成新的模型响应
- messages:绑定 useShortcut('edit_last_user_message'),定位最后一条非 clear 的用户消息并发出 EDIT_MESSAGE
- message:监听 EDIT_MESSAGE,调用 startEditing(message.id) 激活内联编辑(沿用现有自动滚动)

* fix: lint errors and sync i18n

* fix(i18n): complete missing translations in ES, PT, EL and FR locales

* disable new shortcuts by default

* show topic tab on rename shortcut

* refactor: use findLast to simplify find last user message

* add esc key to cancel message editing (discard changes)

* fix missing comma

* remove extra linebreak

* fix: version

---------

Co-authored-by: suyao <sy20010504@gmail.com>
This commit is contained in:
Murphy
2025-09-05 19:59:59 +08:00
committed by GitHub
parent 6531d40386
commit 60e1f15e42
18 changed files with 3216 additions and 5980 deletions

File diff suppressed because one or more lines are too long

View File

@@ -175,7 +175,9 @@ const shortcutKeyMap = {
actions: 'settings.shortcuts.actions',
clear_shortcut: 'settings.shortcuts.clear_shortcut',
clear_topic: 'settings.shortcuts.clear_topic',
rename_topic: 'settings.shortcuts.rename_topic',
copy_last_message: 'settings.shortcuts.copy_last_message',
edit_last_user_message: 'settings.shortcuts.edit_last_user_message',
enabled: 'settings.shortcuts.enabled',
exit_fullscreen: 'settings.shortcuts.exit_fullscreen',
label: 'settings.shortcuts.label',

View File

@@ -3892,12 +3892,14 @@
"clear_shortcut": "Clear Shortcut",
"clear_topic": "Clear Messages",
"copy_last_message": "Copy Last Message",
"edit_last_user_message": "Edit Last User Message",
"enabled": "Enable",
"exit_fullscreen": "Exit Fullscreen",
"label": "Key",
"mini_window": "Quick Assistant",
"new_topic": "New Topic",
"press_shortcut": "Press Shortcut",
"rename_topic": "Rename Topic",
"reset_defaults": "Reset Defaults",
"reset_defaults_confirm": "Are you sure you want to reset all shortcuts?",
"reset_to_default": "Reset to Default",

View File

@@ -3892,12 +3892,14 @@
"clear_shortcut": "清除快捷键",
"clear_topic": "清空消息",
"copy_last_message": "复制上一条消息",
"edit_last_user_message": "编辑最后一条用户消息",
"enabled": "启用",
"exit_fullscreen": "退出全屏",
"label": "按键",
"mini_window": "快捷助手",
"new_topic": "新建话题",
"press_shortcut": "按下快捷键",
"rename_topic": "重命名话题",
"reset_defaults": "重置默认快捷键",
"reset_defaults_confirm": "确定要重置所有快捷键吗?",
"reset_to_default": "重置为默认",

View File

@@ -3892,12 +3892,14 @@
"clear_shortcut": "清除快捷鍵",
"clear_topic": "清除所有訊息",
"copy_last_message": "複製上一則訊息",
"edit_last_user_message": "編輯最後一則使用者訊息",
"enabled": "啟用",
"exit_fullscreen": "退出螢幕",
"label": "按鍵",
"mini_window": "快捷助手",
"new_topic": "新增話題",
"press_shortcut": "按下快捷鍵",
"rename_topic": "重新命名話題",
"reset_defaults": "重設預設快捷鍵",
"reset_defaults_confirm": "確定要重設所有快捷鍵嗎?",
"reset_to_default": "重設為預設",

View File

@@ -3888,12 +3888,14 @@
"clear_shortcut": "Καθαρισμός συντομού πλήκτρου",
"clear_topic": "Άδειασμα μηνυμάτων",
"copy_last_message": "Αντιγραφή του τελευταίου μηνύματος",
"edit_last_user_message": "Επεξεργασία του τελευταίου μηνύματος χρήστη",
"enabled": "ενεργοποίηση",
"exit_fullscreen": "Έξοδος από πλήρη οθόνη",
"label": "Πλήκτρο",
"mini_window": "Συντομεύστε επιχειρηματικά",
"new_topic": "Νέο θέμα",
"press_shortcut": "Πάτησε το συντομού πλήκτρου",
"rename_topic": "Μετονομασία θέματος",
"reset_defaults": "Επαναφορά στα προεπιλεγμένα συντομού πλήκτρα",
"reset_defaults_confirm": "Θέλετε να επαναφέρετε όλα τα συντομού πλήκτρα στις προεπιλεγμένες τιμές;",
"reset_to_default": "Επαναφορά στις προεπιλεγμένες",

View File

@@ -3888,12 +3888,14 @@
"clear_shortcut": "Borrar atajo",
"clear_topic": "Vaciar mensaje",
"copy_last_message": "Copiar el último mensaje",
"edit_last_user_message": "Editar último mensaje de usuario",
"enabled": "habilitar",
"exit_fullscreen": "Salir de pantalla completa",
"label": "Tecla",
"mini_window": "Asistente rápido",
"new_topic": "Nuevo tema",
"press_shortcut": "Presionar atajo",
"rename_topic": "Renombrar tema",
"reset_defaults": "Restablecer atajos predeterminados",
"reset_defaults_confirm": "¿Está seguro de querer restablecer todos los atajos?",
"reset_to_default": "Restablecer a predeterminado",

View File

@@ -3888,12 +3888,14 @@
"clear_shortcut": "Effacer raccourci clavier",
"clear_topic": "Vider les messages",
"copy_last_message": "Copier le dernier message",
"edit_last_user_message": "Éditer le dernier message utilisateur",
"enabled": "activer",
"exit_fullscreen": "Quitter le plein écran",
"label": "Touche",
"mini_window": "Assistant rapide",
"new_topic": "Nouveau sujet",
"press_shortcut": "Appuyer sur raccourci clavier",
"rename_topic": "Renommer le sujet",
"reset_defaults": "Réinitialiser raccourcis par défaut",
"reset_defaults_confirm": "Êtes-vous sûr de vouloir réinitialiser tous les raccourcis clavier ?",
"reset_to_default": "Réinitialiser aux valeurs par défaut",

View File

@@ -3892,12 +3892,14 @@
"clear_shortcut": "ショートカットをクリア",
"clear_topic": "メッセージを消去",
"copy_last_message": "最後のメッセージをコピー",
"edit_last_user_message": "最後のユーザーメッセージを編集",
"enabled": "有効化",
"exit_fullscreen": "フルスクリーンを終了",
"label": "キー",
"mini_window": "クイックアシスタント",
"new_topic": "新しいトピック",
"press_shortcut": "ショートカットを押す",
"rename_topic": "トピックの名前を変更",
"reset_defaults": "デフォルトのショートカットをリセット",
"reset_defaults_confirm": "すべてのショートカットをリセットしてもよろしいですか?",
"reset_to_default": "デフォルトにリセット",

View File

@@ -3888,12 +3888,14 @@
"clear_shortcut": "Limpar atalho",
"clear_topic": "Limpar mensagem",
"copy_last_message": "Copiar a última mensagem",
"edit_last_user_message": "Editar última mensagem do usuário",
"enabled": "ativar",
"exit_fullscreen": "Sair da tela cheia",
"label": "Tecla",
"mini_window": "Atalho de assistente",
"new_topic": "Novo tópico",
"press_shortcut": "Pressionar atalho",
"rename_topic": "Renomear tópico",
"reset_defaults": "Redefinir atalhos padrão",
"reset_defaults_confirm": "Tem certeza de que deseja redefinir todos os atalhos?",
"reset_to_default": "Redefinir para padrão",

View File

@@ -3892,12 +3892,14 @@
"clear_shortcut": "Очистить сочетание клавиш",
"clear_topic": "Очистить все сообщения",
"copy_last_message": "Копировать последнее сообщение",
"enabled": "启用",
"edit_last_user_message": "Редактировать последнее сообщение пользователя",
"enabled": "Включить",
"exit_fullscreen": "Выйти из полноэкранного режима",
"label": "Клавиша",
"mini_window": "Быстрый помощник",
"new_topic": "Новый топик",
"press_shortcut": "Нажмите сочетание клавиш",
"rename_topic": "Переименовать топик",
"reset_defaults": "Сбросить настройки по умолчанию",
"reset_defaults_confirm": "Вы уверены, что хотите сбросить все горячие клавиши?",
"reset_to_default": "Сбросить настройки по умолчанию",

View File

@@ -2,6 +2,7 @@ import { loggerService } from '@logger'
import { ContentSearch, ContentSearchRef } 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 { QuickPanelProvider } from '@renderer/components/QuickPanel'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useChatContext } from '@renderer/hooks/useChatContext'
@@ -9,6 +10,7 @@ import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { useTimer } from '@renderer/hooks/useTimer'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant, Topic } from '@renderer/types'
import { classNames } from '@renderer/utils'
import { Flex } from 'antd'
@@ -16,6 +18,7 @@ import { debounce } from 'lodash'
import { AnimatePresence, motion } from 'motion/react'
import React, { FC, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import ChatNavbar from './ChatNavbar'
@@ -34,7 +37,8 @@ interface Props {
}
const Chat: FC<Props> = (props) => {
const { assistant } = useAssistant(props.assistant.id)
const { assistant, updateTopic } = useAssistant(props.assistant.id)
const { t } = useTranslation()
const { topicPosition, messageStyle, messageNavigation } = useSettings()
const { showTopics } = useShowTopics()
const { isMultiSelectMode } = useChatContext(props.activeTopic)
@@ -59,6 +63,24 @@ const Chat: FC<Props> = (props) => {
}
})
useShortcut('rename_topic', async () => {
const topic = props.activeTopic
if (!topic) return
EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)
const name = await PromptPopup.show({
title: t('chat.topics.edit.title'),
message: '',
defaultValue: topic.name || '',
extraNode: <div style={{ color: 'var(--color-text-3)', marginTop: 8 }}>{t('chat.topics.edit.title_tip')}</div>
})
if (name && topic.name !== name) {
const updatedTopic = { ...topic, name, isNameManuallyEdited: true }
updateTopic(updatedTopic as Topic)
}
})
const contentSearchFilter: NodeFilter = {
acceptNode(node) {
const container = node.parentElement?.closest('.message-content-container')

View File

@@ -70,7 +70,7 @@ const MessageItem: FC<Props> = ({
const { messageFont, fontSize, messageStyle, showMessageOutline } = useSettings()
const { editMessageBlocks, resendUserMessageWithEdit, editMessage } = useMessageOperations(topic)
const messageContainerRef = useRef<HTMLDivElement>(null)
const { editingMessageId, stopEditing } = useMessageEditing()
const { editingMessageId, startEditing, stopEditing } = useMessageEditing()
const { setTimeoutTimer } = useTimer()
const isEditing = editingMessageId === message.id
@@ -148,6 +148,19 @@ const MessageItem: FC<Props> = ({
return () => unsubscribes.forEach((unsub) => unsub())
}, [message.id, messageHighlightHandler])
// Listen for external edit requests and activate editor for this message if it matches
useEffect(() => {
const handleEditRequest = (targetId: string) => {
if (targetId === message.id) {
startEditing(message.id)
}
}
const unsubscribe = EventEmitter.on(EVENT_NAMES.EDIT_MESSAGE, handleEditRequest)
return () => {
unsubscribe()
}
}, [message.id, startEditing])
if (message.type === 'clear') {
return (
<NewContextMessage

View File

@@ -228,6 +228,12 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
return
}
if (event.key === 'Escape') {
event.preventDefault()
onCancel()
return
}
// keep the same enter behavior as inputbar
const isEnterPressed = event.key === 'Enter' && !event.nativeEvent.isComposing
if (isEnterPressed) {

View File

@@ -281,6 +281,13 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
}
})
useShortcut('edit_last_user_message', () => {
const lastUserMessage = messagesRef.current.findLast((m) => m.role === 'user' && m.type !== 'clear')
if (lastUserMessage) {
EventEmitter.emit(EVENT_NAMES.EDIT_MESSAGE, lastUserMessage.id)
}
})
useEffect(() => {
requestAnimationFrame(() => onComponentUpdate?.())
}, [onComponentUpdate])

View File

@@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 149,
version: 150,
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
migrate
},

View File

@@ -2404,6 +2404,16 @@ const migrateConfig = {
logger.error('migrate 149 error', error as Error)
return state
}
},
'150': (state: RootState) => {
try {
addShortcuts(state, ['rename_topic'], 'new_topic')
addShortcuts(state, ['edit_last_user_message'], 'copy_last_message')
return state
} catch (error) {
logger.error('migrate 150 error', error as Error)
return state
}
}
}

View File

@@ -53,6 +53,13 @@ const initialState: ShortcutsState = {
enabled: true,
system: false
},
{
key: 'rename_topic',
shortcut: ['CommandOrControl', 'T'],
editable: true,
enabled: false,
system: false
},
{
key: 'toggle_show_assistants',
shortcut: ['CommandOrControl', '['],
@@ -75,6 +82,13 @@ const initialState: ShortcutsState = {
enabled: false,
system: false
},
{
key: 'edit_last_user_message',
shortcut: ['CommandOrControl', 'Shift', 'E'],
editable: true,
enabled: false,
system: false
},
{
key: 'search_message_in_chat',
shortcut: ['CommandOrControl', 'F'],