webui: add "Send message on Enter" setting (#21577)

* webui: make Enter to send chat a setting

* Shorten description

* Use isMobile hook from $lib/hooks

* Rebuild static output
This commit is contained in:
JvM 2026-04-09 12:26:27 +02:00 committed by GitHub
parent ddf03c6d9a
commit 4ef9301e4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 144 additions and 103 deletions

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@
<div style="display: contents"> <div style="display: contents">
<script> <script>
{ {
__sveltekit_6n4hpv = { __sveltekit_1ao0o9h = {
base: new URL('.', location).pathname.slice(0, -1) base: new URL('.', location).pathname.slice(0, -1)
}; };

View File

@ -294,11 +294,16 @@
} }
if (event.key === KeyboardKey.ENTER && !event.shiftKey && !isIMEComposing(event)) { if (event.key === KeyboardKey.ENTER && !event.shiftKey && !isIMEComposing(event)) {
event.preventDefault(); const isModifier = event.ctrlKey || event.metaKey;
const sendOnEnter = currentConfig.sendOnEnter !== false;
if (!canSubmit || disabled || isLoading || hasLoadingAttachments) return; if (sendOnEnter || isModifier) {
event.preventDefault();
onSubmit?.(); if (!canSubmit || disabled || isLoading || hasLoadingAttachments) return;
onSubmit?.();
}
} }
} }

View File

@ -1,17 +1,30 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/environment';
import { config } from '$lib/stores/settings.svelte';
interface Props { interface Props {
class?: string; class?: string;
show?: boolean; show?: boolean;
} }
let { class: className = '', show = true }: Props = $props(); let { class: className = '', show = true }: Props = $props();
let sendOnEnter = $derived(config().sendOnEnter !== false);
let modKey = browser && /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? 'Cmd' : 'Ctrl';
</script> </script>
{#if show} {#if show}
<div class="mt-6 items-center justify-center {className} hidden md:flex"> <div class="mt-6 items-center justify-center {className} hidden md:flex">
<p class="text-xs text-muted-foreground"> {#if sendOnEnter}
Press <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Enter</kbd> to send, <p class="text-xs text-muted-foreground">
<kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Shift + Enter</kbd> for new line Press <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Enter</kbd> to send,
</p> <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Shift + Enter</kbd> for new line
</p>
{:else}
<p class="text-xs text-muted-foreground">
Press <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">{modKey} + Enter</kbd> to send,
<kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Enter</kbd> for new line
</p>
{/if}
</div> </div>
{/if} {/if}

View File

@ -64,6 +64,11 @@
label: 'Paste long text to file length', label: 'Paste long text to file length',
type: SettingsFieldType.INPUT type: SettingsFieldType.INPUT
}, },
{
key: SETTINGS_KEYS.SEND_ON_ENTER,
label: 'Send message on Enter',
type: SettingsFieldType.CHECKBOX
},
{ {
key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT, key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
label: 'Copy text attachments as plain text', label: 'Copy text attachments as plain text',

View File

@ -22,6 +22,7 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean |
renderUserContentAsMarkdown: false, renderUserContentAsMarkdown: false,
alwaysShowSidebarOnDesktop: false, alwaysShowSidebarOnDesktop: false,
autoShowSidebarOnNewChat: true, autoShowSidebarOnNewChat: true,
sendOnEnter: true,
autoMicOnEmpty: false, autoMicOnEmpty: false,
fullHeightCodeBlocks: false, fullHeightCodeBlocks: false,
showRawModelNames: false, showRawModelNames: false,
@ -126,6 +127,8 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
'Always keep the sidebar visible on desktop instead of auto-hiding it.', 'Always keep the sidebar visible on desktop instead of auto-hiding it.',
autoShowSidebarOnNewChat: autoShowSidebarOnNewChat:
'Automatically show sidebar when starting a new chat. Disable to keep the sidebar hidden until you click on it.', 'Automatically show sidebar when starting a new chat. Disable to keep the sidebar hidden until you click on it.',
sendOnEnter:
'Use Enter to send messages and Shift + Enter for new lines. When disabled, use Ctrl/Cmd + Enter.',
autoMicOnEmpty: autoMicOnEmpty:
'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.', 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.',
fullHeightCodeBlocks: fullHeightCodeBlocks:

View File

@ -11,6 +11,7 @@ export const SETTINGS_KEYS = {
SYSTEM_MESSAGE: 'systemMessage', SYSTEM_MESSAGE: 'systemMessage',
PASTE_LONG_TEXT_TO_FILE_LEN: 'pasteLongTextToFileLen', PASTE_LONG_TEXT_TO_FILE_LEN: 'pasteLongTextToFileLen',
COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT: 'copyTextAttachmentsAsPlainText', COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT: 'copyTextAttachmentsAsPlainText',
SEND_ON_ENTER: 'sendOnEnter',
ENABLE_CONTINUE_GENERATION: 'enableContinueGeneration', ENABLE_CONTINUE_GENERATION: 'enableContinueGeneration',
PDF_AS_IMAGE: 'pdfAsImage', PDF_AS_IMAGE: 'pdfAsImage',
ASK_FOR_TITLE_CONFIRMATION: 'askForTitleConfirmation', ASK_FOR_TITLE_CONFIRMATION: 'askForTitleConfirmation',

View File

@ -239,6 +239,12 @@ export const SYNCABLE_PARAMETERS: SyncableParameter[] = [
serverKey: 'excludeReasoningFromContext', serverKey: 'excludeReasoningFromContext',
type: SyncableParameterType.BOOLEAN, type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
},
{
key: 'sendOnEnter',
serverKey: 'sendOnEnter',
type: SyncableParameterType.BOOLEAN,
canSync: true
} }
]; ];

View File

@ -37,6 +37,7 @@ import {
SETTING_CONFIG_DEFAULT, SETTING_CONFIG_DEFAULT,
USER_OVERRIDES_LOCALSTORAGE_KEY USER_OVERRIDES_LOCALSTORAGE_KEY
} from '$lib/constants'; } from '$lib/constants';
import { IsMobile } from '$lib/hooks/is-mobile.svelte';
import { ParameterSyncService } from '$lib/services/parameter-sync.service'; import { ParameterSyncService } from '$lib/services/parameter-sync.service';
import { serverStore } from '$lib/stores/server.svelte'; import { serverStore } from '$lib/stores/server.svelte';
import { import {
@ -122,6 +123,13 @@ class SettingsStore {
...savedVal ...savedVal
}; };
// Default sendOnEnter to false on mobile when the user has no saved preference
if (!('sendOnEnter' in savedVal)) {
if (new IsMobile().current) {
this.config.sendOnEnter = false;
}
}
// Load user overrides // Load user overrides
const savedOverrides = JSON.parse( const savedOverrides = JSON.parse(
localStorage.getItem(USER_OVERRIDES_LOCALSTORAGE_KEY) || '[]' localStorage.getItem(USER_OVERRIDES_LOCALSTORAGE_KEY) || '[]'