mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-14 05:15:18 +00:00
refac
This commit is contained in:
parent
e361606c61
commit
3c7e739b3c
8 changed files with 119 additions and 95 deletions
|
|
@ -1051,6 +1051,12 @@
|
|||
dispatch('submit', prompt);
|
||||
}}
|
||||
>
|
||||
<button
|
||||
id="generate-message-pair-button"
|
||||
class="hidden"
|
||||
on:click={() => createMessagePair(prompt)}
|
||||
/>
|
||||
|
||||
<div
|
||||
id="message-input-container"
|
||||
class="flex-1 flex flex-col relative w-full shadow-lg rounded-3xl border {$temporaryChatEnabled
|
||||
|
|
@ -1255,24 +1261,6 @@
|
|||
stopResponse();
|
||||
}
|
||||
|
||||
// Command/Ctrl + Shift + Enter to submit a message pair
|
||||
if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
createMessagePair(prompt);
|
||||
}
|
||||
|
||||
// Check if Ctrl + R is pressed
|
||||
if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
|
||||
e.preventDefault();
|
||||
console.log('regenerate');
|
||||
|
||||
const regenerateButton = [
|
||||
...document.getElementsByClassName('regenerate-response-button')
|
||||
]?.at(-1);
|
||||
|
||||
regenerateButton?.click();
|
||||
}
|
||||
|
||||
if (prompt === '' && e.key == 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@
|
|||
let collapseCodeBlocks = false;
|
||||
let expandDetails = false;
|
||||
let showChatTitleInTab = true;
|
||||
let showSidebarHotkeyHints = true;
|
||||
|
||||
let showFloatingActionButtons = true;
|
||||
let floatingActionButtons = null;
|
||||
|
|
@ -227,7 +226,6 @@
|
|||
chatDirection = $settings?.chatDirection ?? 'auto';
|
||||
userLocation = $settings?.userLocation ?? false;
|
||||
showChatTitleInTab = $settings?.showChatTitleInTab ?? true;
|
||||
showSidebarHotkeyHints = $settings?.showSidebarHotkeyHints ?? true;
|
||||
|
||||
notificationSound = $settings?.notificationSound ?? true;
|
||||
notificationSoundAlways = $settings?.notificationSoundAlways ?? false;
|
||||
|
|
@ -352,25 +350,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div id="show-sidebar-hotkey-hints-label" class=" self-center text-xs">
|
||||
{$i18n.t('Show Sidebar Hotkey Hints')}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 p-1">
|
||||
<Switch
|
||||
ariaLabelledbyId="show-sidebar-hotkey-hints-label"
|
||||
tooltip={true}
|
||||
bind:state={showSidebarHotkeyHints}
|
||||
on:change={() => {
|
||||
saveSettings({ showSidebarHotkeyHints });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div id="notification-sound-label" class=" self-center text-xs">
|
||||
|
|
|
|||
|
|
@ -11,26 +11,39 @@
|
|||
function formatKey(key: string): string {
|
||||
const lowerKey = key.toLowerCase();
|
||||
|
||||
if (lowerKey === 'mod') return isMac ? '⌘' : 'Ctrl/⌘';
|
||||
if (lowerKey === 'shift') return isMac ? '⇧' : 'Shift';
|
||||
if (lowerKey === 'backspace') return '⌫/Delete';
|
||||
if (lowerKey === 'escape') return 'Esc';
|
||||
if (lowerKey === 'enter') return 'Enter';
|
||||
if (lowerKey === 'tab') return 'Tab';
|
||||
if (lowerKey === 'arrowup') return '↑';
|
||||
if (lowerKey === 'arrowdown') return '↓';
|
||||
|
||||
// For keys like 'KeyK', 'KeyO', etc., we just want the last character.
|
||||
if (lowerKey.startsWith('key')) return key.slice(-1);
|
||||
// For keys like 'Digit2', 'Digit3', etc.
|
||||
if (lowerKey.startsWith('digit')) return key.slice(-1);
|
||||
// For other special keys
|
||||
if (lowerKey === 'quote') return "'";
|
||||
if (lowerKey === 'period') return '.';
|
||||
if (lowerKey === 'slash') return '/';
|
||||
if (lowerKey === 'semicolon') return ';';
|
||||
|
||||
return key.toUpperCase();
|
||||
switch (lowerKey) {
|
||||
case 'mod':
|
||||
return isMac ? '⌘' : 'Ctrl';
|
||||
case 'shift':
|
||||
return isMac ? '⇧' : 'Shift';
|
||||
case 'alt':
|
||||
return isMac ? '⌥' : 'Alt';
|
||||
case 'backspace':
|
||||
case 'delete':
|
||||
return isMac ? '⌫' : 'Delete';
|
||||
case 'escape':
|
||||
return 'Esc';
|
||||
case 'enter':
|
||||
return isMac ? '↩' : 'Enter';
|
||||
case 'tab':
|
||||
return isMac ? '⇥' : 'Tab';
|
||||
case 'arrowup':
|
||||
return '↑';
|
||||
case 'arrowdown':
|
||||
return '↓';
|
||||
case 'quote':
|
||||
return "'";
|
||||
case 'period':
|
||||
return '.';
|
||||
case 'slash':
|
||||
return '/';
|
||||
case 'semicolon':
|
||||
return ';';
|
||||
default:
|
||||
if (lowerKey.startsWith('key')) return key.slice(-1);
|
||||
if (lowerKey.startsWith('digit')) return key.slice(-1);
|
||||
return key.toUpperCase();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -46,8 +59,8 @@
|
|||
{$i18n.t(shortcut.name)}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex items-center self-center space-x-1 text-xs">
|
||||
{#each shortcut.keys as key}
|
||||
<div class="flex-shrink-0 flex flex-wrap justify-end items-center self-center space-x-1 text-xs">
|
||||
{#each shortcut.keys.filter(key => !(key.toLowerCase() === 'delete' && shortcut.keys.includes('Backspace'))) as key}
|
||||
<div
|
||||
class="h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { getContext, onMount } from 'svelte';
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import { shortcuts } from '$lib/shortcuts';
|
||||
import { settings } from '$lib/stores';
|
||||
import ShortcutItem from './ShortcutItem.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
|
||||
|
|
@ -21,7 +22,15 @@
|
|||
|
||||
onMount(() => {
|
||||
isMac = /Mac/i.test(navigator.userAgent);
|
||||
const allShortcuts = Object.values(shortcuts);
|
||||
});
|
||||
|
||||
$: {
|
||||
const allShortcuts = Object.values(shortcuts).filter((shortcut) => {
|
||||
if (!shortcut.setting) {
|
||||
return true;
|
||||
}
|
||||
return $settings[shortcut.setting.id] === shortcut.setting.value;
|
||||
});
|
||||
|
||||
const result = allShortcuts.reduce((acc, shortcut) => {
|
||||
const category = shortcut.category;
|
||||
|
|
@ -32,20 +41,22 @@
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const newCategorizedShortcuts = {};
|
||||
for (const category in result) {
|
||||
const half = Math.ceil(result[category].length / 2);
|
||||
categorizedShortcuts[category] = {
|
||||
newCategorizedShortcuts[category] = {
|
||||
left: result[category].slice(0, half),
|
||||
right: result[category].slice(half)
|
||||
};
|
||||
}
|
||||
});
|
||||
categorizedShortcuts = newCategorizedShortcuts;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show>
|
||||
<div class="text-gray-700 dark:text-gray-100">
|
||||
<div class="flex justify-between dark:text-gray-300 px-5 pt-4">
|
||||
<div class="text-lg font-medium self-center">{$i18n.t('Shortcuts')}</div>
|
||||
<div class="text-lg font-medium self-center">{$i18n.t('Keyboard Shortcuts')}</div>
|
||||
<button class="self-center" on:click={() => (show = false)}>
|
||||
<XMark className={'size-5'} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@
|
|||
mounted = true;
|
||||
});
|
||||
|
||||
$: if (name === 'newChat' || name === 'search') {
|
||||
isVisible = $settings?.showSidebarHotkeyHints !== false;
|
||||
}
|
||||
|
||||
function formatKey(key: string): string {
|
||||
const lowerKey = key.toLowerCase();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
type ShortcutRegistry = {
|
||||
[key in Shortcut]: {
|
||||
[key in Shortcut]?: {
|
||||
name: string;
|
||||
keys: string[];
|
||||
category: string;
|
||||
tooltip?: string;
|
||||
setting?: {
|
||||
id: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -12,7 +16,6 @@ export enum Shortcut {
|
|||
NEW_CHAT = 'newChat',
|
||||
NEW_TEMPORARY_CHAT = 'newTemporaryChat',
|
||||
DELETE_CHAT = 'deleteChat',
|
||||
GENERATE_PROMPT_PAIR = 'generatePromptPair',
|
||||
|
||||
//Global
|
||||
SEARCH = 'search',
|
||||
|
|
@ -26,11 +29,15 @@ export enum Shortcut {
|
|||
ACCEPT_AUTOCOMPLETE = 'acceptAutocomplete',
|
||||
PREVENT_FILE_CREATION = 'preventFileCreation',
|
||||
NAVIGATE_PROMPT_HISTORY_UP = 'navigatePromptHistoryUp',
|
||||
SEND_MESSAGE_NORMAL = 'sendMessageNormal',
|
||||
SEND_MESSAGE_MOD = 'sendMessageMod',
|
||||
ATTACH_FILE = 'attachFile',
|
||||
ADD_PROMPT = 'addPrompt',
|
||||
TALK_TO_MODEL = 'talkToModel',
|
||||
|
||||
//Message
|
||||
GENERATE_MESSAGE_PAIR = 'generateMessagePair',
|
||||
REGENERATE_RESPONSE = 'regenerateResponse',
|
||||
COPY_LAST_CODE_BLOCK = 'copyLastCodeBlock',
|
||||
COPY_LAST_RESPONSE = 'copyLastResponse',
|
||||
STOP_GENERATING = 'stopGenerating'
|
||||
|
|
@ -50,15 +57,9 @@ export const shortcuts: ShortcutRegistry = {
|
|||
},
|
||||
[Shortcut.DELETE_CHAT]: {
|
||||
name: 'Delete Chat',
|
||||
keys: ['mod', 'shift', 'Backspace'],
|
||||
keys: ['mod', 'shift', 'Backspace', 'Delete'],
|
||||
category: 'Chat'
|
||||
},
|
||||
[Shortcut.GENERATE_PROMPT_PAIR]: {
|
||||
name: 'Generate Prompt Pair',
|
||||
keys: ['mod', 'shift', 'Enter'],
|
||||
category: 'Chat',
|
||||
tooltip: 'Only active when the chat input is in focus and an LLM is generating a response.'
|
||||
},
|
||||
|
||||
//Global
|
||||
[Shortcut.SEARCH]: {
|
||||
|
|
@ -89,7 +90,7 @@ export const shortcuts: ShortcutRegistry = {
|
|||
|
||||
//Input
|
||||
[Shortcut.FOCUS_INPUT]: {
|
||||
name: 'Focus Text Area',
|
||||
name: 'Focus Chat Input',
|
||||
keys: ['shift', 'Escape'],
|
||||
category: 'Input'
|
||||
},
|
||||
|
|
@ -104,11 +105,25 @@ export const shortcuts: ShortcutRegistry = {
|
|||
category: 'Input',
|
||||
tooltip: 'Only active when "Paste Large Text as File" setting is toggled on.'
|
||||
},
|
||||
[Shortcut.NAVIGATE_PROMPT_HISTORY_UP]: {
|
||||
name: 'Edit Last Message',
|
||||
keys: ['ArrowUp'],
|
||||
[Shortcut.SEND_MESSAGE_NORMAL]: {
|
||||
name: 'Send Message',
|
||||
keys: ['Enter'],
|
||||
category: 'Input',
|
||||
tooltip: 'Only can be triggered when the chat input is in focus.'
|
||||
tooltip: 'The behavior of this shortcut is determined by the "Enter Key Behavior" setting.',
|
||||
setting: {
|
||||
id: 'ctrlEnterToSend',
|
||||
value: false
|
||||
}
|
||||
},
|
||||
[Shortcut.SEND_MESSAGE_MOD]: {
|
||||
name: 'Send Message',
|
||||
keys: ['mod', 'Enter'],
|
||||
category: 'Input',
|
||||
tooltip: 'The behavior of this shortcut is determined by the "Enter Key Behavior" setting.',
|
||||
setting: {
|
||||
id: 'ctrlEnterToSend',
|
||||
value: true
|
||||
}
|
||||
},
|
||||
[Shortcut.ATTACH_FILE]: {
|
||||
name: 'Attach File From Knowledge',
|
||||
|
|
@ -127,14 +142,15 @@ export const shortcuts: ShortcutRegistry = {
|
|||
},
|
||||
|
||||
//Message
|
||||
[Shortcut.COPY_LAST_CODE_BLOCK]: {
|
||||
name: 'Copy Last Code Block',
|
||||
keys: ['mod', 'shift', 'Semicolon'],
|
||||
category: 'Message'
|
||||
[Shortcut.GENERATE_MESSAGE_PAIR]: {
|
||||
name: 'Generate Message Pair',
|
||||
keys: ['mod', 'shift', 'Enter'],
|
||||
category: 'Message',
|
||||
tooltip: 'Only active when the chat input is in focus.'
|
||||
},
|
||||
[Shortcut.COPY_LAST_RESPONSE]: {
|
||||
name: 'Copy Last Response',
|
||||
keys: ['mod', 'shift', 'KeyC'],
|
||||
[Shortcut.REGENERATE_RESPONSE]: {
|
||||
name: 'Regenerate Response',
|
||||
keys: ['mod', 'KeyR'],
|
||||
category: 'Message'
|
||||
},
|
||||
[Shortcut.STOP_GENERATING]: {
|
||||
|
|
@ -142,5 +158,21 @@ export const shortcuts: ShortcutRegistry = {
|
|||
keys: ['Escape'],
|
||||
category: 'Message',
|
||||
tooltip: 'Only active when the chat input is in focus and an LLM is generating a response.'
|
||||
},
|
||||
[Shortcut.NAVIGATE_PROMPT_HISTORY_UP]: {
|
||||
name: 'Edit Last Message',
|
||||
keys: ['ArrowUp'],
|
||||
category: 'Message',
|
||||
tooltip: 'Only can be triggered when the chat input is in focus.'
|
||||
},
|
||||
[Shortcut.COPY_LAST_RESPONSE]: {
|
||||
name: 'Copy Last Response',
|
||||
keys: ['mod', 'shift', 'KeyC'],
|
||||
category: 'Message'
|
||||
},
|
||||
[Shortcut.COPY_LAST_CODE_BLOCK]: {
|
||||
name: 'Copy Last Code Block',
|
||||
keys: ['mod', 'shift', 'Semicolon'],
|
||||
category: 'Message'
|
||||
}
|
||||
};
|
||||
|
|
@ -187,7 +187,6 @@ type Settings = {
|
|||
highContrastMode?: boolean;
|
||||
title?: TitleSettings;
|
||||
showChatTitleInTab?: boolean;
|
||||
showSidebarHotkeyHints?: boolean;
|
||||
splitLargeDeltas?: boolean;
|
||||
chatDirection?: 'LTR' | 'RTL' | 'auto';
|
||||
ctrlEnterToSend?: boolean;
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ const setupKeyboardShortcuts = () => {
|
|||
showShortcuts.set(false);
|
||||
break;
|
||||
case Shortcut.NEW_TEMPORARY_CHAT:
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
if ($user?.role !== 'admin' && $user?.permissions?.chat?.temporary_enforced) {
|
||||
temporaryChatEnabled.set(true);
|
||||
} else {
|
||||
|
|
@ -258,14 +258,20 @@ const setupKeyboardShortcuts = () => {
|
|||
document.getElementById('new-chat-button')?.click();
|
||||
}, 0);
|
||||
break;
|
||||
case Shortcut.GENERATE_PROMPT_PAIR:
|
||||
// Placeholder for future implementation
|
||||
case Shortcut.GENERATE_MESSAGE_PAIR:
|
||||
event.preventDefault();
|
||||
document.getElementById('generate-message-pair-button')?.click();
|
||||
break;
|
||||
|
||||
case Shortcut.REGENERATE_RESPONSE:
|
||||
event.preventDefault();
|
||||
[...document.getElementsByClassName('regenerate-response-button')]?.at(-1)?.click();
|
||||
break;
|
||||
case Shortcut.STOP_GENERATING:
|
||||
// Placeholder for future implementation
|
||||
break;
|
||||
case Shortcut.PREVENT_FILE_CREATION:
|
||||
// This shortcut is handled by the paste event in MessageInput.svelte
|
||||
// This shortcut is handled by the paste event in MessageInput.svelte
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue