Cherry Studio 消息输入换行改为可配置快捷键:发送 / 换行 / Steer 三角色互斥的设计与迁移指南
2026/9/20 22:21:53 网站建设 项目流程
  • 人工智能
  • 大模型
  • AI 应用
  • 交互助手
  • 本地部署

【免费下载链接】cherry-studio

🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端

项目地址:https://gitcode.com/CherryHQ/cherry-studio
点击查看免费下载

Cherry Studio 从 v2 重构的某次变更(PR #18642,2026-08-15)起,将聊天、Agent 与绘画三类消息输入框的换行行为从「Enter / Shift+Enter / Cmd+Ctrl+Enter 一律插入换行」改为「单一可配置的换行快捷键」,并顺势引入Steer(转向)快捷键用于在 Agent 运行中把草稿直接送入当前轮次。本文以官方 breaking-change 文档为主线,结合仓库源码说明新快捷键模型、互斥解析逻辑与底层按键分发实现,帮助用户与开发者理解变化并完成迁移。

变更概览:换行不再是「三键通用」

在旧版本中,聊天、Agent、绘画消息输入框对EnterShift+EnterCmd/Ctrl+Enter一视同仁——任意组合都会插入一个换行符。从 2026-08-15 起(变更文档见 v2-refactor-temp/docs/breaking-changes/2026-08-15-composer-mod-enter-no-longer-inserts-line-break.md),行为被收敛为:

  • 换行只由一个可配置快捷键完成,默认Shift+Enter
  • 配置入口位于Settings > Chat > Input > Line break shortcut
  • 发送(send)、换行(line break)、Steer 三个角色必须使用不同的按键组合,不允许互相占用;
  • 默认情况下Cmd/Ctrl+Enter处于空闲状态,并成为Steer 快捷键:在 Agent 运行时,按下它会把当前草稿送入正在进行的这一轮对话,而不是排入队列等待。

文档同时指出:任何既不属于发送、也不属于换行、也不属于 Steer 的 Enter 组合,将什么都不做(不再退回为插入换行)。

为什么这对用户有影响

这是一次明显的行为破坏(breaking change),对两类用户影响最大:

  1. 习惯用Cmd/Ctrl+Enter换行的用户:该组合不再插入换行;在 Agent 会话中它现在会执行 Steer,即把草稿直接注入当前运行轮次。
  2. Windows / Linux 用户:发送快捷键失去了Win/Super+Enter选项。该组合被操作系统保留,在非 macOS 平台上永远不会触发(文档称其 "was reserved by the OS and could never fire")。如果此前选择了这一项,迁移后发送将自动回落到Ctrl+Enter

另外,三个快捷键的标签显示也切换到应用的共享按键词表(shared key vocabulary):例如 macOS 上显示为⌘Enter而非⌘ + Enter,与其他设置页(Settings > Shortcuts)的写法保持一致。

新快捷键模型:发送 / 换行 / Steer 三角色互斥

新模型把输入框的 Enter 组合划分为三个互斥角色,每个角色各占一个组合,且不可复用:

角色默认值(macOS)默认值(Windows / Linux)说明
Send(发送)EnterEnter发送当前草稿
Line break(换行)Shift+EnterShift+Enter在输入框中插入换行
Steer(转向)⌘Enter(Cmd+Enter)Ctrl+EnterAgent 运行时,把草稿送入当前轮次而非排队

三者互斥的规则体现在设置界面中:在 ChatPreferenceSections.tsx 中,每个下拉框都会隐藏另外两个角色已经占用的按键

// Send / newline / steer are mutually exclusive: each select hides the two keys already in use. // Bindings are arrays, so the select trades in their ids and maps back on change. const shortcutItemsExcluding = useCallback((...taken: ComposerShortcut[]): SelectOption<string>[] => { const takenIds = taken.map(composerShortcutId) return COMPOSER_SHORTCUTS.filter((shortcut) => !takenIds.includes(composerShortcutId(shortcut))).map( (shortcut) => ({ value: composerShortcutId(shortcut), label: getComposerShortcutLabel(shortcut) }) ) }, [])

发送下拉排除当前换行与 Steer 组合(L211),换行下拉排除发送与 Steer(L229),Steer 下拉排除发送与换行(L247),从 UI 层面就杜绝了按键冲突。

源码级解析:快捷键解析与互斥的兜底逻辑

互斥不仅停留在 UI 层面,存储与解析层同样有兜底。核心实现位于 src/renderer/utils/input.ts:

可选项集合(L85-L91):

export const COMPOSER_SHORTCUTS: readonly ComposerShortcut[] = [ ['Enter'], ['Shift', 'Enter'], ['CommandOrControl', 'Enter'], ['Alt', 'Enter'], ...(isMac ? [['Ctrl', 'Enter'] as ComposerShortcut] : []) ]

注意CommandOrControl在 macOS 上代表 Command(⌘),在其他平台代表 Control(Ctrl),因此纯 Ctrl 组合只在 macOS 上才是独立选项(macOS 上 ⌘ 与 Ctrl 是两颗不同的键)。

默认值解析(L126-L135):

export const resolveSendShortcut = (stored: unknown): ComposerShortcut => resolveComposerShortcut(stored, [], ['Enter']) export const resolveNewlineShortcut = (stored: unknown, send: ComposerShortcut): ComposerShortcut => resolveComposerShortcut(stored, [send], ['Shift', 'Enter']) export const resolveSteerShortcut = ( stored: unknown, send: ComposerShortcut, newline: ComposerShortcut ): ComposerShortcut => resolveComposerShortcut(stored, [send, newline], ['CommandOrControl', 'Enter'])

三者各自的偏好(preference)默认值定义为:发送['Enter']、换行与 Steer 均为null(表示未覆盖,使用生效默认值),见 preferenceSchemas.ts。换行与 Steer 存null的设计意味着:即使用户修改过发送快捷键,未显式覆盖的换行 / Steer 也会随发送键自动漂移到新的空闲组合。

互斥兜底(resolveComposerShortcut):如果存储值被另一角色占用、或首选默认值被占用,则回落到第一个空闲组合;极端情况下才使用首选值兜底:

const resolveComposerShortcut = ( stored: unknown, taken: readonly ComposerShortcut[], preferred: ComposerShortcut ): ComposerShortcut => { const takenIds = taken.map(composerShortcutId) const shortcut = normalizeComposerShortcut(stored) if (shortcut && !takenIds.includes(composerShortcutId(shortcut))) return shortcut if (!takenIds.includes(composerShortcutId(preferred))) return preferred return COMPOSER_SHORTCUTS.find((candidate) => !takenIds.includes(composerShortcutId(candidate))) ?? preferred }

旧值迁移(LEGACY_COMPOSER_SHORTCUTS):v1(以及 v2 早期)存储的是五种固定字符串而非绑定数组,迁移映射中'Command+Enter'当年匹配的是Meta 键,即非 macOS 上的 Win/Super 键——这正是文档所说"被 OS 保留、永不触发"的历史根源。迁移后该组合统一映射为['CommandOrControl', 'Enter'],因此 Windows/Linux 上先前选择它的人现在实际以Ctrl+Enter发送。

源码级解析:输入框按键分发与 IME 防护

按键的实际分发发生在共享输入框组件 ComposerSurface.tsx。其onKeyDown处理逻辑严格遵循"Steer 优先于 Send、其余 Enter 组合一律吞掉"的优先级顺序,与运行时输入框(runtime surface)保持一致,避免两处逻辑漂移:

const isEnterPressed = (event.key === 'Enter' || event.key === 'NumpadEnter') && !event.nativeEvent.isComposing if (!isEnterPressed) return event.preventDefault() const isSteerPressed = !!props.steerShortcut && matchesComposerShortcut(event, props.steerShortcut) if (isSteerPressed || matchesComposerShortcut(event, sendMessageShortcut)) { // Holding the key must not send twice; holding the newline key still repeats. if (event.repeat) return if (props.sendDisabled) { showBlockedSendReason() } else if (isSteerPressed) { void props.onSendDraft(getFallbackDraft(), { steer: true }) } else { void props.onSendDraft(getFallbackDraft()) } return } if (matchesComposerShortcut(event, newlineShortcut)) insertFallbackNewline(event.currentTarget)

这段实现中有三个值得注意的工程细节:

  1. IME 组合防护!event.nativeEvent.isComposing确保中文等输入法组合过程中的 Enter(用于选词上屏)不会被误判为发送/换行,onCompositionStart/End同步维护组合状态(L299-L307);
  2. 按住防重复event.repeat使按住发送/Steer 不会重复触发,而按住换行键仍可连续插入空行;
  3. 未绑定组合被吞掉:既非 Steer、非 Send、也非 Newline 的 Enter 组合在event.preventDefault()之后直接返回,不再插入换行——与 breaking-change 文档描述完全一致。

按键匹配通过 matchesComposerShortcut 完成,它调用共享工具 getShortcutBindingFromKeyboardEvent 将键盘事件归一化为绑定数组(含平台差异:darwin 下metaKey→CommandOrControlctrlKey→Ctrl;其他平台ctrlKey→CommandOrControlmetaKey→Meta),再与目标快捷键比对。

Steer 的语义:送入当前轮次而非排队

Steer 的核心语义在 AgentComposer.tsx 的handleSendDraft中实现:

// Busy (streaming) → queue the follow-up; the head auto-drains when the session goes idle and // the dock lets the user steer/edit/remove items. The steer shortcut opts out of the queue and // falls through to the direct send below, mirroring the dock's "insert" action. if (isStreaming && !options?.steer) { enqueueFollowup(draft, payload) clearCurrentDraft() return }

即:Agent 正在流式输出(isStreaming)时,普通发送会把草稿排入后续队列(enqueue),等待当前轮次空闲后自动消化;而携带steer: true的发送则跳过队列直接发送,把草稿注入当前运行轮次。这与输入框 Dock 上的"insert"(插入当前轮)动作互为镜像——Steer 快捷键就是它的键盘入口。同时 Steer 会保留在 Dock 中并弹出 toast,与直接发送/自动排空路径的反馈一致(L1747、L1773)。

用户需要做什么

无需任何操作——迁移是自动的

  • Shift+Enter继续作为换行快捷键工作,行为与旧版一致;
  • 若想恢复Cmd/Ctrl+Enter换行的旧习惯,只需在Settings > Chat > Input > Line break shortcut中把它设为换行快捷键;
  • 设置为换行后,Steer 会按互斥规则自动让位到下一个空闲组合;
  • 若之前在 Windows/Linux 上选择了Win/Super+Enter作为发送键,现在将以Ctrl+Enter发送。

相关偏好键速查

新快捷键模型对应三个偏好键,定义与默认值见 preferenceSchemas.ts:

偏好键类型默认值说明
chat.input.send_message_shortcutComposerShortcut['Enter']发送快捷键
chat.input.newline_shortcutComposerShortcut \| nullnull换行快捷键,null表示使用生效默认值Shift+Enter
chat.input.steer_shortcutComposerShortcut \| nullnullSteer 快捷键,null表示使用生效默认值Cmd/Ctrl+Enter

三个角色的互斥约束由 resolveComposerShortcut 在运行时兜底,即使偏好数据被外部写入冲突值也不会导致两个角色共用一个按键。

  • 人工智能
  • 大模型
  • AI 应用
  • 交互助手
  • 本地部署

【免费下载链接】cherry-studio

🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端

项目地址:https://gitcode.com/CherryHQ/cherry-studio
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询