feat(ui): add enter-to-submit toggle for prompt input
This commit is contained in:
@@ -60,6 +60,7 @@ const App: Component = () => {
|
||||
toggleShowTimelineTools,
|
||||
toggleAutoCleanupBlankSessions,
|
||||
toggleUsageMetrics,
|
||||
togglePromptSubmitOnEnter,
|
||||
setDiffViewMode,
|
||||
setToolOutputExpansion,
|
||||
setDiagnosticsExpansion,
|
||||
@@ -271,6 +272,7 @@ const App: Component = () => {
|
||||
toggleShowThinkingBlocks,
|
||||
toggleShowTimelineTools,
|
||||
toggleUsageMetrics,
|
||||
togglePromptSubmitOnEnter,
|
||||
setDiffViewMode,
|
||||
setToolOutputExpansion,
|
||||
setDiagnosticsExpansion,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getCommands } from "../stores/commands"
|
||||
import { showAlertDialog } from "../stores/alerts"
|
||||
import { useI18n } from "../lib/i18n"
|
||||
import { getLogger } from "../lib/logger"
|
||||
import { preferences } from "../stores/preferences"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
@@ -542,13 +543,46 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
if (showPicker()) {
|
||||
handlePickerClose()
|
||||
if (e.key === "Enter") {
|
||||
const isModified = e.metaKey || e.ctrlKey
|
||||
|
||||
// If the picker is open, Enter should select from it.
|
||||
if (!isModified && showPicker()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (submitOnEnter()) {
|
||||
// Swapped mode: Enter submits, Cmd/Ctrl+Enter inserts a newline.
|
||||
if (isModified) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
insertNewlineAtCursor()
|
||||
return
|
||||
}
|
||||
|
||||
// Shift+Enter always inserts a newline.
|
||||
if (e.shiftKey) {
|
||||
// If the picker is open, avoid selecting an item on Enter.
|
||||
if (showPicker()) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
handleSend()
|
||||
return
|
||||
}
|
||||
|
||||
// Default: Cmd/Ctrl+Enter submits.
|
||||
if (isModified) {
|
||||
e.preventDefault()
|
||||
if (showPicker()) {
|
||||
handlePickerClose()
|
||||
}
|
||||
handleSend()
|
||||
return
|
||||
}
|
||||
handleSend()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === "ArrowUp") {
|
||||
@@ -1056,6 +1090,25 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
: { key: "!", text: t("promptInput.hints.shell.enable") }
|
||||
const commandHint = () => ({ key: "/", text: t("promptInput.hints.commands") })
|
||||
|
||||
const submitOnEnter = () => preferences().promptSubmitOnEnter
|
||||
|
||||
function insertNewlineAtCursor() {
|
||||
const textarea = textareaRef
|
||||
const current = prompt()
|
||||
const start = textarea ? textarea.selectionStart : current.length
|
||||
const end = textarea ? textarea.selectionEnd : current.length
|
||||
const nextValue = current.substring(0, start) + "\n" + current.substring(end)
|
||||
const nextCursor = start + 1
|
||||
|
||||
setPrompt(nextValue)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!textareaRef) return
|
||||
textareaRef.focus()
|
||||
textareaRef.setSelectionRange(nextCursor, nextCursor)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const shouldShowOverlay = () => prompt().length === 0
|
||||
|
||||
const instance = () => getActiveInstance()
|
||||
@@ -1142,7 +1195,19 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
fallback={
|
||||
<>
|
||||
<span class="prompt-overlay-text">
|
||||
<Kbd>Enter</Kbd> {t("promptInput.overlay.newLine")} • <Kbd shortcut="cmd+enter" /> {t("promptInput.overlay.send")} • <Kbd>@</Kbd> {t("promptInput.overlay.filesAgents")} • <Kbd>↑↓</Kbd> {t("promptInput.overlay.history")}
|
||||
<Show
|
||||
when={submitOnEnter()}
|
||||
fallback={
|
||||
<>
|
||||
<Kbd>Enter</Kbd> {t("promptInput.overlay.newLine")} • <Kbd shortcut="cmd+enter" /> {t("promptInput.overlay.send")}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<Kbd>Enter</Kbd> {t("promptInput.overlay.send")} • <Kbd shortcut="cmd+enter" /> {t("promptInput.overlay.newLine")}
|
||||
</>
|
||||
</Show>
|
||||
{" "}• <Kbd>@</Kbd> {t("promptInput.overlay.filesAgents")} • <Kbd>↑↓</Kbd> {t("promptInput.overlay.history")}
|
||||
</span>
|
||||
<Show when={attachments().length > 0}>
|
||||
<span class="prompt-overlay-text prompt-overlay-muted">{t("promptInput.overlay.attachments", { count: attachments().length })}</span>
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface UseCommandsOptions {
|
||||
toggleShowTimelineTools: () => void
|
||||
toggleUsageMetrics: () => void
|
||||
toggleAutoCleanupBlankSessions: () => void
|
||||
togglePromptSubmitOnEnter: () => void
|
||||
setDiffViewMode: (mode: "split" | "unified") => void
|
||||
setToolOutputExpansion: (mode: ExpansionPreference) => void
|
||||
setDiagnosticsExpansion: (mode: ExpansionPreference) => void
|
||||
@@ -423,6 +424,18 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
},
|
||||
})
|
||||
|
||||
commandRegistry.register({
|
||||
id: "prompt-submit-shortcut",
|
||||
label: () =>
|
||||
options.preferences().promptSubmitOnEnter
|
||||
? tGlobal("commands.promptSubmitShortcut.label.swapped")
|
||||
: tGlobal("commands.promptSubmitShortcut.label.default"),
|
||||
description: () => tGlobal("commands.promptSubmitShortcut.description"),
|
||||
category: "Input & Focus",
|
||||
keywords: () => splitKeywords("commands.promptSubmitShortcut.keywords"),
|
||||
action: options.togglePromptSubmitOnEnter,
|
||||
})
|
||||
|
||||
commandRegistry.register({
|
||||
id: "thinking",
|
||||
label: () => tGlobal(options.preferences().showThinkingBlocks ? "commands.thinkingBlocks.label.hide" : "commands.thinkingBlocks.label.show"),
|
||||
|
||||
@@ -82,6 +82,11 @@ export const commandMessages = {
|
||||
"commands.clearInput.description": "Clear the prompt textarea",
|
||||
"commands.clearInput.keywords": "clear, reset",
|
||||
|
||||
"commands.promptSubmitShortcut.label.default": "Enter: New Line, Cmd/Ctrl+Enter: Submit Prompt",
|
||||
"commands.promptSubmitShortcut.label.swapped": "Enter: Submit Prompt, Cmd/Ctrl+Enter: New Line",
|
||||
"commands.promptSubmitShortcut.description": "Swap Enter and Cmd/Ctrl+Enter behavior in the prompt input",
|
||||
"commands.promptSubmitShortcut.keywords": "enter, cmd, ctrl, submit, send, newline, shortcut, keybind, prompt",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "Show Thinking",
|
||||
"commands.thinkingBlocks.label.hide": "Hide Thinking",
|
||||
"commands.thinkingBlocks.description": "Show or hide AI thinking sections",
|
||||
|
||||
@@ -82,6 +82,11 @@ export const commandMessages = {
|
||||
"commands.clearInput.description": "Borrar el área de texto del prompt",
|
||||
"commands.clearInput.keywords": "limpiar, reiniciar",
|
||||
|
||||
"commands.promptSubmitShortcut.label.default": "Enter: Nueva linea, Cmd/Ctrl+Enter: Enviar prompt",
|
||||
"commands.promptSubmitShortcut.label.swapped": "Enter: Enviar prompt, Cmd/Ctrl+Enter: Nueva linea",
|
||||
"commands.promptSubmitShortcut.description": "Intercambiar el comportamiento de Enter y Cmd/Ctrl+Enter en la entrada de prompt",
|
||||
"commands.promptSubmitShortcut.keywords": "enter, enviar, salto de linea, atajo, teclado, cmd, ctrl, prompt",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "Mostrar pensamiento",
|
||||
"commands.thinkingBlocks.label.hide": "Ocultar pensamiento",
|
||||
"commands.thinkingBlocks.description": "Mostrar u ocultar secciones de pensamiento de IA",
|
||||
|
||||
@@ -82,6 +82,11 @@ export const commandMessages = {
|
||||
"commands.clearInput.description": "Effacer la zone de texte du prompt",
|
||||
"commands.clearInput.keywords": "effacer, réinitialiser, prompt",
|
||||
|
||||
"commands.promptSubmitShortcut.label.default": "Entree: Nouvelle ligne, Cmd/Ctrl+Entree: Envoyer le prompt",
|
||||
"commands.promptSubmitShortcut.label.swapped": "Entree: Envoyer le prompt, Cmd/Ctrl+Entree: Nouvelle ligne",
|
||||
"commands.promptSubmitShortcut.description": "Echanger le comportement de Entree et Cmd/Ctrl+Entree dans la saisie du prompt",
|
||||
"commands.promptSubmitShortcut.keywords": "entree, envoyer, nouvelle ligne, raccourci, cmd, ctrl, prompt",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "Afficher la réflexion",
|
||||
"commands.thinkingBlocks.label.hide": "Masquer la réflexion",
|
||||
"commands.thinkingBlocks.description": "Afficher ou masquer les sections de réflexion de l'IA",
|
||||
|
||||
@@ -82,6 +82,11 @@ export const commandMessages = {
|
||||
"commands.clearInput.description": "プロンプト入力欄をクリア",
|
||||
"commands.clearInput.keywords": "クリア, リセット, clear, reset",
|
||||
|
||||
"commands.promptSubmitShortcut.label.default": "Enter: 改行, Cmd/Ctrl+Enter: プロンプト送信",
|
||||
"commands.promptSubmitShortcut.label.swapped": "Enter: プロンプト送信, Cmd/Ctrl+Enter: 改行",
|
||||
"commands.promptSubmitShortcut.description": "プロンプト入力で Enter と Cmd/Ctrl+Enter の動作を入れ替え",
|
||||
"commands.promptSubmitShortcut.keywords": "enter, 送信, 改行, ショートカット, cmd, ctrl, プロンプト",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "思考を表示",
|
||||
"commands.thinkingBlocks.label.hide": "思考を非表示",
|
||||
"commands.thinkingBlocks.description": "AI の思考セクションを表示/非表示",
|
||||
|
||||
@@ -82,6 +82,11 @@ export const commandMessages = {
|
||||
"commands.clearInput.description": "Очистить поле prompt",
|
||||
"commands.clearInput.keywords": "очистить, сброс",
|
||||
|
||||
"commands.promptSubmitShortcut.label.default": "Enter: Новая строка, Cmd/Ctrl+Enter: Отправить промпт",
|
||||
"commands.promptSubmitShortcut.label.swapped": "Enter: Отправить промпт, Cmd/Ctrl+Enter: Новая строка",
|
||||
"commands.promptSubmitShortcut.description": "Поменять местами Enter и Cmd/Ctrl+Enter в поле ввода промпта",
|
||||
"commands.promptSubmitShortcut.keywords": "enter, отправить, новая строка, сочетание, cmd, ctrl, промпт",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "Показать размышления",
|
||||
"commands.thinkingBlocks.label.hide": "Скрыть размышления",
|
||||
"commands.thinkingBlocks.description": "Показать или скрыть секции размышлений ИИ",
|
||||
|
||||
@@ -82,6 +82,11 @@ export const commandMessages = {
|
||||
"commands.clearInput.description": "清空 prompt 输入框",
|
||||
"commands.clearInput.keywords": "clear, reset, 清空, 重置",
|
||||
|
||||
"commands.promptSubmitShortcut.label.default": "Enter: 换行, Cmd/Ctrl+Enter: 提交提示词",
|
||||
"commands.promptSubmitShortcut.label.swapped": "Enter: 提交提示词, Cmd/Ctrl+Enter: 换行",
|
||||
"commands.promptSubmitShortcut.description": "在提示词输入框中交换 Enter 与 Cmd/Ctrl+Enter 的行为",
|
||||
"commands.promptSubmitShortcut.keywords": "enter, 换行, 提交, 发送, 快捷键, cmd, ctrl, 提示词",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "显示思考",
|
||||
"commands.thinkingBlocks.label.hide": "隐藏思考",
|
||||
"commands.thinkingBlocks.description": "显示或隐藏 AI 思考部分",
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface Preferences {
|
||||
showThinkingBlocks: boolean
|
||||
thinkingBlocksExpansion: ExpansionPreference
|
||||
showTimelineTools: boolean
|
||||
promptSubmitOnEnter: boolean
|
||||
lastUsedBinary?: string
|
||||
locale?: string
|
||||
environmentVariables: Record<string, string>
|
||||
@@ -73,6 +74,7 @@ const defaultPreferences: Preferences = {
|
||||
showThinkingBlocks: false,
|
||||
thinkingBlocksExpansion: "expanded",
|
||||
showTimelineTools: true,
|
||||
promptSubmitOnEnter: false,
|
||||
environmentVariables: {},
|
||||
modelRecents: [],
|
||||
modelFavorites: [],
|
||||
@@ -120,6 +122,7 @@ function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelectio
|
||||
showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks,
|
||||
thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultPreferences.thinkingBlocksExpansion,
|
||||
showTimelineTools: sanitized.showTimelineTools ?? defaultPreferences.showTimelineTools,
|
||||
promptSubmitOnEnter: sanitized.promptSubmitOnEnter ?? defaultPreferences.promptSubmitOnEnter,
|
||||
lastUsedBinary: sanitized.lastUsedBinary ?? defaultPreferences.lastUsedBinary,
|
||||
locale: sanitized.locale ?? defaultPreferences.locale,
|
||||
environmentVariables,
|
||||
@@ -381,6 +384,10 @@ function toggleUsageMetrics(): void {
|
||||
updatePreferences({ showUsageMetrics: !preferences().showUsageMetrics })
|
||||
}
|
||||
|
||||
function togglePromptSubmitOnEnter(): void {
|
||||
updatePreferences({ promptSubmitOnEnter: !preferences().promptSubmitOnEnter })
|
||||
}
|
||||
|
||||
function toggleAutoCleanupBlankSessions(): void {
|
||||
const nextValue = !preferences().autoCleanupBlankSessions
|
||||
log.info("toggle auto cleanup", { value: nextValue })
|
||||
@@ -490,6 +497,7 @@ interface ConfigContextValue {
|
||||
toggleShowTimelineTools: typeof toggleShowTimelineTools
|
||||
toggleUsageMetrics: typeof toggleUsageMetrics
|
||||
toggleAutoCleanupBlankSessions: typeof toggleAutoCleanupBlankSessions
|
||||
togglePromptSubmitOnEnter: typeof togglePromptSubmitOnEnter
|
||||
|
||||
setDiffViewMode: typeof setDiffViewMode
|
||||
setToolOutputExpansion: typeof setToolOutputExpansion
|
||||
@@ -526,6 +534,7 @@ const configContextValue: ConfigContextValue = {
|
||||
toggleShowTimelineTools,
|
||||
toggleUsageMetrics,
|
||||
toggleAutoCleanupBlankSessions,
|
||||
togglePromptSubmitOnEnter,
|
||||
setDiffViewMode,
|
||||
setToolOutputExpansion,
|
||||
setDiagnosticsExpansion,
|
||||
@@ -585,6 +594,7 @@ export {
|
||||
toggleShowTimelineTools,
|
||||
toggleAutoCleanupBlankSessions,
|
||||
toggleUsageMetrics,
|
||||
togglePromptSubmitOnEnter,
|
||||
recentFolders,
|
||||
addRecentFolder,
|
||||
removeRecentFolder,
|
||||
|
||||
Reference in New Issue
Block a user