Repository: ggml-org/llama.cpp
Pull request: webui: Add setting to always show sidebar on Desktop

Close #17807

Added 2 new settings for sidebar:

<img width="482" height="160" alt="Zrzut ekranu 2025-12-15 o 12 21 44" src="https://github.com/user-attachments/assets/bde66cb9-9e4c-466e-84a9-e5ccb926eb9a" />

Changed files:
filename: tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte
patch: @@ -109,6 +109,16 @@
 					key: 'disableAutoScroll',
 					label: 'Disable automatic scroll',
 					type: 'checkbox'
+				},
+				{
+					key: 'alwaysShowSidebarOnDesktop',
+					label: 'Always show sidebar on desktop',
+					type: 'checkbox'
+				},
+				{
+					key: 'autoShowSidebarOnNewChat',
+					label: 'Auto-show sidebar on new chat',
+					type: 'checkbox'
 				}
 			]
 		},
@@ -404,7 +414,7 @@
 	</div>
 
 	<!-- Mobile Header with Horizontal Scrollable Menu -->
-	<div class="flex flex-col md:hidden">
+	<div class="flex flex-col pt-6 md:hidden">
 		<div class="border-b border-border/30 py-4">
 			<!-- Horizontal Scrollable Category Menu with Navigation -->
 			<div class="relative flex items-center" style="scroll-padding: 1rem;">

filename: tools/server/webui/src/lib/constants/settings-config.ts
patch: @@ -15,6 +15,8 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
 	pdfAsImage: false,
 	disableAutoScroll: false,
 	renderUserContentAsMarkdown: false,
+	alwaysShowSidebarOnDesktop: false,
+	autoShowSidebarOnNewChat: true,
 	autoMicOnEmpty: false,
 	// make sure these default values are in sync with `common.h`
 	samplers: 'top_k;typ_p;top_p;min_p;temperature',
@@ -96,6 +98,10 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
 	disableAutoScroll:
 		'Disable automatic scrolling while messages stream so you can control the viewport position manually.',
 	renderUserContentAsMarkdown: 'Render user messages using markdown formatting in the chat.',
+	alwaysShowSidebarOnDesktop:
+		'Always keep the sidebar visible on desktop instead of auto-hiding it.',
+	autoShowSidebarOnNewChat:
+		'Automatically show sidebar when starting a new chat. Disable to keep the sidebar hidden until you click on it.',
 	autoMicOnEmpty:
 		'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.',
 	pyInterpreterEnabled:

filename: tools/server/webui/src/routes/+layout.svelte
patch: @@ -14,13 +14,18 @@
 	import { goto } from '$app/navigation';
 	import { modelsStore } from '$lib/stores/models.svelte';
 	import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
+	import { IsMobile } from '$lib/hooks/is-mobile.svelte';
 
 	let { children } = $props();
 
 	let isChatRoute = $derived(page.route.id === '/chat/[id]');
 	let isHomeRoute = $derived(page.route.id === '/');
 	let isNewChatMode = $derived(page.url.searchParams.get('new_chat') === 'true');
 	let showSidebarByDefault = $derived(activeMessages().length > 0 || isLoading());
+	let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop);
+	let autoShowSidebarOnNewChat = $derived(config().autoShowSidebarOnNewChat);
+	let isMobile = new IsMobile();
+	let isDesktop = $derived(!isMobile.current);
 	let sidebarOpen = $state(false);
 	let innerHeight = $state<number | undefined>();
 	let chatSidebar:
@@ -76,15 +81,23 @@
 	}
 
 	$effect(() => {
+		if (alwaysShowSidebarOnDesktop && isDesktop) {
+			sidebarOpen = true;
+			return;
+		}
+
 		if (isHomeRoute && !isNewChatMode) {
 			// Auto-collapse sidebar when navigating to home route (but not in new chat mode)
 			sidebarOpen = false;
 		} else if (isHomeRoute && isNewChatMode) {
 			// Keep sidebar open in new chat mode
 			sidebarOpen = true;
 		} else if (isChatRoute) {
-			// On chat routes, show sidebar by default
-			sidebarOpen = true;
+			// On chat routes, only auto-show sidebar if setting is enabled
+			if (autoShowSidebarOnNewChat) {
+				sidebarOpen = true;
+			}
+			// If setting is disabled, don't change sidebar state - let user control it manually
 		} else {
 			// Other routes follow default behavior
 			sidebarOpen = showSidebarByDefault;
@@ -190,12 +203,14 @@
 				<ChatSidebar bind:this={chatSidebar} />
 			</Sidebar.Root>
 
-			<Sidebar.Trigger
-				class="transition-left absolute left-0 z-[900] h-8 w-8 duration-200 ease-linear {sidebarOpen
-					? 'md:left-[var(--sidebar-width)]'
-					: ''}"
-				style="translate: 1rem 1rem;"
-			/>
+			{#if !(alwaysShowSidebarOnDesktop && isDesktop)}
+				<Sidebar.Trigger
+					class="transition-left absolute left-0 z-[900] h-8 w-8 duration-200 ease-linear {sidebarOpen
+						? 'md:left-[var(--sidebar-width)]'
+						: ''}"
+					style="translate: 1rem 1rem;"
+				/>
+			{/if}
 
 			<Sidebar.Inset class="flex flex-1 flex-col overflow-hidden">
 				{@render children?.()}