Browse Source

Merge pull request #751 from ollama-webui/styling

feat: fullscreen mode
Timothy Jaeryang Baek 1 year ago
parent
commit
a7bb692a54

+ 15 - 8
src/lib/components/chat/MessageInput.svelte

@@ -55,6 +55,11 @@
 	let isRecording = false;
 	const MIN_DECIBELS = -45;
 
+	const scrollToBottom = () => {
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollHeight;
+	};
+
 	const startRecording = async () => {
 		const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
 		mediaRecorder = new MediaRecorder(stream);
@@ -371,17 +376,17 @@
 	</div>
 {/if}
 
-<div class="fixed bottom-0 w-full">
-	<div class="px-2.5 pt-2.5 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
+<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>
+			<div class="relative">
 				{#if autoScroll === false && messages.length > 0}
-					<div class=" flex justify-center mb-4">
+					<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;
-								window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
+								scrollToBottom();
 							}}
 						>
 							<svg
@@ -401,7 +406,7 @@
 				{/if}
 			</div>
 
-			<div class="w-full">
+			<div class="w-full relative">
 				{#if prompt.charAt(0) === '/'}
 					<Prompts bind:this={promptsElement} bind:prompt />
 				{:else if prompt.charAt(0) === '#'}
@@ -432,14 +437,16 @@
 						bind:chatInputPlaceholder
 						{messages}
 					/>
-				{:else if messages.length == 0 && suggestionPrompts.length !== 0}
+				{/if}
+
+				{#if messages.length == 0 && suggestionPrompts.length !== 0}
 					<Suggestions {suggestionPrompts} {submitPrompt} />
 				{/if}
 			</div>
 		</div>
 	</div>
 	<div class="bg-white dark:bg-gray-900">
-		<div class="max-w-3xl px-2.5 -mb-0.5 mx-auto inset-x-0">
+		<div class="max-w-3xl px-2.5 mx-auto inset-x-0">
 			<div class=" pb-2">
 				<input
 					bind:this={filesInputElement}

+ 1 - 1
src/lib/components/chat/MessageInput/Documents.svelte

@@ -88,7 +88,7 @@
 </script>
 
 {#if filteredItems.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
-	<div class="md:px-2 mb-3 text-left w-full">
+	<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
 		<div class="flex w-full rounded-lg border border-gray-100 dark:border-gray-700">
 			<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-lg text-center">
 				<div class=" text-lg font-semibold mt-2">#</div>

+ 1 - 1
src/lib/components/chat/MessageInput/Models.svelte

@@ -120,7 +120,7 @@
 </script>
 
 {#if filteredModels.length > 0}
-	<div class="md:px-2 mb-3 text-left w-full">
+	<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
 		<div class="flex w-full rounded-lg border border-gray-100 dark:border-gray-700">
 			<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-lg text-center">
 				<div class=" text-lg font-semibold mt-2">@</div>

+ 1 - 1
src/lib/components/chat/MessageInput/PromptCommands.svelte

@@ -47,7 +47,7 @@
 </script>
 
 {#if filteredPromptCommands.length > 0}
-	<div class="md:px-2 mb-3 text-left w-full">
+	<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
 		<div class="flex w-full rounded-lg border border-gray-100 dark:border-gray-700">
 			<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-lg text-center">
 				<div class=" text-lg font-semibold mt-2">/</div>

+ 99 - 89
src/lib/components/chat/Messages.svelte

@@ -29,10 +29,15 @@
 	$: if (autoScroll && bottomPadding) {
 		(async () => {
 			await tick();
-			window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
+			scrollToBottom();
 		})();
 	}
 
+	const scrollToBottom = () => {
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollHeight;
+	};
+
 	const copyToClipboard = (text) => {
 		if (!navigator.clipboard) {
 			var textArea = document.createElement('textarea');
@@ -160,10 +165,11 @@
 
 		await tick();
 
-		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
+		const element = document.getElementById('messages-container');
+		autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
 
 		setTimeout(() => {
-			window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
+			scrollToBottom();
 		}, 100);
 	};
 
@@ -208,9 +214,11 @@
 
 		await tick();
 
-		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
+		const element = document.getElementById('messages-container');
+		autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
+
 		setTimeout(() => {
-			window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
+			scrollToBottom();
 		}, 100);
 	};
 </script>
@@ -218,95 +226,97 @@
 {#if messages.length == 0}
 	<Placeholder models={selectedModels} modelfiles={selectedModelfiles} />
 {:else}
-	{#key chatId}
-		{#each messages as message, messageIdx}
-			<div class=" w-full">
-				<div
-					class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
-						? 'max-w-full'
-						: 'max-w-3xl'} mx-auto rounded-lg group"
-				>
-					{#if message.role === 'user'}
-						<UserMessage
-							user={$user}
-							{message}
-							siblings={message.parentId !== null
-								? history.messages[message.parentId]?.childrenIds ?? []
-								: Object.values(history.messages)
-										.filter((message) => message.parentId === null)
-										.map((message) => message.id) ?? []}
-							{confirmEditMessage}
-							{showPreviousMessage}
-							{showNextMessage}
-							{copyToClipboard}
-						/>
-
-						{#if messages.length - 1 === messageIdx && processing !== ''}
-							<div class="flex my-2.5 ml-12 items-center w-fit space-x-2.5">
-								<div class=" dark:text-blue-100">
-									<svg
-										class=" w-4 h-4 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);
+	<div class=" pb-10">
+		{#key chatId}
+			{#each messages as message, messageIdx}
+				<div class=" w-full">
+					<div
+						class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
+							? 'max-w-full'
+							: 'max-w-3xl'} mx-auto rounded-lg group"
+					>
+						{#if message.role === 'user'}
+							<UserMessage
+								user={$user}
+								{message}
+								siblings={message.parentId !== null
+									? history.messages[message.parentId]?.childrenIds ?? []
+									: Object.values(history.messages)
+											.filter((message) => message.parentId === null)
+											.map((message) => message.id) ?? []}
+								{confirmEditMessage}
+								{showPreviousMessage}
+								{showNextMessage}
+								{copyToClipboard}
+							/>
+
+							{#if messages.length - 1 === messageIdx && processing !== ''}
+								<div class="flex my-2.5 ml-12 items-center w-fit space-x-2.5">
+									<div class=" dark:text-blue-100">
+										<svg
+											class=" w-4 h-4 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
-									>
-								</div>
-								<div class=" text-sm font-medium">
-									{processing}
+												@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
+										>
+									</div>
+									<div class=" text-sm font-medium">
+										{processing}
+									</div>
 								</div>
-							</div>
+							{/if}
+						{:else}
+							<ResponseMessage
+								{message}
+								modelfiles={selectedModelfiles}
+								siblings={history.messages[message.parentId]?.childrenIds ?? []}
+								isLastMessage={messageIdx + 1 === messages.length}
+								{confirmEditResponseMessage}
+								{showPreviousMessage}
+								{showNextMessage}
+								{rateMessage}
+								{copyToClipboard}
+								{continueGeneration}
+								{regenerateResponse}
+							/>
 						{/if}
-					{:else}
-						<ResponseMessage
-							{message}
-							modelfiles={selectedModelfiles}
-							siblings={history.messages[message.parentId]?.childrenIds ?? []}
-							isLastMessage={messageIdx + 1 === messages.length}
-							{confirmEditResponseMessage}
-							{showPreviousMessage}
-							{showNextMessage}
-							{rateMessage}
-							{copyToClipboard}
-							{continueGeneration}
-							{regenerateResponse}
-						/>
-					{/if}
+					</div>
 				</div>
-			</div>
-		{/each}
+			{/each}
 
-		{#if bottomPadding}
-			<div class=" mb-10" />
-		{/if}
-	{/key}
+			{#if bottomPadding}
+				<div class=" mb-10" />
+			{/if}
+		{/key}
+	</div>
 {/if}

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

@@ -16,7 +16,7 @@
 </script>
 
 {#if models.length > 0}
-	<div class="m-auto text-center max-w-md pb-56 px-2">
+	<div class="m-auto text-center max-w-md px-2">
 		<div class="flex justify-center mt-8">
 			<div class="flex -space-x-4 mb-1">
 				{#each models as model, modelIdx}

+ 3 - 5
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -270,9 +270,7 @@
 				{#if message.model in modelfiles}
 					{modelfiles[message.model]?.title}
 				{:else}
-					Ollama <span class=" text-gray-500 text-sm font-medium"
-						>{message.model ? ` ${message.model}` : ''}</span
-					>
+					{message.model ? ` ${message.model}` : ''}
 				{/if}
 
 				{#if message.timestamp}
@@ -365,7 +363,7 @@
 								{#if message.done}
 									<div class=" flex justify-start space-x-1 -mt-2 overflow-x-auto buttons">
 										{#if siblings.length > 1}
-											<div class="flex self-center">
+											<div class="flex self-center min-w-fit">
 												<button
 													class="self-center"
 													on:click={() => {
@@ -386,7 +384,7 @@
 													</svg>
 												</button>
 
-												<div class="text-xs font-bold self-center">
+												<div class="text-xs font-bold self-center min-w-fit">
 													{siblings.indexOf(message.id) + 1} / {siblings.length}
 												</div>
 

+ 2 - 2
src/lib/components/chat/Settings/Models.svelte

@@ -3,7 +3,7 @@
 	import toast from 'svelte-french-toast';
 
 	import { createModel, deleteModel, pullModel } from '$lib/apis/ollama';
-	import { WEBUI_API_BASE_URL } from '$lib/constants';
+	import { WEBUI_API_BASE_URL, WEBUI_NAME } from '$lib/constants';
 	import { models, user } from '$lib/stores';
 	import { splitStream } from '$lib/utils';
 
@@ -59,7 +59,7 @@
 				} else {
 					toast.success(`Model '${modelName}' has been successfully downloaded.`);
 
-					const notification = new Notification(`Ollama`, {
+					const notification = new Notification(WEBUI_NAME, {
 						body: `Model '${modelName}' has been successfully downloaded.`,
 						icon: '/favicon.png'
 					});

+ 1 - 1
src/lib/components/layout/Navbar.svelte

@@ -69,7 +69,7 @@
 <ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} />
 <nav
 	id="nav"
-	class=" fixed py-2.5 top-0 flex flex-row justify-center bg-white/95 dark:bg-gray-900/90 dark:text-gray-200 backdrop-blur-xl w-screen z-30"
+	class=" sticky py-2.5 top-0 flex flex-row justify-center bg-white/95 dark:bg-gray-900/90 dark:text-gray-200 backdrop-blur-xl z-30"
 >
 	<div
 		class=" flex {$settings?.fullScreenMode ?? null

+ 6 - 2
src/lib/components/layout/Sidebar.svelte

@@ -89,10 +89,14 @@
 	bind:this={navElement}
 	class="h-screen {show
 		? ''
-		: '-translate-x-[260px]'}  w-[260px] fixed top-0 left-0 z-40 transition bg-black text-gray-200 shadow-2xl text-sm
+		: '-translate-x-[260px] w-[0px]'}  w-[260px] min-w[260px] bg-black text-gray-200 shadow-2xl text-sm transition z-40 fixed top-0 left-0 lg:relative
         "
 >
-	<div class="py-2.5 my-auto flex flex-col justify-between h-screen">
+	<div
+		class="py-2.5 my-auto flex flex-col justify-between h-screen w-[260px] {show
+			? ''
+			: 'invisible'}"
+	>
 		<div class="px-2.5 flex justify-center space-x-2">
 			<button
 				id="sidebar-new-chat-button"

+ 55 - 46
src/routes/(app)/+page.svelte

@@ -137,6 +137,11 @@
 		});
 	};
 
+	const scrollToBottom = () => {
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollHeight;
+	};
+
 	//////////////////////////
 	// Ollama functions
 	//////////////////////////
@@ -316,7 +321,7 @@
 		await tick();
 
 		// Scroll down
-		window.scrollTo({ top: document.body.scrollHeight });
+		scrollToBottom();
 
 		const messagesBody = [
 			$settings.system
@@ -440,7 +445,7 @@
 														selectedModelfile.title.charAt(0).toUpperCase() +
 														selectedModelfile.title.slice(1)
 												  }`
-												: `Ollama - ${model}`,
+												: `${model}`,
 											{
 												body: responseMessage.content,
 												icon: selectedModelfile?.imageUrl ?? '/favicon.png'
@@ -469,7 +474,7 @@
 				}
 
 				if (autoScroll) {
-					window.scrollTo({ top: document.body.scrollHeight });
+					scrollToBottom();
 				}
 			}
 
@@ -508,7 +513,7 @@
 		await tick();
 
 		if (autoScroll) {
-			window.scrollTo({ top: document.body.scrollHeight });
+			scrollToBottom();
 		}
 
 		if (messages.length == 2 && messages.at(1).content !== '') {
@@ -519,8 +524,7 @@
 
 	const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
 		const responseMessage = history.messages[responseMessageId];
-
-		window.scrollTo({ top: document.body.scrollHeight });
+		scrollToBottom();
 
 		const res = await generateOpenAIChatCompletion(localStorage.token, {
 			model: model,
@@ -628,7 +632,7 @@
 				}
 
 				if (autoScroll) {
-					window.scrollTo({ top: document.body.scrollHeight });
+					scrollToBottom();
 				}
 			}
 
@@ -672,7 +676,7 @@
 		await tick();
 
 		if (autoScroll) {
-			window.scrollTo({ top: document.body.scrollHeight });
+			scrollToBottom();
 		}
 
 		if (messages.length == 2) {
@@ -783,47 +787,52 @@
 	};
 </script>
 
-<svelte:window
-	on:scroll={(e) => {
-		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
-	}}
-/>
-
-<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
-<div class="min-h-screen w-full flex justify-center">
-	<div class=" py-2.5 flex flex-col justify-between w-full">
+<div class="min-h-screen max-h-screen w-full flex flex-col">
+	<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
+	<div class="flex flex-col flex-auto">
 		<div
-			class="{$settings?.fullScreenMode ?? null
-				? 'max-w-full'
-				: 'max-w-2xl md:px-0'} mx-auto w-full px-4 mt-10"
+			class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
+			id="messages-container"
+			on:scroll={(e) => {
+				autoScroll = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50;
+			}}
 		>
-			<ModelSelector bind:selectedModels disabled={messages.length > 0} />
+			<div
+				class="{$settings?.fullScreenMode ?? null
+					? 'max-w-full'
+					: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
+			>
+				<ModelSelector
+					bind:selectedModels
+					disabled={messages.length > 0 && !selectedModels.includes('')}
+				/>
+			</div>
+
+			<div class=" h-full w-full flex flex-col py-8">
+				<Messages
+					chatId={$chatId}
+					{selectedModels}
+					{selectedModelfiles}
+					{processing}
+					bind:history
+					bind:messages
+					bind:autoScroll
+					bottomPadding={files.length > 0}
+					{sendPrompt}
+					{continueGeneration}
+					{regenerateResponse}
+				/>
+			</div>
 		</div>
 
-		<div class=" h-full mt-10 mb-32 w-full flex flex-col">
-			<Messages
-				chatId={$chatId}
-				{selectedModels}
-				{selectedModelfiles}
-				{processing}
-				bind:history
-				bind:messages
-				bind:autoScroll
-				bottomPadding={files.length > 0}
-				{sendPrompt}
-				{continueGeneration}
-				{regenerateResponse}
-			/>
-		</div>
+		<MessageInput
+			bind:files
+			bind:prompt
+			bind:autoScroll
+			suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
+			{messages}
+			{submitPrompt}
+			{stopResponse}
+		/>
 	</div>
-
-	<MessageInput
-		bind:files
-		bind:prompt
-		bind:autoScroll
-		suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
-		{messages}
-		{submitPrompt}
-		{stopResponse}
-	/>
 </div>

+ 69 - 61
src/routes/(app)/c/[id]/+page.svelte

@@ -153,6 +153,11 @@
 		}
 	};
 
+	const scrollToBottom = () => {
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollHeight;
+	};
+
 	//////////////////////////
 	// Ollama functions
 	//////////////////////////
@@ -330,7 +335,7 @@
 		await tick();
 
 		// Scroll down
-		window.scrollTo({ top: document.body.scrollHeight });
+		scrollToBottom();
 
 		const messagesBody = [
 			$settings.system
@@ -454,7 +459,7 @@
 														selectedModelfile.title.charAt(0).toUpperCase() +
 														selectedModelfile.title.slice(1)
 												  }`
-												: `Ollama - ${model}`,
+												: `${model}`,
 											{
 												body: responseMessage.content,
 												icon: selectedModelfile?.imageUrl ?? '/favicon.png'
@@ -483,7 +488,7 @@
 				}
 
 				if (autoScroll) {
-					window.scrollTo({ top: document.body.scrollHeight });
+					scrollToBottom();
 				}
 			}
 
@@ -522,7 +527,7 @@
 		await tick();
 
 		if (autoScroll) {
-			window.scrollTo({ top: document.body.scrollHeight });
+			scrollToBottom();
 		}
 
 		if (messages.length == 2 && messages.at(1).content !== '') {
@@ -534,7 +539,7 @@
 	const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
 		const responseMessage = history.messages[responseMessageId];
 
-		window.scrollTo({ top: document.body.scrollHeight });
+		scrollToBottom();
 
 		const res = await generateOpenAIChatCompletion(localStorage.token, {
 			model: model,
@@ -642,7 +647,7 @@
 				}
 
 				if (autoScroll) {
-					window.scrollTo({ top: document.body.scrollHeight });
+					scrollToBottom();
 				}
 			}
 
@@ -686,7 +691,7 @@
 		await tick();
 
 		if (autoScroll) {
-			window.scrollTo({ top: document.body.scrollHeight });
+			scrollToBottom();
 		}
 
 		if (messages.length == 2) {
@@ -797,66 +802,69 @@
 	});
 </script>
 
-<svelte:window
-	on:scroll={(e) => {
-		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
-	}}
-/>
-
 {#if loaded}
-	<Navbar
-		{title}
-		shareEnabled={messages.length > 0}
-		initNewChat={async () => {
-			if (currentRequestId !== null) {
-				await cancelChatCompletion(localStorage.token, currentRequestId);
-				currentRequestId = null;
-			}
+	<div class="min-h-screen max-h-screen w-full flex flex-col">
+		<Navbar
+			{title}
+			shareEnabled={messages.length > 0}
+			initNewChat={async () => {
+				if (currentRequestId !== null) {
+					await cancelChatCompletion(localStorage.token, currentRequestId);
+					currentRequestId = null;
+				}
 
-			goto('/');
-		}}
-		{tags}
-		{addTag}
-		{deleteTag}
-	/>
-	<div class="min-h-screen w-full flex justify-center">
-		<div class=" py-2.5 flex flex-col justify-between w-full">
+				goto('/');
+			}}
+			{tags}
+			{addTag}
+			{deleteTag}
+		/>
+		<div class="flex flex-col flex-auto">
 			<div
-				class="{$settings?.fullScreenMode ?? null
-					? 'max-w-full'
-					: 'max-w-2xl md:px-0'} mx-auto w-full px-4 mt-10"
+				class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
+				id="messages-container"
+				on:scroll={(e) => {
+					autoScroll = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50;
+				}}
 			>
-				<ModelSelector
-					bind:selectedModels
-					disabled={messages.length > 0 && !selectedModels.includes('')}
-				/>
+				<div
+					class="{$settings?.fullScreenMode ?? null
+						? 'max-w-full'
+						: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
+				>
+					<ModelSelector
+						bind:selectedModels
+						disabled={messages.length > 0 && !selectedModels.includes('')}
+					/>
+				</div>
+
+				<div class=" h-full w-full flex flex-col py-8">
+					<Messages
+						chatId={$chatId}
+						{selectedModels}
+						{selectedModelfiles}
+						{processing}
+						bind:history
+						bind:messages
+						bind:autoScroll
+						bottomPadding={files.length > 0}
+						{sendPrompt}
+						{continueGeneration}
+						{regenerateResponse}
+					/>
+				</div>
 			</div>
 
-			<div class=" h-full mt-10 mb-32 w-full flex flex-col">
-				<Messages
-					chatId={$chatId}
-					{selectedModels}
-					{selectedModelfiles}
-					{processing}
-					bind:history
-					bind:messages
-					bind:autoScroll
-					bottomPadding={files.length > 0}
-					{sendPrompt}
-					{continueGeneration}
-					{regenerateResponse}
-				/>
-			</div>
+			<MessageInput
+				bind:files
+				bind:prompt
+				bind:autoScroll
+				suggestionPrompts={selectedModelfile?.suggestionPrompts ??
+					$config.default_prompt_suggestions}
+				{messages}
+				{submitPrompt}
+				{stopResponse}
+			/>
 		</div>
-
-		<MessageInput
-			bind:files
-			bind:prompt
-			bind:autoScroll
-			suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
-			{messages}
-			{submitPrompt}
-			{stopResponse}
-		/>
 	</div>
 {/if}

+ 2 - 1
src/routes/error/+page.svelte

@@ -1,5 +1,6 @@
 <script>
 	import { goto } from '$app/navigation';
+	import { WEBUI_NAME } from '$lib/constants';
 	import { config } from '$lib/stores';
 	import { onMount } from 'svelte';
 
@@ -19,7 +20,7 @@
 		<div class="absolute rounded-xl w-full h-full backdrop-blur flex justify-center">
 			<div class="m-auto pb-44 flex flex-col justify-center">
 				<div class="max-w-md">
-					<div class="text-center text-2xl font-medium z-50">Ollama WebUI Backend Required</div>
+					<div class="text-center text-2xl font-medium z-50">{WEBUI_NAME} Backend Required</div>
 
 					<div class=" mt-4 text-center text-sm w-full">
 						Oops! You're using an unsupported method (frontend only). Please serve the WebUI from