forked from open-webui/open-webui
Merge pull request #304 from ollama-webui/keyboard-shortcut
feat: keyboard shortcuts
This commit is contained in:
commit
72524272f0
4 changed files with 303 additions and 1 deletions
|
@ -119,6 +119,7 @@
|
|||
langDiv.style.fontSize = '0.75rem';
|
||||
|
||||
let button = document.createElement('button');
|
||||
button.className = 'copy-code-button';
|
||||
button.textContent = 'Copy Code';
|
||||
button.style.background = 'none';
|
||||
button.style.fontSize = '0.75rem';
|
||||
|
@ -832,7 +833,7 @@
|
|||
<button
|
||||
class="{messageIdx + 1 === messages.length
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:bg-gray-800 transition"
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:bg-gray-800 transition copy-response-button"
|
||||
on:click={() => {
|
||||
copyToClipboard(message.content);
|
||||
}}
|
||||
|
|
216
src/lib/components/chat/ShortcutsModal.svelte
Normal file
216
src/lib/components/chat/ShortcutsModal.svelte
Normal file
|
@ -0,0 +1,216 @@
|
|||
<script lang="ts">
|
||||
import Modal from '../common/Modal.svelte';
|
||||
|
||||
export let show = false;
|
||||
</script>
|
||||
|
||||
<Modal bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||
<div class=" text-lg font-medium self-center">Keyboard shortcuts</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<hr class=" dark:border-gray-800" />
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
|
||||
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
|
||||
<div class="flex flex-col space-y-3 w-full self-start">
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Open new chat</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Ctrl/⌘
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Shift
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
O
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Focus chat input</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Shift
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Esc
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Copy last code block</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Ctrl/⌘
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Shift
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
;
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Copy last response</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Ctrl/⌘
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Shift
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
C
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-3 w-full self-start">
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Toggle sidebar</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Ctrl/⌘
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Shift
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
S
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Delete chat</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Ctrl/⌘
|
||||
</div>
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Shift
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
⌫
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class=" text-sm">Show shortcuts</div>
|
||||
|
||||
<div class="flex space-x-1 text-xs">
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
Ctrl/⌘
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||
>
|
||||
/
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
/* display: none; <- Crashes Chrome on hover */
|
||||
-webkit-appearance: none;
|
||||
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
|
||||
}
|
||||
|
||||
.tabs::-webkit-scrollbar {
|
||||
display: none; /* for Chrome, Safari and Opera */
|
||||
}
|
||||
|
||||
.tabs {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield; /* Firefox */
|
||||
}
|
||||
</style>
|
|
@ -61,6 +61,7 @@
|
|||
<div class="py-2.5 my-auto flex flex-col justify-between h-screen">
|
||||
<div class="px-2.5 flex justify-center space-x-2">
|
||||
<button
|
||||
id="sidebar-new-chat-button"
|
||||
class="flex-grow flex justify-between rounded-md px-3 py-1.5 mt-2 hover:bg-gray-900 transition"
|
||||
on:click={async () => {
|
||||
goto('/');
|
||||
|
@ -320,6 +321,13 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div class="flex self-center space-x-1.5">
|
||||
<button
|
||||
id="delete-chat-button"
|
||||
class=" hidden"
|
||||
on:click={() => {
|
||||
deleteChat(chat.id);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
class=" self-center hover:text-white transition"
|
||||
on:click={() => {
|
||||
|
@ -533,6 +541,7 @@
|
|||
class="fixed left-0 top-[50dvh] z-40 -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
||||
>
|
||||
<button
|
||||
id="sidebar-toggle-button"
|
||||
class=" group"
|
||||
on:click={() => {
|
||||
show = !show;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
|
||||
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
||||
import { checkVersion } from '$lib/utils';
|
||||
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
|
||||
|
||||
let ollamaVersion = '';
|
||||
let loaded = false;
|
||||
|
@ -25,6 +26,8 @@
|
|||
let DB = null;
|
||||
let localDBChats = [];
|
||||
|
||||
let showShortcuts = false;
|
||||
|
||||
const getModels = async () => {
|
||||
let models = [];
|
||||
models.push(
|
||||
|
@ -106,6 +109,65 @@
|
|||
});
|
||||
|
||||
await setOllamaVersion();
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
|
||||
// Check if the Shift key is pressed
|
||||
const isShiftPressed = event.shiftKey;
|
||||
|
||||
// Check if Ctrl + Shift + O is pressed
|
||||
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'o') {
|
||||
event.preventDefault();
|
||||
console.log('newChat');
|
||||
document.getElementById('sidebar-new-chat-button')?.click();
|
||||
}
|
||||
|
||||
// Check if Shift + Esc is pressed
|
||||
if (isShiftPressed && event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
console.log('focusInput');
|
||||
document.getElementById('chat-textarea')?.focus();
|
||||
}
|
||||
|
||||
// Check if Ctrl + Shift + ; is pressed
|
||||
if (isCtrlPressed && isShiftPressed && event.key === ';') {
|
||||
event.preventDefault();
|
||||
console.log('copyLastCodeBlock');
|
||||
const button = [...document.getElementsByClassName('copy-code-button')]?.at(-1);
|
||||
button?.click();
|
||||
}
|
||||
|
||||
// Check if Ctrl + Shift + C is pressed
|
||||
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'c') {
|
||||
event.preventDefault();
|
||||
console.log('copyLastResponse');
|
||||
const button = [...document.getElementsByClassName('copy-response-button')]?.at(-1);
|
||||
console.log(button);
|
||||
button?.click();
|
||||
}
|
||||
|
||||
// Check if Ctrl + Shift + S is pressed
|
||||
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 's') {
|
||||
event.preventDefault();
|
||||
console.log('toggleSidebar');
|
||||
document.getElementById('sidebar-toggle-button')?.click();
|
||||
}
|
||||
|
||||
// Check if Ctrl + Shift + Backspace is pressed
|
||||
if (isCtrlPressed && isShiftPressed && event.key === 'Backspace') {
|
||||
event.preventDefault();
|
||||
console.log('deleteChat');
|
||||
document.getElementById('delete-chat-button')?.click();
|
||||
}
|
||||
|
||||
// Check if Ctrl + / is pressed
|
||||
if (isCtrlPressed && event.key === '/') {
|
||||
event.preventDefault();
|
||||
console.log('showShortcuts');
|
||||
document.getElementById('show-shortcuts-button')?.click();
|
||||
}
|
||||
});
|
||||
|
||||
await tick();
|
||||
}
|
||||
|
||||
|
@ -114,6 +176,20 @@
|
|||
</script>
|
||||
|
||||
{#if loaded}
|
||||
<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10">
|
||||
<button
|
||||
id="show-shortcuts-button"
|
||||
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
|
||||
on:click={() => {
|
||||
showShortcuts = !showShortcuts;
|
||||
}}
|
||||
>
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ShortcutsModal bind:show={showShortcuts} />
|
||||
|
||||
<div class="app relative">
|
||||
{#if !['user', 'admin'].includes($user.role)}
|
||||
<div class="fixed w-full h-full flex z-50">
|
||||
|
|
Loading…
Reference in a new issue