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:
File diff suppressed because one or more lines are too long
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "重置为默认",
|
||||
|
||||
@@ -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": "重設為預設",
|
||||
|
||||
@@ -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": "Επαναφορά στις προεπιλεγμένες",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "デフォルトにリセット",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Сбросить настройки по умолчанию",
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 149,
|
||||
version: 150,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user