Browse Source

Merge pull request #304 from ollama-webui/keyboard-shortcut

feat: keyboard shortcuts
Timothy Jaeryang Baek 1 năm trước cách đây
mục cha
commit
72524272f0

+ 2 - 1
src/lib/components/chat/Messages.svelte

@@ -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 - 0
src/lib/components/chat/ShortcutsModal.svelte

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

+ 9 - 0
src/lib/components/layout/Sidebar.svelte

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

+ 76 - 0
src/routes/(app)/+layout.svelte

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