mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
feat: make Ctrl/Cmd+Enter insert a newline when Enter sends
Ctrl/Cmd+Enter did the same thing as a bare Enter whenever Enter-to-send was on, so the chord was spent duplicating a key that was already bound and users had only Shift+Enter to break a line. Read the modifier as the inverse of plain Enter instead: it sends when Enter writes a newline, and writes one when Enter sends. The rebound path in resolveSubmitOverrideAction already resolved it this way, so this only brings the default path in line with it. The during-run table claims Ctrl/Cmd+Enter for the alternate send action and resolves before the idle tail, so steering, queueing and interrupting are untouched. Answer mode hands the newline chords to the normal path so the free-form answer box breaks lines like the composer; Enter on a highlighted option still confirms the selection. Closes #12030
This commit is contained in:
parent
60498036ed
commit
f79d851501
4 changed files with 52 additions and 10 deletions
|
|
@ -348,8 +348,10 @@ export default function useAskAnswerMode(conversationId?: string | null) {
|
|||
const composerText = e.currentTarget.value;
|
||||
if (composerText.trim().length > 0) {
|
||||
// The composer IS the free-form answer box: Enter submits the typed
|
||||
// text (before useTextarea's submitting-lock can swallow it).
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
// text (before useTextarea's submitting-lock can swallow it). The
|
||||
// newline chords are left to the normal path so the answer box breaks
|
||||
// lines the same way the composer does.
|
||||
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
e.preventDefault();
|
||||
return submitText(composerText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@
|
|||
"com_nav_info_code_background": "Code executions can run in the background by default: the conversation continues immediately while the code runs, and the model retrieves the result later with the background task tool. Output and generated files still attach to the original code run. Turn this off to require every code execution to finish before the conversation continues. Requires the app-level background tools capability.",
|
||||
"com_nav_info_default_temporary_chat": "When enabled, new chats will start with temporary chat mode activated by default. Temporary chats are not saved to your history.",
|
||||
"com_nav_info_during_run_action": "Queued messages send automatically when the reply finishes. Sending into the reply adds your message to the response the assistant is writing right now.",
|
||||
"com_nav_info_enter_to_send": "When enabled, pressing `ENTER` will send your message. When disabled, pressing Enter will add a new line, and you'll need to press `CTRL + ENTER` / `⌘ + ENTER` to send your message.",
|
||||
"com_nav_info_enter_to_send": "When enabled, pressing `ENTER` will send your message, and `SHIFT + ENTER` or `CTRL + ENTER` / `⌘ + ENTER` will add a new line. When disabled, pressing Enter will add a new line, and you'll need to press `CTRL + ENTER` / `⌘ + ENTER` to send your message.",
|
||||
"com_nav_info_fork_change_default": "`Visible messages only` includes just the direct path to the selected message. `Include related branches` adds branches along the path. `Include all to/from here` includes all connected messages and branches.",
|
||||
"com_nav_info_fork_split_target_setting": "When enabled, forking will commence from the target message to the latest message in the conversation, according to the behavior selected.",
|
||||
"com_nav_info_latex_parsing": "When enabled, LaTeX code in messages will be rendered as mathematical equations. Disabling this may improve performance if you don't need LaTeX rendering.",
|
||||
|
|
@ -1963,7 +1963,6 @@
|
|||
"com_ui_steer_failed": "Steering failed",
|
||||
"com_ui_steer_failed_inline": "Couldn't add to this reply",
|
||||
"com_ui_steer_in_flight": "Steering",
|
||||
"com_ui_steer_in_flight_preempt": "Interrupting",
|
||||
"com_ui_steer_interrupts_default": "Steering interrupts generation",
|
||||
"com_ui_steer_interrupts_default_info": "When on, Enter stops the response at the next safe point instead of waiting for the agent's next tool step. Either way the partial answer is kept and the response continues.",
|
||||
"com_ui_steer_interrupts_enable_info": "Switches the during-run default to steering and stops the response at the next safe point. The partial answer is kept and the response continues.",
|
||||
|
|
|
|||
|
|
@ -382,11 +382,43 @@ describe('resolveComposerKeyDown', () => {
|
|||
it('keeps idle Enter semantics', () => {
|
||||
expect(resolveComposerKeyDown(keydown(), idle)).toBe('submit');
|
||||
expect(resolveComposerKeyDown(keydown(), { ...idle, enterToSend: false })).toBe('newline');
|
||||
expect(resolveComposerKeyDown(keydown({ ctrlKey: true }), idle)).toBe('submit');
|
||||
expect(resolveComposerKeyDown(keydown({ shiftKey: true }), idle)).toBe('none');
|
||||
expect(resolveComposerKeyDown(new KeyboardEvent('keydown', { key: 'a' }), idle)).toBe('none');
|
||||
});
|
||||
|
||||
/* The modifier is the inverse of plain Enter, so whichever of the two sends,
|
||||
the other writes a newline and neither preference leaves the user without
|
||||
a way to break a line. */
|
||||
it('inverts Ctrl/Cmd+Enter against the Enter-to-send preference while idle', () => {
|
||||
expect(resolveComposerKeyDown(keydown({ ctrlKey: true }), idle)).toBe('newline');
|
||||
expect(resolveComposerKeyDown(keydown({ metaKey: true }), idle)).toBe('newline');
|
||||
expect(
|
||||
resolveComposerKeyDown(keydown({ ctrlKey: true }), { ...idle, enterToSend: false }),
|
||||
).toBe('submit');
|
||||
expect(
|
||||
resolveComposerKeyDown(keydown({ metaKey: true }), { ...idle, enterToSend: false }),
|
||||
).toBe('submit');
|
||||
});
|
||||
|
||||
it('leaves both newline chords available whichever way Enter is bound', () => {
|
||||
expect(resolveComposerKeyDown(keydown({ ctrlKey: true }), idle)).toBe('newline');
|
||||
expect(resolveComposerKeyDown(keydown({ shiftKey: true }), idle)).toBe('none');
|
||||
const newlineOnEnter = { ...idle, enterToSend: false };
|
||||
expect(resolveComposerKeyDown(keydown(), newlineOnEnter)).toBe('newline');
|
||||
expect(resolveComposerKeyDown(keydown({ shiftKey: true }), newlineOnEnter)).toBe('newline');
|
||||
});
|
||||
|
||||
/* The during-run table claims Ctrl/Cmd+Enter for the alternate send action,
|
||||
and it resolves before the idle tail, so the newline chord must not reach
|
||||
into a live run and swallow it. */
|
||||
it('does not let the idle newline chord displace the during-run actions', () => {
|
||||
expect(resolveComposerKeyDown(keydown({ ctrlKey: true }), duringRun)).toBe('other');
|
||||
expect(resolveComposerKeyDown(keydown({ ctrlKey: true, shiftKey: true }), duringRun)).toBe(
|
||||
'preempt',
|
||||
);
|
||||
expect(resolveComposerKeyDown(keydown({ altKey: true }), duringRun)).toBe('interrupt');
|
||||
});
|
||||
|
||||
it('resolves through the submit override while idle', () => {
|
||||
const ctx = { ...idle, submitOverride: makeBinding({ alt: true, key: 'Enter' }) };
|
||||
expect(resolveComposerKeyDown(keydown({ altKey: true }), ctx)).toBe('submit');
|
||||
|
|
|
|||
|
|
@ -295,11 +295,20 @@ export function resolveComposerKeyDown(
|
|||
return resolveSubmitOverrideAction(binding, ctx.submitOverride, ctx.enterToSend);
|
||||
}
|
||||
const isCtrlEnter = e.ctrlKey || e.metaKey;
|
||||
if (!ctx.enterToSend && !isCtrlEnter && !ctx.isComposing) {
|
||||
return 'newline';
|
||||
}
|
||||
if ((!e.shiftKey || isCtrlEnter) && !ctx.isComposing) {
|
||||
return 'submit';
|
||||
if (!ctx.isComposing) {
|
||||
/** The modifier is always the inverse of plain Enter: it sends when Enter
|
||||
* writes a newline, and writes one when Enter sends. The rebound path in
|
||||
* `resolveSubmitOverrideAction` already reads it this way, so the default
|
||||
* path only looked different because it had no newline branch. */
|
||||
if (isCtrlEnter) {
|
||||
return ctx.enterToSend ? 'newline' : 'submit';
|
||||
}
|
||||
if (!ctx.enterToSend) {
|
||||
return 'newline';
|
||||
}
|
||||
if (!e.shiftKey) {
|
||||
return 'submit';
|
||||
}
|
||||
}
|
||||
if (!e.shiftKey) {
|
||||
return 'block';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue