瀏覽代碼

fix: styling

Timothy J. Baek 1 年之前
父節點
當前提交
3c9fc7858b

+ 470 - 467
src/lib/components/chat/MessageInput.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 	import { onMount, tick, getContext } from 'svelte';
-	import { settings } from '$lib/stores';
+	import { settings, showSidebar } from '$lib/stores';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 
 	import Prompts from './MessageInput/PromptCommands.svelte';
@@ -291,6 +291,7 @@
 	};
 
 	onMount(() => {
+		console.log(document.getElementById('sidebar'));
 		window.setTimeout(() => chatTextAreaElement?.focus(), 0);
 
 		const dropZone = document.querySelector('body');
@@ -389,142 +390,216 @@
 	</div>
 {/if}
 
-<div class="w-full absolute bottom-0">
-	<div class="px-2.5 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
-		<div class="flex flex-col max-w-3xl w-full">
-			<div class="relative">
-				{#if autoScroll === false && messages.length > 0}
-					<div class=" absolute -top-12 left-0 right-0 flex justify-center">
-						<button
-							class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
-							on:click={() => {
-								autoScroll = true;
-								scrollToBottom();
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 20 20"
-								fill="currentColor"
-								class="w-5 h-5"
+<div class="fixed bottom-0 {$showSidebar ? 'left-0 lg:left-[260px]' : 'left-0'} right-0">
+	<div class="w-full">
+		<div class=" px-2.5 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
+			<div class="flex flex-col max-w-3xl w-full">
+				<div class="relative">
+					{#if autoScroll === false && messages.length > 0}
+						<div class=" absolute -top-12 left-0 right-0 flex justify-center">
+							<button
+								class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
+								on:click={() => {
+									autoScroll = true;
+									scrollToBottom();
+								}}
 							>
-								<path
-									fill-rule="evenodd"
-									d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</button>
-					</div>
-				{/if}
-			</div>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 20 20"
+									fill="currentColor"
+									class="w-5 h-5"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							</button>
+						</div>
+					{/if}
+				</div>
 
-			<div class="w-full relative">
-				{#if prompt.charAt(0) === '/'}
-					<Prompts bind:this={promptsElement} bind:prompt />
-				{:else if prompt.charAt(0) === '#'}
-					<Documents
-						bind:this={documentsElement}
-						bind:prompt
-						on:url={(e) => {
-							console.log(e);
-							uploadWeb(e.detail);
-						}}
-						on:select={(e) => {
-							console.log(e);
-							files = [
-								...files,
-								{
-									type: e?.detail?.type ?? 'doc',
-									...e.detail,
-									upload_status: true
-								}
-							];
-						}}
-					/>
-				{:else if prompt.charAt(0) === '@'}
-					<Models
-						bind:this={modelsElement}
-						bind:prompt
-						bind:user
-						bind:chatInputPlaceholder
-						{messages}
-					/>
-				{/if}
+				<div class="w-full relative">
+					{#if prompt.charAt(0) === '/'}
+						<Prompts bind:this={promptsElement} bind:prompt />
+					{:else if prompt.charAt(0) === '#'}
+						<Documents
+							bind:this={documentsElement}
+							bind:prompt
+							on:url={(e) => {
+								console.log(e);
+								uploadWeb(e.detail);
+							}}
+							on:select={(e) => {
+								console.log(e);
+								files = [
+									...files,
+									{
+										type: e?.detail?.type ?? 'doc',
+										...e.detail,
+										upload_status: true
+									}
+								];
+							}}
+						/>
+					{:else if prompt.charAt(0) === '@'}
+						<Models
+							bind:this={modelsElement}
+							bind:prompt
+							bind:user
+							bind:chatInputPlaceholder
+							{messages}
+						/>
+					{/if}
 
-				<!-- {#if messages.length == 0 && suggestionPrompts.length !== 0}
-					<Suggestions {suggestionPrompts} {submitPrompt} />
-				{/if} -->
+					<!-- {#if messages.length == 0 && suggestionPrompts.length !== 0}
+						<Suggestions {suggestionPrompts} {submitPrompt} />
+					{/if} -->
+				</div>
 			</div>
 		</div>
-	</div>
-	<div class="bg-white dark:bg-gray-900">
-		<div class="max-w-3xl px-2.5 mx-auto inset-x-0">
-			<div class=" pb-2">
-				<input
-					bind:this={filesInputElement}
-					bind:files={inputFiles}
-					type="file"
-					hidden
-					multiple
-					on:change={async () => {
-						if (inputFiles && inputFiles.length > 0) {
-							const _inputFiles = Array.from(inputFiles);
-							_inputFiles.forEach((file) => {
-								if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
-									let reader = new FileReader();
-									reader.onload = (event) => {
-										files = [
-											...files,
-											{
-												type: 'image',
-												url: `${event.target.result}`
-											}
-										];
-										inputFiles = null;
+		<div class="bg-white dark:bg-gray-900">
+			<div class="max-w-3xl px-2.5 mx-auto inset-x-0">
+				<div class=" pb-2">
+					<input
+						bind:this={filesInputElement}
+						bind:files={inputFiles}
+						type="file"
+						hidden
+						multiple
+						on:change={async () => {
+							if (inputFiles && inputFiles.length > 0) {
+								const _inputFiles = Array.from(inputFiles);
+								_inputFiles.forEach((file) => {
+									if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
+										let reader = new FileReader();
+										reader.onload = (event) => {
+											files = [
+												...files,
+												{
+													type: 'image',
+													url: `${event.target.result}`
+												}
+											];
+											inputFiles = null;
+											filesInputElement.value = '';
+										};
+										reader.readAsDataURL(file);
+									} else if (
+										SUPPORTED_FILE_TYPE.includes(file['type']) ||
+										SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
+									) {
+										uploadDoc(file);
 										filesInputElement.value = '';
-									};
-									reader.readAsDataURL(file);
-								} else if (
-									SUPPORTED_FILE_TYPE.includes(file['type']) ||
-									SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
-								) {
-									uploadDoc(file);
-									filesInputElement.value = '';
-								} else {
-									toast.error(
-										$i18n.t(
-											`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
-											{ file_type: file['type'] }
-										)
-									);
-									uploadDoc(file);
-									filesInputElement.value = '';
-								}
-							});
-						} else {
-							toast.error($i18n.t(`File not found.`));
-						}
-					}}
-				/>
-				<form
-					class=" flex flex-col relative w-full rounded-3xl px-1.5 border border-gray-100 dark:border-gray-850 bg-white dark:bg-gray-900 dark:text-gray-100"
-					on:submit|preventDefault={() => {
-						submitPrompt(prompt, user);
-					}}
-				>
-					{#if files.length > 0}
-						<div class="mx-2 mt-2 mb-1 flex flex-wrap gap-2">
-							{#each files as file, fileIdx}
-								<div class=" relative group">
-									{#if file.type === 'image'}
-										<img src={file.url} alt="input" class=" h-16 w-16 rounded-xl object-cover" />
-									{:else if file.type === 'doc'}
-										<div
-											class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
-										>
-											<div class="p-2.5 bg-red-400 text-white rounded-lg">
-												{#if file.upload_status}
+									} else {
+										toast.error(
+											$i18n.t(
+												`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
+												{ file_type: file['type'] }
+											)
+										);
+										uploadDoc(file);
+										filesInputElement.value = '';
+									}
+								});
+							} else {
+								toast.error($i18n.t(`File not found.`));
+							}
+						}}
+					/>
+					<form
+						class=" flex flex-col relative w-full rounded-3xl px-1.5 border border-gray-100 dark:border-gray-850 bg-white dark:bg-gray-900 dark:text-gray-100"
+						on:submit|preventDefault={() => {
+							submitPrompt(prompt, user);
+						}}
+					>
+						{#if files.length > 0}
+							<div class="mx-2 mt-2 mb-1 flex flex-wrap gap-2">
+								{#each files as file, fileIdx}
+									<div class=" relative group">
+										{#if file.type === 'image'}
+											<img src={file.url} alt="input" class=" h-16 w-16 rounded-xl object-cover" />
+										{:else if file.type === 'doc'}
+											<div
+												class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
+											>
+												<div class="p-2.5 bg-red-400 text-white rounded-lg">
+													{#if file.upload_status}
+														<svg
+															xmlns="http://www.w3.org/2000/svg"
+															viewBox="0 0 24 24"
+															fill="currentColor"
+															class="w-6 h-6"
+														>
+															<path
+																fill-rule="evenodd"
+																d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
+																clip-rule="evenodd"
+															/>
+															<path
+																d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
+															/>
+														</svg>
+													{:else}
+														<svg
+															class=" w-6 h-6 translate-y-[0.5px]"
+															fill="currentColor"
+															viewBox="0 0 24 24"
+															xmlns="http://www.w3.org/2000/svg"
+															><style>
+																.spinner_qM83 {
+																	animation: spinner_8HQG 1.05s infinite;
+																}
+																.spinner_oXPr {
+																	animation-delay: 0.1s;
+																}
+																.spinner_ZTLf {
+																	animation-delay: 0.2s;
+																}
+																@keyframes spinner_8HQG {
+																	0%,
+																	57.14% {
+																		animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
+																		transform: translate(0);
+																	}
+																	28.57% {
+																		animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
+																		transform: translateY(-6px);
+																	}
+																	100% {
+																		transform: translate(0);
+																	}
+																}
+															</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
+																class="spinner_qM83 spinner_oXPr"
+																cx="12"
+																cy="12"
+																r="2.5"
+															/><circle
+																class="spinner_qM83 spinner_ZTLf"
+																cx="20"
+																cy="12"
+																r="2.5"
+															/></svg
+														>
+													{/if}
+												</div>
+
+												<div class="flex flex-col justify-center -space-y-0.5">
+													<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
+														{file.name}
+													</div>
+
+													<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
+												</div>
+											</div>
+										{:else if file.type === 'collection'}
+											<div
+												class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
+											>
+												<div class="p-2.5 bg-red-400 text-white rounded-lg">
 													<svg
 														xmlns="http://www.w3.org/2000/svg"
 														viewBox="0 0 24 24"
@@ -532,421 +607,349 @@
 														class="w-6 h-6"
 													>
 														<path
-															fill-rule="evenodd"
-															d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
-															clip-rule="evenodd"
+															d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
 														/>
 														<path
-															d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
+															d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
 														/>
 													</svg>
-												{:else}
-													<svg
-														class=" w-6 h-6 translate-y-[0.5px]"
-														fill="currentColor"
-														viewBox="0 0 24 24"
-														xmlns="http://www.w3.org/2000/svg"
-														><style>
-															.spinner_qM83 {
-																animation: spinner_8HQG 1.05s infinite;
-															}
-															.spinner_oXPr {
-																animation-delay: 0.1s;
-															}
-															.spinner_ZTLf {
-																animation-delay: 0.2s;
-															}
-															@keyframes spinner_8HQG {
-																0%,
-																57.14% {
-																	animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
-																	transform: translate(0);
-																}
-																28.57% {
-																	animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
-																	transform: translateY(-6px);
-																}
-																100% {
-																	transform: translate(0);
-																}
-															}
-														</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
-															class="spinner_qM83 spinner_oXPr"
-															cx="12"
-															cy="12"
-															r="2.5"
-														/><circle
-															class="spinner_qM83 spinner_ZTLf"
-															cx="20"
-															cy="12"
-															r="2.5"
-														/></svg
-													>
-												{/if}
-											</div>
-
-											<div class="flex flex-col justify-center -space-y-0.5">
-												<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
-													{file.name}
 												</div>
 
-												<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
+												<div class="flex flex-col justify-center -space-y-0.5">
+													<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
+														{file?.title ?? `#${file.name}`}
+													</div>
+
+													<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
+												</div>
 											</div>
-										</div>
-									{:else if file.type === 'collection'}
-										<div
-											class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
-										>
-											<div class="p-2.5 bg-red-400 text-white rounded-lg">
+										{/if}
+
+										<div class=" absolute -top-1 -right-1">
+											<button
+												class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
+												type="button"
+												on:click={() => {
+													files.splice(fileIdx, 1);
+													files = files;
+												}}
+											>
 												<svg
 													xmlns="http://www.w3.org/2000/svg"
-													viewBox="0 0 24 24"
+													viewBox="0 0 20 20"
 													fill="currentColor"
-													class="w-6 h-6"
+													class="w-4 h-4"
 												>
 													<path
-														d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
-													/>
-													<path
-														d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
+														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>
-											</div>
-
-											<div class="flex flex-col justify-center -space-y-0.5">
-												<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
-													{file?.title ?? `#${file.name}`}
-												</div>
-
-												<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
-											</div>
+											</button>
 										</div>
-									{/if}
+									</div>
+								{/each}
+							</div>
+						{/if}
 
-									<div class=" absolute -top-1 -right-1">
+						<div class=" flex">
+							{#if fileUploadEnabled}
+								<div class=" self-end mb-2 ml-1">
+									<Tooltip content={$i18n.t('Upload files')}>
 										<button
-											class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
+											class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
 											type="button"
 											on:click={() => {
-												files.splice(fileIdx, 1);
-												files = files;
+												filesInputElement.click();
 											}}
 										>
 											<svg
 												xmlns="http://www.w3.org/2000/svg"
-												viewBox="0 0 20 20"
+												viewBox="0 0 16 16"
 												fill="currentColor"
-												class="w-4 h-4"
+												class="w-[1.2rem] h-[1.2rem]"
 											>
 												<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"
+													d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
 												/>
 											</svg>
 										</button>
-									</div>
+									</Tooltip>
 								</div>
-							{/each}
-						</div>
-					{/if}
-
-					<div class=" flex">
-						{#if fileUploadEnabled}
-							<div class=" self-end mb-2 ml-1">
-								<Tooltip content={$i18n.t('Upload files')}>
-									<button
-										class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
-										type="button"
-										on:click={() => {
-											filesInputElement.click();
-										}}
-									>
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 16 16"
-											fill="currentColor"
-											class="w-[1.2rem] h-[1.2rem]"
-										>
-											<path
-												d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
-											/>
-										</svg>
-									</button>
-								</Tooltip>
-							</div>
-						{/if}
+							{/if}
 
-						<textarea
-							id="chat-textarea"
-							bind:this={chatTextAreaElement}
-							class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
-								? ''
-								: ' pl-4'} rounded-xl resize-none h-[48px]"
-							placeholder={chatInputPlaceholder !== ''
-								? chatInputPlaceholder
-								: isRecording
-								? $i18n.t('Listening...')
-								: $i18n.t('Send a Message')}
-							bind:value={prompt}
-							on:keypress={(e) => {
-								if (window.innerWidth > 1024) {
-									if (e.keyCode == 13 && !e.shiftKey) {
-										e.preventDefault();
-									}
-									if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
-										submitPrompt(prompt, user);
+							<textarea
+								id="chat-textarea"
+								bind:this={chatTextAreaElement}
+								class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
+									? ''
+									: ' pl-4'} rounded-xl resize-none h-[48px]"
+								placeholder={chatInputPlaceholder !== ''
+									? chatInputPlaceholder
+									: isRecording
+									? $i18n.t('Listening...')
+									: $i18n.t('Send a Message')}
+								bind:value={prompt}
+								on:keypress={(e) => {
+									if (window.innerWidth > 1024) {
+										if (e.keyCode == 13 && !e.shiftKey) {
+											e.preventDefault();
+										}
+										if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
+											submitPrompt(prompt, user);
+										}
 									}
-								}
-							}}
-							on:keydown={async (e) => {
-								const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
+								}}
+								on:keydown={async (e) => {
+									const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
 
-								// Check if Ctrl + R is pressed
-								if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
-									e.preventDefault();
-									console.log('regenerate');
+									// 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);
+										const regenerateButton = [
+											...document.getElementsByClassName('regenerate-response-button')
+										]?.at(-1);
 
-									regenerateButton?.click();
-								}
+										regenerateButton?.click();
+									}
 
-								if (prompt === '' && e.key == 'ArrowUp') {
-									e.preventDefault();
+									if (prompt === '' && e.key == 'ArrowUp') {
+										e.preventDefault();
 
-									const userMessageElement = [
-										...document.getElementsByClassName('user-message')
-									]?.at(-1);
+										const userMessageElement = [
+											...document.getElementsByClassName('user-message')
+										]?.at(-1);
 
-									const editButton = [
-										...document.getElementsByClassName('edit-user-message-button')
-									]?.at(-1);
+										const editButton = [
+											...document.getElementsByClassName('edit-user-message-button')
+										]?.at(-1);
 
-									console.log(userMessageElement);
+										console.log(userMessageElement);
 
-									userMessageElement.scrollIntoView({ block: 'center' });
-									editButton?.click();
-								}
+										userMessageElement.scrollIntoView({ block: 'center' });
+										editButton?.click();
+									}
 
-								if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowUp') {
-									e.preventDefault();
+									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowUp') {
+										e.preventDefault();
 
-									(promptsElement || documentsElement || modelsElement).selectUp();
+										(promptsElement || documentsElement || modelsElement).selectUp();
 
-									const commandOptionButton = [
-										...document.getElementsByClassName('selected-command-option-button')
-									]?.at(-1);
-									commandOptionButton.scrollIntoView({ block: 'center' });
-								}
+										const commandOptionButton = [
+											...document.getElementsByClassName('selected-command-option-button')
+										]?.at(-1);
+										commandOptionButton.scrollIntoView({ block: 'center' });
+									}
 
-								if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowDown') {
-									e.preventDefault();
+									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowDown') {
+										e.preventDefault();
 
-									(promptsElement || documentsElement || modelsElement).selectDown();
+										(promptsElement || documentsElement || modelsElement).selectDown();
 
-									const commandOptionButton = [
-										...document.getElementsByClassName('selected-command-option-button')
-									]?.at(-1);
-									commandOptionButton.scrollIntoView({ block: 'center' });
-								}
+										const commandOptionButton = [
+											...document.getElementsByClassName('selected-command-option-button')
+										]?.at(-1);
+										commandOptionButton.scrollIntoView({ block: 'center' });
+									}
 
-								if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Enter') {
-									e.preventDefault();
+									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Enter') {
+										e.preventDefault();
 
-									const commandOptionButton = [
-										...document.getElementsByClassName('selected-command-option-button')
-									]?.at(-1);
+										const commandOptionButton = [
+											...document.getElementsByClassName('selected-command-option-button')
+										]?.at(-1);
 
-									if (commandOptionButton) {
-										commandOptionButton?.click();
-									} else {
-										document.getElementById('send-message-button')?.click();
+										if (commandOptionButton) {
+											commandOptionButton?.click();
+										} else {
+											document.getElementById('send-message-button')?.click();
+										}
 									}
-								}
 
-								if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
-									e.preventDefault();
+									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
+										e.preventDefault();
 
-									const commandOptionButton = [
-										...document.getElementsByClassName('selected-command-option-button')
-									]?.at(-1);
+										const commandOptionButton = [
+											...document.getElementsByClassName('selected-command-option-button')
+										]?.at(-1);
 
-									commandOptionButton?.click();
-								} else if (e.key === 'Tab') {
-									const words = findWordIndices(prompt);
+										commandOptionButton?.click();
+									} else if (e.key === 'Tab') {
+										const words = findWordIndices(prompt);
 
-									if (words.length > 0) {
-										const word = words.at(0);
-										const fullPrompt = prompt;
+										if (words.length > 0) {
+											const word = words.at(0);
+											const fullPrompt = prompt;
 
-										prompt = prompt.substring(0, word?.endIndex + 1);
-										await tick();
+											prompt = prompt.substring(0, word?.endIndex + 1);
+											await tick();
 
-										e.target.scrollTop = e.target.scrollHeight;
-										prompt = fullPrompt;
-										await tick();
+											e.target.scrollTop = e.target.scrollHeight;
+											prompt = fullPrompt;
+											await tick();
 
-										e.preventDefault();
-										e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
-									}
-								}
-							}}
-							rows="1"
-							on:input={(e) => {
-								e.target.style.height = '';
-								e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
-								user = null;
-							}}
-							on:focus={(e) => {
-								e.target.style.height = '';
-								e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
-							}}
-							on:paste={(e) => {
-								const clipboardData = e.clipboardData || window.clipboardData;
-
-								if (clipboardData && clipboardData.items) {
-									for (const item of clipboardData.items) {
-										if (item.type.indexOf('image') !== -1) {
-											const blob = item.getAsFile();
-											const reader = new FileReader();
-
-											reader.onload = function (e) {
-												files = [
-													...files,
-													{
-														type: 'image',
-														url: `${e.target.result}`
-													}
-												];
-											};
-
-											reader.readAsDataURL(blob);
+											e.preventDefault();
+											e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
 										}
 									}
-								}
-							}}
-						/>
-
-						<div class="self-end mb-2 flex space-x-1 mr-1">
-							{#if messages.length == 0 || messages.at(-1).done == true}
-								<Tooltip content={$i18n.t('Record voice')}>
-									{#if speechRecognitionEnabled}
-										<button
-											id="voice-input-button"
-											class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
-											type="button"
-											on:click={() => {
-												speechRecognitionHandler();
-											}}
-										>
-											{#if isRecording}
-												<svg
-													class=" w-5 h-5 translate-y-[0.5px]"
-													fill="currentColor"
-													viewBox="0 0 24 24"
-													xmlns="http://www.w3.org/2000/svg"
-													><style>
-														.spinner_qM83 {
-															animation: spinner_8HQG 1.05s infinite;
+								}}
+								rows="1"
+								on:input={(e) => {
+									e.target.style.height = '';
+									e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
+									user = null;
+								}}
+								on:focus={(e) => {
+									e.target.style.height = '';
+									e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
+								}}
+								on:paste={(e) => {
+									const clipboardData = e.clipboardData || window.clipboardData;
+
+									if (clipboardData && clipboardData.items) {
+										for (const item of clipboardData.items) {
+											if (item.type.indexOf('image') !== -1) {
+												const blob = item.getAsFile();
+												const reader = new FileReader();
+
+												reader.onload = function (e) {
+													files = [
+														...files,
+														{
+															type: 'image',
+															url: `${e.target.result}`
 														}
-														.spinner_oXPr {
-															animation-delay: 0.1s;
-														}
-														.spinner_ZTLf {
-															animation-delay: 0.2s;
-														}
-														@keyframes spinner_8HQG {
-															0%,
-															57.14% {
-																animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
-																transform: translate(0);
+													];
+												};
+
+												reader.readAsDataURL(blob);
+											}
+										}
+									}
+								}}
+							/>
+
+							<div class="self-end mb-2 flex space-x-1 mr-1">
+								{#if messages.length == 0 || messages.at(-1).done == true}
+									<Tooltip content={$i18n.t('Record voice')}>
+										{#if speechRecognitionEnabled}
+											<button
+												id="voice-input-button"
+												class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
+												type="button"
+												on:click={() => {
+													speechRecognitionHandler();
+												}}
+											>
+												{#if isRecording}
+													<svg
+														class=" w-5 h-5 translate-y-[0.5px]"
+														fill="currentColor"
+														viewBox="0 0 24 24"
+														xmlns="http://www.w3.org/2000/svg"
+														><style>
+															.spinner_qM83 {
+																animation: spinner_8HQG 1.05s infinite;
 															}
-															28.57% {
-																animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
-																transform: translateY(-6px);
+															.spinner_oXPr {
+																animation-delay: 0.1s;
 															}
-															100% {
-																transform: translate(0);
+															.spinner_ZTLf {
+																animation-delay: 0.2s;
 															}
-														}
-													</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
-														class="spinner_qM83 spinner_oXPr"
-														cx="12"
-														cy="12"
-														r="2.5"
-													/><circle
-														class="spinner_qM83 spinner_ZTLf"
-														cx="20"
-														cy="12"
-														r="2.5"
-													/></svg
-												>
-											{:else}
-												<svg
-													xmlns="http://www.w3.org/2000/svg"
-													viewBox="0 0 20 20"
-													fill="currentColor"
-													class="w-5 h-5 translate-y-[0.5px]"
-												>
-													<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
-													<path
-														d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
-													/>
-												</svg>
-											{/if}
-										</button>
-									{/if}
-								</Tooltip>
+															@keyframes spinner_8HQG {
+																0%,
+																57.14% {
+																	animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
+																	transform: translate(0);
+																}
+																28.57% {
+																	animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
+																	transform: translateY(-6px);
+																}
+																100% {
+																	transform: translate(0);
+																}
+															}
+														</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
+															class="spinner_qM83 spinner_oXPr"
+															cx="12"
+															cy="12"
+															r="2.5"
+														/><circle
+															class="spinner_qM83 spinner_ZTLf"
+															cx="20"
+															cy="12"
+															r="2.5"
+														/></svg
+													>
+												{:else}
+													<svg
+														xmlns="http://www.w3.org/2000/svg"
+														viewBox="0 0 20 20"
+														fill="currentColor"
+														class="w-5 h-5 translate-y-[0.5px]"
+													>
+														<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
+														<path
+															d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
+														/>
+													</svg>
+												{/if}
+											</button>
+										{/if}
+									</Tooltip>
 
-								<Tooltip content={$i18n.t('Send message')}>
+									<Tooltip content={$i18n.t('Send message')}>
+										<button
+											id="send-message-button"
+											class="{prompt !== ''
+												? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
+												: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
+											type="submit"
+											disabled={prompt === ''}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 16 16"
+												fill="currentColor"
+												class="w-5 h-5"
+											>
+												<path
+													fill-rule="evenodd"
+													d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
+													clip-rule="evenodd"
+												/>
+											</svg>
+										</button>
+									</Tooltip>
+								{:else}
 									<button
-										id="send-message-button"
-										class="{prompt !== ''
-											? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
-											: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
-										type="submit"
-										disabled={prompt === ''}
+										class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
+										on:click={stopResponse}
 									>
 										<svg
 											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 16 16"
+											viewBox="0 0 24 24"
 											fill="currentColor"
 											class="w-5 h-5"
 										>
 											<path
 												fill-rule="evenodd"
-												d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
+												d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm6-2.438c0-.724.588-1.312 1.313-1.312h4.874c.725 0 1.313.588 1.313 1.313v4.874c0 .725-.588 1.313-1.313 1.313H9.564a1.312 1.312 0 01-1.313-1.313V9.564z"
 												clip-rule="evenodd"
 											/>
 										</svg>
 									</button>
-								</Tooltip>
-							{:else}
-								<button
-									class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
-									on:click={stopResponse}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 24 24"
-										fill="currentColor"
-										class="w-5 h-5"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm6-2.438c0-.724.588-1.312 1.313-1.312h4.874c.725 0 1.313.588 1.313 1.313v4.874c0 .725-.588 1.313-1.313 1.313H9.564a1.312 1.312 0 01-1.313-1.313V9.564z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
-							{/if}
+								{/if}
+							</div>
 						</div>
-					</div>
-				</form>
+					</form>
 
-				<div class="mt-1.5 text-xs text-gray-500 text-center">
-					{$i18n.t('LLMs can make mistakes. Verify important information.')}
+					<div class="mt-1.5 text-xs text-gray-500 text-center">
+						{$i18n.t('LLMs can make mistakes. Verify important information.')}
+					</div>
 				</div>
 			</div>
 		</div>

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

@@ -297,7 +297,7 @@
 			}}
 		/>
 	{:else}
-		<div class="pt-2 pb-28">
+		<div class="{$settings?.fullScreenMode ?? null ? 'w-full' : 'mx-auto'} pt-2 pb-28">
 			{#key chatId}
 				{#each messages as message, messageIdx}
 					<div class=" w-full">

+ 15 - 18
src/lib/components/layout/Sidebar.svelte

@@ -1,12 +1,6 @@
 <script lang="ts">
-	import { v4 as uuidv4 } from 'uuid';
-
-	import fileSaver from 'file-saver';
-	const { saveAs } = fileSaver;
-
-	import { goto, invalidateAll } from '$app/navigation';
-	import { page } from '$app/stores';
-	import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores';
+	import { goto } from '$app/navigation';
+	import { user, chats, settings, showSettings, chatId, tags, showSidebar } from '$lib/stores';
 	import { onMount, getContext } from 'svelte';
 
 	const i18n = getContext('i18n');
@@ -30,6 +24,7 @@
 	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
 
 	const BREAKPOINT = 1024;
+
 	let show = false;
 	let navElement;
 
@@ -50,7 +45,7 @@
 	let isEditing = false;
 
 	onMount(async () => {
-		show = window.innerWidth > BREAKPOINT;
+		showSidebar.set(window.innerWidth > BREAKPOINT);
 		await chats.set(await getChatList(localStorage.token));
 
 		let touchstart;
@@ -61,10 +56,10 @@
 			const swipeDistance = Math.abs(touchend.screenX - touchstart.screenX);
 			if (touchstart.clientX < 40 && swipeDistance >= screenWidth / 4) {
 				if (touchend.screenX < touchstart.screenX) {
-					show = false;
+					showSidebar.set(false);
 				}
 				if (touchend.screenX > touchstart.screenX) {
-					show = true;
+					showSidebar.set(true);
 				}
 			}
 		}
@@ -80,8 +75,8 @@
 		};
 
 		const onResize = () => {
-			if (show && window.innerWidth < BREAKPOINT) {
-				show = false;
+			if ($showSidebar && window.innerWidth < BREAKPOINT) {
+				showSidebar.set(false);
 			}
 		};
 
@@ -167,13 +162,15 @@
 
 <div
 	bind:this={navElement}
-	class="h-screen max-h-[100dvh] min-h-screen {show
+	id="sidebar"
+	class="h-screen max-h-[100dvh] min-h-screen {$showSidebar
 		? 'lg:relative w-[260px]'
 		: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0
         "
+	data-state={$showSidebar}
 >
 	<div
-		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {show
+		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {$showSidebar
 			? ''
 			: 'invisible'}"
 	>
@@ -466,7 +463,7 @@
 								on:click={() => {
 									selectedChatId = chat.id;
 									if (window.innerWidth < 1024) {
-										show = false;
+										showSidebar.set(false);
 									}
 								}}
 								draggable="false"
@@ -803,14 +800,14 @@
 	>
 		<Tooltip
 			placement="right"
-			content={`${show ? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
+			content={`${$showSidebar ? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
 			touch={false}
 		>
 			<button
 				id="sidebar-toggle-button"
 				class=" group"
 				on:click={() => {
-					show = !show;
+					showSidebar.set(!$showSidebar);
 				}}
 				><span class="" data-state="closed"
 					><div

+ 2 - 0
src/lib/stores/index.ts

@@ -34,6 +34,8 @@ export const documents = writable([
 ]);
 
 export const settings: Writable<Settings> = writable({});
+
+export const showSidebar = writable(false);
 export const showSettings = writable(false);
 export const showChangelog = writable(false);
 

+ 1 - 2
src/routes/(app)/+page.svelte

@@ -877,7 +877,6 @@
 				/>
 			</div>
 		</div>
-
-		<MessageInput bind:files bind:prompt bind:autoScroll {messages} {submitPrompt} {stopResponse} />
 	</div>
 </div>
+<MessageInput bind:files bind:prompt bind:autoScroll {messages} {submitPrompt} {stopResponse} />

+ 10 - 11
src/routes/(app)/c/[id]/+page.svelte

@@ -900,17 +900,16 @@
 					/>
 				</div>
 			</div>
-
-			<MessageInput
-				bind:files
-				bind:prompt
-				bind:autoScroll
-				suggestionPrompts={selectedModelfile?.suggestionPrompts ??
-					$config.default_prompt_suggestions}
-				{messages}
-				{submitPrompt}
-				{stopResponse}
-			/>
 		</div>
 	</div>
+
+	<MessageInput
+		bind:files
+		bind:prompt
+		bind:autoScroll
+		suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
+		{messages}
+		{submitPrompt}
+		{stopResponse}
+	/>
 {/if}