mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 21:42:27 +08:00
fix: unified the behavior of SendMessage shortcut (#7276)
This commit is contained in:
parent
68c1a3e1cc
commit
37dac7f6ea
@ -183,7 +183,7 @@
|
||||
"input.new.context": "Clear Context {{Command}}",
|
||||
"input.new_topic": "New Topic {{Command}}",
|
||||
"input.pause": "Pause",
|
||||
"input.placeholder": "Type your message here...",
|
||||
"input.placeholder": "Type your message here, press {{key}} to send...",
|
||||
"input.send": "Send",
|
||||
"input.settings": "Settings",
|
||||
"input.topics": " Topics ",
|
||||
|
||||
@ -183,7 +183,7 @@
|
||||
"input.new.context": "コンテキストをクリア {{Command}}",
|
||||
"input.new_topic": "新しいトピック {{Command}}",
|
||||
"input.pause": "一時停止",
|
||||
"input.placeholder": "ここにメッセージを入力...",
|
||||
"input.placeholder": "ここにメッセージを入力し、{{key}} を押して送信...",
|
||||
"input.send": "送信",
|
||||
"input.settings": "設定",
|
||||
"input.topics": " トピック ",
|
||||
|
||||
@ -183,7 +183,7 @@
|
||||
"input.new.context": "Очистить контекст {{Command}}",
|
||||
"input.new_topic": "Новый топик {{Command}}",
|
||||
"input.pause": "Остановить",
|
||||
"input.placeholder": "Введите ваше сообщение здесь...",
|
||||
"input.placeholder": "Введите ваше сообщение здесь, нажмите {{key}} для отправки...",
|
||||
"input.send": "Отправить",
|
||||
"input.settings": "Настройки",
|
||||
"input.topics": " Топики ",
|
||||
|
||||
@ -183,7 +183,7 @@
|
||||
"input.new.context": "清除上下文 {{Command}}",
|
||||
"input.new_topic": "新话题 {{Command}}",
|
||||
"input.pause": "暂停",
|
||||
"input.placeholder": "在这里输入消息...",
|
||||
"input.placeholder": "在这里输入消息,按 {{key}} 发送...",
|
||||
"input.translating": "翻译中...",
|
||||
"input.send": "发送",
|
||||
"input.settings": "设置",
|
||||
|
||||
@ -183,7 +183,7 @@
|
||||
"input.new.context": "清除上下文 {{Command}}",
|
||||
"input.new_topic": "新話題 {{Command}}",
|
||||
"input.pause": "暫停",
|
||||
"input.placeholder": "在此輸入您的訊息...",
|
||||
"input.placeholder": "在此輸入您的訊息,按 {{key}} 傳送...",
|
||||
"input.send": "傳送",
|
||||
"input.settings": "設定",
|
||||
"input.topics": " 話題 ",
|
||||
|
||||
@ -36,6 +36,7 @@ import type { MessageInputBaseParams } from '@renderer/types/newMessage'
|
||||
import { classNames, delay, formatFileSize, getFileExtension } from '@renderer/utils'
|
||||
import { formatQuotedText } from '@renderer/utils/formats'
|
||||
import { getFilesFromDropEvent } from '@renderer/utils/input'
|
||||
import { getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
@ -309,8 +310,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
}, [knowledgeBases, openKnowledgeFileList, quickPanel, t, inputbarToolsRef])
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const isEnterPressed = event.key === 'Enter' && !event.nativeEvent.isComposing
|
||||
|
||||
// 按下Tab键,自动选中${xxx}
|
||||
if (event.key === 'Tab' && inputFocus) {
|
||||
event.preventDefault()
|
||||
@ -366,32 +365,37 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnterPressed && !event.shiftKey && sendMessageShortcut === 'Enter') {
|
||||
if (quickPanel.isVisible) return event.preventDefault()
|
||||
//to check if the SendMessage key is pressed
|
||||
//other keys should be ignored
|
||||
const isEnterPressed = event.key === 'Enter' && !event.nativeEvent.isComposing
|
||||
if (isEnterPressed) {
|
||||
if (isSendMessageKeyPressed(event, sendMessageShortcut)) {
|
||||
if (quickPanel.isVisible) return event.preventDefault()
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
} else {
|
||||
//shift+enter's default behavior is to add a new line, ignore it
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault()
|
||||
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
const textArea = textareaRef.current?.resizableTextArea?.textArea
|
||||
if (textArea) {
|
||||
const start = textArea.selectionStart
|
||||
const end = textArea.selectionEnd
|
||||
const text = textArea.value
|
||||
const newText = text.substring(0, start) + '\n' + text.substring(end)
|
||||
|
||||
if (sendMessageShortcut === 'Shift+Enter' && isEnterPressed && event.shiftKey) {
|
||||
if (quickPanel.isVisible) return event.preventDefault()
|
||||
// update text by setState, not directly modify textarea.value
|
||||
setText(newText)
|
||||
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Ctrl+Enter' && isEnterPressed && event.ctrlKey) {
|
||||
if (quickPanel.isVisible) return event.preventDefault()
|
||||
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Command+Enter' && isEnterPressed && event.metaKey) {
|
||||
if (quickPanel.isVisible) return event.preventDefault()
|
||||
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
// set cursor position in the next render cycle
|
||||
setTimeout(() => {
|
||||
textArea.selectionStart = textArea.selectionEnd = start + 1
|
||||
onInput() // trigger resizeTextArea
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enableBackspaceDeleteModel && event.key === 'Backspace' && text.trim() === '' && mentionModels.length > 0) {
|
||||
@ -798,7 +802,11 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
value={text}
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={isTranslating ? t('chat.input.translating') : t('chat.input.placeholder')}
|
||||
placeholder={
|
||||
isTranslating
|
||||
? t('chat.input.translating')
|
||||
: t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) })
|
||||
}
|
||||
autoFocus
|
||||
contextMenu="true"
|
||||
variant="borderless"
|
||||
|
||||
@ -8,7 +8,7 @@ import PasteService from '@renderer/services/PasteService'
|
||||
import { FileType, FileTypes } from '@renderer/types'
|
||||
import { Message, MessageBlock, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { classNames, getFileExtension } from '@renderer/utils'
|
||||
import { getFilesFromDropEvent } from '@renderer/utils/input'
|
||||
import { getFilesFromDropEvent, isSendMessageKeyPressed } from '@renderer/utils/input'
|
||||
import { createFileBlock, createImageBlock } from '@renderer/utils/messageUtils/create'
|
||||
import { findAllBlocks } from '@renderer/utils/messageUtils/find'
|
||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||
@ -169,31 +169,39 @@ const MessageBlockEditor: FC<Props> = ({ message, onSave, onResend, onCancel })
|
||||
onResend(updatedBlocks)
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>, blockId: string) => {
|
||||
if (message.role !== 'user') {
|
||||
return
|
||||
}
|
||||
|
||||
// keep the same enter behavior as inputbar
|
||||
const isEnterPressed = event.key === 'Enter' && !event.nativeEvent.isComposing
|
||||
if (isEnterPressed) {
|
||||
if (isSendMessageKeyPressed(event, sendMessageShortcut)) {
|
||||
handleResend()
|
||||
return event.preventDefault()
|
||||
} else {
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault()
|
||||
|
||||
if (isEnterPressed && !event.shiftKey && sendMessageShortcut === 'Enter') {
|
||||
handleResend()
|
||||
return event.preventDefault()
|
||||
}
|
||||
const textArea = textareaRef.current?.resizableTextArea?.textArea
|
||||
if (textArea) {
|
||||
const start = textArea.selectionStart
|
||||
const end = textArea.selectionEnd
|
||||
const text = textArea.value
|
||||
const newText = text.substring(0, start) + '\n' + text.substring(end)
|
||||
|
||||
if (sendMessageShortcut === 'Shift+Enter' && isEnterPressed && event.shiftKey) {
|
||||
handleResend()
|
||||
return event.preventDefault()
|
||||
}
|
||||
//same with onChange()
|
||||
handleTextChange(blockId, newText)
|
||||
|
||||
if (sendMessageShortcut === 'Ctrl+Enter' && isEnterPressed && event.ctrlKey) {
|
||||
handleResend()
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Command+Enter' && isEnterPressed && event.metaKey) {
|
||||
handleResend()
|
||||
return event.preventDefault()
|
||||
// set cursor position in the next render cycle
|
||||
setTimeout(() => {
|
||||
textArea.selectionStart = textArea.selectionEnd = start + 1
|
||||
resizeTextArea() // trigger resizeTextArea
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +220,7 @@ const MessageBlockEditor: FC<Props> = ({ message, onSave, onResend, onCancel })
|
||||
handleTextChange(block.id, e.target.value)
|
||||
resizeTextArea()
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyDown={(e) => handleKeyDown(e, block.id)}
|
||||
autoFocus
|
||||
contextMenu="true"
|
||||
spellCheck={false}
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
import { CheckOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import {
|
||||
DEFAULT_CONTEXTCOUNT,
|
||||
DEFAULT_MAX_TOKENS,
|
||||
DEFAULT_TEMPERATURE,
|
||||
isMac,
|
||||
isWindows
|
||||
} from '@renderer/config/constant'
|
||||
import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import {
|
||||
isOpenAIModel,
|
||||
isSupportedFlexServiceTier,
|
||||
@ -59,6 +53,7 @@ import {
|
||||
TranslateLanguageVarious
|
||||
} from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
||||
import { Button, Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { CircleHelp, Settings2 } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
@ -670,10 +665,11 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
value={sendMessageShortcut}
|
||||
menuItemSelectedIcon={<CheckOutlined />}
|
||||
options={[
|
||||
{ value: 'Enter', label: 'Enter' },
|
||||
{ value: 'Shift+Enter', label: 'Shift + Enter' },
|
||||
{ value: 'Ctrl+Enter', label: 'Ctrl + Enter' },
|
||||
{ value: 'Command+Enter', label: `${isMac ? '⌘' : isWindows ? 'Win' : 'Super'} + Enter` }
|
||||
{ value: 'Enter', label: getSendMessageShortcutLabel('Enter') },
|
||||
{ value: 'Ctrl+Enter', label: getSendMessageShortcutLabel('Ctrl+Enter') },
|
||||
{ value: 'Alt+Enter', label: getSendMessageShortcutLabel('Alt+Enter') },
|
||||
{ value: 'Command+Enter', label: getSendMessageShortcutLabel('Command+Enter') },
|
||||
{ value: 'Shift+Enter', label: getSendMessageShortcutLabel('Shift+Enter') }
|
||||
]}
|
||||
onChange={(value) => setSendMessageShortcut(value as SendMessageShortcut)}
|
||||
style={{ width: 135 }}
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
|
||||
import { WebDAVSyncState } from './backup'
|
||||
|
||||
export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter'
|
||||
export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter' | 'Alt+Enter'
|
||||
|
||||
export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files'
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import Logger from '@renderer/config/logger'
|
||||
import type { SendMessageShortcut } from '@renderer/store/settings'
|
||||
import { FileType } from '@renderer/types'
|
||||
|
||||
export const getFilesFromDropEvent = async (e: React.DragEvent<HTMLDivElement>): Promise<FileType[]> => {
|
||||
@ -58,3 +60,47 @@ export const getFilesFromDropEvent = async (e: React.DragEvent<HTMLDivElement>):
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// convert send message shortcut to human readable label
|
||||
export const getSendMessageShortcutLabel = (shortcut: SendMessageShortcut) => {
|
||||
switch (shortcut) {
|
||||
case 'Enter':
|
||||
return 'Enter'
|
||||
case 'Ctrl+Enter':
|
||||
return 'Ctrl + Enter'
|
||||
case 'Alt+Enter':
|
||||
return `${isMac ? '⌥' : 'Alt'} + Enter`
|
||||
case 'Command+Enter':
|
||||
return `${isMac ? '⌘' : isWindows ? 'Win' : 'Super'} + Enter`
|
||||
case 'Shift+Enter':
|
||||
return 'Shift + Enter'
|
||||
default:
|
||||
return shortcut
|
||||
}
|
||||
}
|
||||
|
||||
// check if the send message key is pressed in textarea
|
||||
export const isSendMessageKeyPressed = (
|
||||
event: React.KeyboardEvent<HTMLTextAreaElement>,
|
||||
shortcut: SendMessageShortcut
|
||||
) => {
|
||||
let isSendMessageKeyPressed = false
|
||||
switch (shortcut) {
|
||||
case 'Enter':
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) isSendMessageKeyPressed = true
|
||||
break
|
||||
case 'Ctrl+Enter':
|
||||
if (event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey) isSendMessageKeyPressed = true
|
||||
break
|
||||
case 'Command+Enter':
|
||||
if (event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey) isSendMessageKeyPressed = true
|
||||
break
|
||||
case 'Alt+Enter':
|
||||
if (event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey) isSendMessageKeyPressed = true
|
||||
break
|
||||
case 'Shift+Enter':
|
||||
if (event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) isSendMessageKeyPressed = true
|
||||
break
|
||||
}
|
||||
return isSendMessageKeyPressed
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user