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:
parent
ddf03c6d9a
commit
4ef9301e4d
File diff suppressed because one or more lines are too long
|
|
@ -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)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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?.();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) || '[]'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue