This commit is contained in:
silentoplayz 2025-10-18 00:32:03 -04:00
parent e361606c61
commit 3c7e739b3c
8 changed files with 119 additions and 95 deletions

View file

@ -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();

View file

@ -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">

View file

@ -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"
>

View file

@ -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>

View file

@ -17,10 +17,6 @@
mounted = true;
});
$: if (name === 'newChat' || name === 'search') {
isVisible = $settings?.showSidebarHotkeyHints !== false;
}
function formatKey(key: string): string {
const lowerKey = key.toLowerCase();

View file

@ -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'
}
};

View file

@ -187,7 +187,6 @@ type Settings = {
highContrastMode?: boolean;
title?: TitleSettings;
showChatTitleInTab?: boolean;
showSidebarHotkeyHints?: boolean;
splitLargeDeltas?: boolean;
chatDirection?: 'LTR' | 'RTL' | 'auto';
ctrlEnterToSend?: boolean;

View file

@ -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;
}
}