From 1865cf083294c53a718a51570bdb64e42ac01084 Mon Sep 17 00:00:00 2001
From: Pascal <admin@serveurperso.com>
Date: Thu, 18 Sep 2025 16:58:33 +0200
Subject: [PATCH 1/4] feat: add dynamic model selector with persistence for
 llama-swap workflows

- Add ModelSelector component in ChatSidebar with dropdown interface
- Implement models store with localStorage persistence and auto-selection
- Add ModelsService for /v1/models API integration with multi-model setups
- Inject selected model into chat completion requests
- Display model capabilities with badges (Vision, Audio, etc.)
- Auto-refresh server props after model usage for accurate state

Particularly useful for llama-swap users managing multiple models dynamically.
---
 .../app/chat/ChatSidebar/ChatSidebar.svelte   |   3 +
 .../app/chat/ChatSidebar/ModelSelector.svelte | 175 ++++++++++++++
 .../webui/src/lib/components/app/index.ts     |   1 +
 tools/server/webui/src/lib/services/chat.ts   |   6 +
 tools/server/webui/src/lib/services/models.ts |  22 ++
 .../webui/src/lib/stores/chat.svelte.ts       |   9 +
 .../webui/src/lib/stores/models.svelte.ts     | 220 ++++++++++++++++++
 tools/server/webui/src/lib/types/api.d.ts     |  36 +++
 8 files changed, 472 insertions(+)
 create mode 100644 tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte
 create mode 100644 tools/server/webui/src/lib/services/models.ts
 create mode 100644 tools/server/webui/src/lib/stores/models.svelte.ts

diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
index 5976e5dd03d7..adf9f880ae67 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
@@ -13,6 +13,7 @@
 		updateConversationName
 	} from '$lib/stores/chat.svelte';
 	import ChatSidebarActions from './ChatSidebarActions.svelte';
+	import ModelSelector from './ModelSelector.svelte';
 
 	const sidebar = Sidebar.useSidebar();
 
@@ -110,6 +111,8 @@
 			<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
 		</a>
 
+		<ModelSelector />
+
 		<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
 	</Sidebar.Header>
 
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte
new file mode 100644
index 000000000000..48f3632da683
--- /dev/null
+++ b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte
@@ -0,0 +1,175 @@
+<script lang="ts">
+	import { onMount } from 'svelte';
+	import { Loader2, RefreshCw } from '@lucide/svelte';
+	import * as Select from '$lib/components/ui/select';
+	import { Button } from '$lib/components/ui/button';
+	import { Badge } from '$lib/components/ui/badge';
+	import {
+		fetchModels,
+		modelOptions,
+		modelsError,
+		modelsLoading,
+		modelsUpdating,
+		selectModel,
+		selectedModelId
+	} from '$lib/stores/models.svelte';
+	import type { ModelOption } from '$lib/stores/models.svelte';
+	import { toast } from 'svelte-sonner';
+
+	let options = $derived(modelOptions());
+	let loading = $derived(modelsLoading());
+	let updating = $derived(modelsUpdating());
+	let error = $derived(modelsError());
+	let activeId = $derived(selectedModelId());
+
+	let isMounted = $state(false);
+
+	onMount(async () => {
+		try {
+			await fetchModels();
+		} catch (error) {
+			reportError('Unable to load models', error);
+		} finally {
+			isMounted = true;
+		}
+	});
+
+	async function handleRefresh() {
+		try {
+			await fetchModels(true);
+			toast.success('Model list refreshed');
+		} catch (error) {
+			reportError('Failed to refresh model list', error);
+		}
+	}
+
+	async function handleSelect(value: string | undefined) {
+		if (!value) return;
+
+		const option = options.find((item) => item.id === value);
+		if (!option) {
+			reportError('Model is no longer available', new Error('Unknown model'));
+			return;
+		}
+
+		try {
+			await selectModel(option.id);
+			toast.success(`Switched to ${option.name}`);
+		} catch (error) {
+			reportError('Failed to switch model', error);
+		}
+	}
+
+	function reportError(message: string, error: unknown) {
+		const description = error instanceof Error ? error.message : 'Unknown error';
+		toast.error(message, { description });
+	}
+
+	function getDisplayOption(): ModelOption | undefined {
+		if (activeId) {
+			return options.find((option) => option.id === activeId);
+		}
+
+		return options[0];
+	}
+
+	function getCapabilityLabel(capability: string): string {
+		switch (capability.toLowerCase()) {
+			case 'vision':
+				return 'Vision';
+			case 'audio':
+				return 'Audio';
+			case 'multimodal':
+				return 'Multimodal';
+			case 'completion':
+				return 'Text';
+			default:
+				return capability;
+		}
+	}
+</script>
+
+<div class="rounded-lg border border-border/40 bg-background/5 p-3 shadow-sm">
+	<div class="mb-2 flex items-center justify-between">
+		<p class="text-xs font-medium text-muted-foreground">Model selector</p>
+
+		<Button
+			aria-label="Refresh model list"
+			class="h-7 w-7"
+			disabled={loading}
+			onclick={handleRefresh}
+			size="icon"
+			variant="ghost"
+		>
+			{#if loading}
+				<Loader2 class="h-4 w-4 animate-spin" />
+			{:else}
+				<RefreshCw class="h-4 w-4" />
+			{/if}
+		</Button>
+	</div>
+
+	{#if loading && options.length === 0 && !isMounted}
+		<div class="flex items-center gap-2 text-xs text-muted-foreground">
+			<Loader2 class="h-4 w-4 animate-spin" />
+			Loading models…
+		</div>
+	{:else if options.length === 0}
+		<p class="text-xs text-muted-foreground">No models available.</p>
+	{:else}
+		{@const selectedOption = getDisplayOption()}
+
+		<Select.Root
+			type="single"
+			value={selectedOption?.id ?? ''}
+			onValueChange={handleSelect}
+			disabled={loading || updating}
+		>
+			<Select.Trigger class="h-9 w-full justify-between">
+				<span class="truncate text-sm font-medium">{selectedOption?.name || 'Select model'}</span>
+
+				{#if updating}
+					<Loader2 class="h-4 w-4 animate-spin text-muted-foreground" />
+				{/if}
+			</Select.Trigger>
+
+			<Select.Content class="z-[100000]">
+				{#each options as option (option.id)}
+					<Select.Item value={option.id} label={option.name}>
+						<div class="flex flex-col gap-1">
+							<span class="text-sm font-medium">{option.name}</span>
+
+							{#if option.description}
+								<span class="text-xs text-muted-foreground">{option.description}</span>
+							{/if}
+
+							{#if option.capabilities.length > 0}
+								<div class="flex flex-wrap gap-1">
+									{#each option.capabilities as capability (capability)}
+										<Badge variant="secondary" class="text-[10px]">
+											{getCapabilityLabel(capability)}
+										</Badge>
+									{/each}
+								</div>
+							{/if}
+						</div>
+					</Select.Item>
+				{/each}
+			</Select.Content>
+		</Select.Root>
+
+		{#if selectedOption?.capabilities.length}
+			<div class="mt-3 flex flex-wrap gap-1">
+				{#each selectedOption.capabilities as capability (capability)}
+					<Badge variant="outline" class="text-[10px]">
+						{getCapabilityLabel(capability)}
+					</Badge>
+				{/each}
+			</div>
+		{/if}
+	{/if}
+
+	{#if error}
+		<p class="mt-2 text-xs text-destructive">{error}</p>
+	{/if}
+</div>
diff --git a/tools/server/webui/src/lib/components/app/index.ts b/tools/server/webui/src/lib/components/app/index.ts
index 7c1af27ecd3f..023186ad5e36 100644
--- a/tools/server/webui/src/lib/components/app/index.ts
+++ b/tools/server/webui/src/lib/components/app/index.ts
@@ -29,6 +29,7 @@ export { default as ChatSettingsFields } from './chat/ChatSettings/ChatSettingsF
 export { default as ChatSidebar } from './chat/ChatSidebar/ChatSidebar.svelte';
 export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatSidebarConversationItem.svelte';
 export { default as ChatSidebarSearch } from './chat/ChatSidebar/ChatSidebarSearch.svelte';
+export { default as ChatSidebarModelSelector } from './chat/ChatSidebar/ModelSelector.svelte';
 
 export { default as EmptyFileAlertDialog } from './dialogs/EmptyFileAlertDialog.svelte';
 
diff --git a/tools/server/webui/src/lib/services/chat.ts b/tools/server/webui/src/lib/services/chat.ts
index 369cdf4e8b93..2f89595cc2b8 100644
--- a/tools/server/webui/src/lib/services/chat.ts
+++ b/tools/server/webui/src/lib/services/chat.ts
@@ -1,4 +1,5 @@
 import { config } from '$lib/stores/settings.svelte';
+import { selectedModelName } from '$lib/stores/models.svelte';
 import { slotsService } from './slots';
 /**
  * ChatService - Low-level API communication layer for llama.cpp server interactions
@@ -117,6 +118,11 @@ export class ChatService {
 			stream
 		};
 
+		const activeModel = selectedModelName();
+		if (activeModel) {
+			requestBody.model = activeModel;
+		}
+
 		requestBody.reasoning_format = 'auto';
 
 		if (temperature !== undefined) requestBody.temperature = temperature;
diff --git a/tools/server/webui/src/lib/services/models.ts b/tools/server/webui/src/lib/services/models.ts
new file mode 100644
index 000000000000..1c7fa3b45631
--- /dev/null
+++ b/tools/server/webui/src/lib/services/models.ts
@@ -0,0 +1,22 @@
+import { base } from '$app/paths';
+import { config } from '$lib/stores/settings.svelte';
+import type { ApiModelListResponse } from '$lib/types/api';
+
+export class ModelsService {
+	static async list(): Promise<ApiModelListResponse> {
+		const currentConfig = config();
+		const apiKey = currentConfig.apiKey?.toString().trim();
+
+		const response = await fetch(`${base}/v1/models`, {
+			headers: {
+				...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
+			}
+		});
+
+		if (!response.ok) {
+			throw new Error(`Failed to fetch model list (status ${response.status})`);
+		}
+
+		return response.json() as Promise<ApiModelListResponse>;
+	}
+}
diff --git a/tools/server/webui/src/lib/stores/chat.svelte.ts b/tools/server/webui/src/lib/stores/chat.svelte.ts
index e7f59d0117d0..42f88483d212 100644
--- a/tools/server/webui/src/lib/stores/chat.svelte.ts
+++ b/tools/server/webui/src/lib/stores/chat.svelte.ts
@@ -332,12 +332,21 @@ class ChatStore {
 			return undefined;
 		};
 
+		let hasSyncedServerProps = false;
+
 		slotsService.startStreaming();
 
 		await chatService.sendMessage(allMessages, {
 			...this.getApiOptions(),
 
 			onChunk: (chunk: string) => {
+				if (!hasSyncedServerProps) {
+					hasSyncedServerProps = true;
+					void serverStore.fetchServerProps().catch((error) => {
+						console.warn('Failed to refresh server props after first chunk:', error);
+					});
+				}
+
 				streamedContent += chunk;
 				this.currentResponse = streamedContent;
 
diff --git a/tools/server/webui/src/lib/stores/models.svelte.ts b/tools/server/webui/src/lib/stores/models.svelte.ts
new file mode 100644
index 000000000000..24f0a3d89e3b
--- /dev/null
+++ b/tools/server/webui/src/lib/stores/models.svelte.ts
@@ -0,0 +1,220 @@
+import { browser } from '$app/environment';
+import { ModelsService } from '$lib/services/models';
+import type { ApiModelDataEntry, ApiModelDetails } from '$lib/types/api';
+
+export interface ModelOption {
+	id: string;
+	name: string;
+	model: string;
+	description?: string;
+	capabilities: string[];
+	details?: ApiModelDetails['details'];
+	meta?: ApiModelDataEntry['meta'];
+}
+
+type PersistedModelSelection = {
+	id: string;
+	model: string;
+};
+
+const STORAGE_KEY = 'llama.cpp:selectedModel';
+
+class ModelsStore {
+	private _models = $state<ModelOption[]>([]);
+	private _loading = $state(false);
+	private _updating = $state(false);
+	private _error = $state<string | null>(null);
+	private _selectedModelId = $state<string | null>(null);
+	private _selectedModelName = $state<string | null>(null);
+
+	constructor() {
+		const persisted = this.readPersistedSelection();
+		if (persisted) {
+			this._selectedModelId = persisted.id;
+			this._selectedModelName = persisted.model;
+		}
+	}
+
+	get models(): ModelOption[] {
+		return this._models;
+	}
+
+	get loading(): boolean {
+		return this._loading;
+	}
+
+	get updating(): boolean {
+		return this._updating;
+	}
+
+	get error(): string | null {
+		return this._error;
+	}
+
+	get selectedModelId(): string | null {
+		return this._selectedModelId;
+	}
+
+	get selectedModelName(): string | null {
+		return this._selectedModelName;
+	}
+
+	get selectedModel(): ModelOption | null {
+		if (!this._selectedModelId) {
+			return null;
+		}
+
+		return this._models.find((model) => model.id === this._selectedModelId) ?? null;
+	}
+
+	async fetch(force = false): Promise<void> {
+		if (this._loading) return;
+		if (this._models.length > 0 && !force) return;
+
+		this._loading = true;
+		this._error = null;
+
+		try {
+			const response = await ModelsService.list();
+
+			const models: ModelOption[] = response.data.map((item, index) => {
+				const details = response.models?.[index];
+				const rawCapabilities = Array.isArray(details?.capabilities)
+					? [...(details?.capabilities ?? [])]
+					: [];
+
+				return {
+					id: item.id,
+					name: details?.name || this.toDisplayName(item.id),
+					model: details?.model || item.id,
+					description: details?.description,
+					capabilities: rawCapabilities.filter((value): value is string => Boolean(value)),
+					details: details?.details,
+					meta: item.meta ?? null
+				} satisfies ModelOption;
+			});
+
+			this._models = models;
+
+			const persisted = this.readPersistedSelection();
+			let nextSelectionId = this._selectedModelId ?? persisted?.id ?? null;
+			let nextSelectionName = this._selectedModelName ?? persisted?.model ?? null;
+			if (nextSelectionId) {
+				const match = models.find((model) => model.id === nextSelectionId);
+				if (match) {
+					nextSelectionId = match.id;
+					nextSelectionName = match.model;
+				} else if (models[0]) {
+					nextSelectionId = models[0].id;
+					nextSelectionName = models[0].model;
+				} else {
+					nextSelectionId = null;
+					nextSelectionName = null;
+				}
+			} else if (models[0]) {
+				nextSelectionId = models[0].id;
+				nextSelectionName = models[0].model;
+			}
+
+			this._selectedModelId = nextSelectionId;
+			this._selectedModelName = nextSelectionName;
+			this.persistSelection(
+				nextSelectionId && nextSelectionName
+					? { id: nextSelectionId, model: nextSelectionName }
+					: null
+			);
+		} catch (error) {
+			this._models = [];
+			this._error = error instanceof Error ? error.message : 'Failed to load models';
+			throw error;
+		} finally {
+			this._loading = false;
+		}
+	}
+
+	async select(modelId: string): Promise<void> {
+		if (!modelId || this._updating) {
+			return;
+		}
+
+		if (this._selectedModelId === modelId) {
+			return;
+		}
+
+		const option = this._models.find((model) => model.id === modelId);
+		if (!option) {
+			throw new Error('Selected model is not available');
+		}
+
+		this._updating = true;
+		this._error = null;
+
+		try {
+			this._selectedModelId = option.id;
+			this._selectedModelName = option.model;
+			this.persistSelection({ id: option.id, model: option.model });
+		} finally {
+			this._updating = false;
+		}
+	}
+
+	private toDisplayName(id: string): string {
+		const segments = id.split(/\\|\//);
+		const candidate = segments.pop();
+		return candidate && candidate.trim().length > 0 ? candidate : id;
+	}
+
+	private readPersistedSelection(): PersistedModelSelection | null {
+		if (!browser) {
+			return null;
+		}
+
+		try {
+			const raw = localStorage.getItem(STORAGE_KEY);
+			if (!raw) {
+				return null;
+			}
+
+			const parsed = JSON.parse(raw);
+			if (parsed && typeof parsed.id === 'string') {
+				const id = parsed.id;
+				const model =
+					typeof parsed.model === 'string' && parsed.model.length > 0 ? parsed.model : id;
+				return { id, model };
+			}
+		} catch (error) {
+			console.warn('Failed to read model selection from localStorage:', error);
+		}
+
+		return null;
+	}
+
+	private persistSelection(selection: PersistedModelSelection | null): void {
+		if (!browser) {
+			return;
+		}
+
+		try {
+			if (selection) {
+				localStorage.setItem(STORAGE_KEY, JSON.stringify(selection));
+			} else {
+				localStorage.removeItem(STORAGE_KEY);
+			}
+		} catch (error) {
+			console.warn('Failed to persist model selection to localStorage:', error);
+		}
+	}
+}
+
+export const modelsStore = new ModelsStore();
+
+export const modelOptions = () => modelsStore.models;
+export const modelsLoading = () => modelsStore.loading;
+export const modelsUpdating = () => modelsStore.updating;
+export const modelsError = () => modelsStore.error;
+export const selectedModelId = () => modelsStore.selectedModelId;
+export const selectedModelName = () => modelsStore.selectedModelName;
+export const selectedModelOption = () => modelsStore.selectedModel;
+
+export const fetchModels = modelsStore.fetch.bind(modelsStore);
+export const selectModel = modelsStore.select.bind(modelsStore);
diff --git a/tools/server/webui/src/lib/types/api.d.ts b/tools/server/webui/src/lib/types/api.d.ts
index d0e60a6c1370..027ce213c35f 100644
--- a/tools/server/webui/src/lib/types/api.d.ts
+++ b/tools/server/webui/src/lib/types/api.d.ts
@@ -36,6 +36,41 @@ export interface ApiChatMessageData {
 	timestamp?: number;
 }
 
+export interface ApiModelDataEntry {
+	id: string;
+	object: string;
+	created: number;
+	owned_by: string;
+	meta?: Record<string, unknown> | null;
+}
+
+export interface ApiModelDetails {
+	name: string;
+	model: string;
+	modified_at?: string;
+	size?: string | number;
+	digest?: string;
+	type?: string;
+	description?: string;
+	tags?: string[];
+	capabilities?: string[];
+	parameters?: string;
+	details?: {
+		parent_model?: string;
+		format?: string;
+		family?: string;
+		families?: string[];
+		parameter_size?: string;
+		quantization_level?: string;
+	};
+}
+
+export interface ApiModelListResponse {
+	object: string;
+	data: ApiModelDataEntry[];
+	models?: ApiModelDetails[];
+}
+
 export interface ApiLlamaCppServerProps {
 	default_generation_settings: {
 		id: number;
@@ -120,6 +155,7 @@ export interface ApiChatCompletionRequest {
 		content: string | ApiChatMessageContentPart[];
 	}>;
 	stream?: boolean;
+	model?: string;
 	// Reasoning parameters
 	reasoning_format?: string;
 	// Generation parameters

From 0ff099bc7b3fdbcf1ba5a76ad400abf750c5f0bc Mon Sep 17 00:00:00 2001
From: Pascal <admin@serveurperso.com>
Date: Mon, 29 Sep 2025 17:49:10 +0200
Subject: [PATCH 2/4] feat: normalize model selector display names (base
 filename + OpenAI-Compat name) + move Toaster

---
 tools/server/webui/src/lib/stores/models.svelte.ts | 5 ++++-
 tools/server/webui/src/routes/+layout.svelte       | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/tools/server/webui/src/lib/stores/models.svelte.ts b/tools/server/webui/src/lib/stores/models.svelte.ts
index 24f0a3d89e3b..967346fcc964 100644
--- a/tools/server/webui/src/lib/stores/models.svelte.ts
+++ b/tools/server/webui/src/lib/stores/models.svelte.ts
@@ -82,10 +82,13 @@ class ModelsStore {
 				const rawCapabilities = Array.isArray(details?.capabilities)
 					? [...(details?.capabilities ?? [])]
 					: [];
+				const displayNameSource =
+					details?.name && details.name.trim().length > 0 ? details.name : item.id;
+				const displayName = this.toDisplayName(displayNameSource);
 
 				return {
 					id: item.id,
-					name: details?.name || this.toDisplayName(item.id),
+					name: displayName,
 					model: details?.model || item.id,
 					description: details?.description,
 					capabilities: rawCapabilities.filter((value): value is string => Boolean(value)),
diff --git a/tools/server/webui/src/routes/+layout.svelte b/tools/server/webui/src/routes/+layout.svelte
index bc204291960c..bcac572a2f53 100644
--- a/tools/server/webui/src/routes/+layout.svelte
+++ b/tools/server/webui/src/routes/+layout.svelte
@@ -143,7 +143,7 @@
 
 <ModeWatcher />
 
-<Toaster richColors />
+<Toaster position="top-center" richColors />
 
 <MaximumContextAlertDialog />
 

From 3998da1221f40737bbe8a6be92f6fafdf55b7b1a Mon Sep 17 00:00:00 2001
From: Pascal <admin@serveurperso.com>
Date: Fri, 3 Oct 2025 11:53:48 +0200
Subject: [PATCH 3/4] fix: capture freshly selected model in chat store for
 accurate reply metadata

---
 tools/server/webui/src/lib/stores/chat.svelte.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tools/server/webui/src/lib/stores/chat.svelte.ts b/tools/server/webui/src/lib/stores/chat.svelte.ts
index 42f88483d212..76085c6ddfb1 100644
--- a/tools/server/webui/src/lib/stores/chat.svelte.ts
+++ b/tools/server/webui/src/lib/stores/chat.svelte.ts
@@ -1,6 +1,7 @@
 import { DatabaseStore } from '$lib/stores/database';
 import { chatService, slotsService } from '$lib/services';
 import { serverStore } from '$lib/stores/server.svelte';
+import { selectedModelName } from '$lib/stores/models.svelte';
 import { config } from '$lib/stores/settings.svelte';
 import { filterByLeafNodeId, findLeafNode, findDescendantMessages } from '$lib/utils/branching';
 import { browser } from '$app/environment';
@@ -312,7 +313,7 @@ class ChatStore {
 
 		const captureModelIfNeeded = (updateDbImmediately = true): string | undefined => {
 			if (!modelCaptured) {
-				const currentModelName = serverStore.modelName;
+				const currentModelName = selectedModelName() ?? serverStore.modelName;
 
 				if (currentModelName) {
 					if (updateDbImmediately) {

From 03363a0485c859e0dcc912e21386693a60f75af5 Mon Sep 17 00:00:00 2001
From: Pascal <admin@serveurperso.com>
Date: Mon, 13 Oct 2025 13:56:16 +0200
Subject: [PATCH 4/4] webui: simplified model selector

---
 .../app/chat/ChatSidebar/ModelSelector.svelte | 164 +++++-------------
 1 file changed, 44 insertions(+), 120 deletions(-)

diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte
index 48f3632da683..ca74610b4a17 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ModelSelector.svelte
@@ -1,9 +1,7 @@
 <script lang="ts">
 	import { onMount } from 'svelte';
-	import { Loader2, RefreshCw } from '@lucide/svelte';
+	import { Loader2 } from '@lucide/svelte';
 	import * as Select from '$lib/components/ui/select';
-	import { Button } from '$lib/components/ui/button';
-	import { Badge } from '$lib/components/ui/badge';
 	import {
 		fetchModels,
 		modelOptions,
@@ -14,7 +12,6 @@
 		selectedModelId
 	} from '$lib/stores/models.svelte';
 	import type { ModelOption } from '$lib/stores/models.svelte';
-	import { toast } from 'svelte-sonner';
 
 	let options = $derived(modelOptions());
 	let loading = $derived(modelsLoading());
@@ -28,43 +25,28 @@
 		try {
 			await fetchModels();
 		} catch (error) {
-			reportError('Unable to load models', error);
+			console.error('Unable to load models:', error);
 		} finally {
 			isMounted = true;
 		}
 	});
 
-	async function handleRefresh() {
-		try {
-			await fetchModels(true);
-			toast.success('Model list refreshed');
-		} catch (error) {
-			reportError('Failed to refresh model list', error);
-		}
-	}
-
 	async function handleSelect(value: string | undefined) {
 		if (!value) return;
 
 		const option = options.find((item) => item.id === value);
 		if (!option) {
-			reportError('Model is no longer available', new Error('Unknown model'));
+			console.error('Model is no longer available');
 			return;
 		}
 
 		try {
 			await selectModel(option.id);
-			toast.success(`Switched to ${option.name}`);
 		} catch (error) {
-			reportError('Failed to switch model', error);
+			console.error('Failed to switch model:', error);
 		}
 	}
 
-	function reportError(message: string, error: unknown) {
-		const description = error instanceof Error ? error.message : 'Unknown error';
-		toast.error(message, { description });
-	}
-
 	function getDisplayOption(): ModelOption | undefined {
 		if (activeId) {
 			return options.find((option) => option.id === activeId);
@@ -72,104 +54,46 @@
 
 		return options[0];
 	}
-
-	function getCapabilityLabel(capability: string): string {
-		switch (capability.toLowerCase()) {
-			case 'vision':
-				return 'Vision';
-			case 'audio':
-				return 'Audio';
-			case 'multimodal':
-				return 'Multimodal';
-			case 'completion':
-				return 'Text';
-			default:
-				return capability;
-		}
-	}
 </script>
 
-<div class="rounded-lg border border-border/40 bg-background/5 p-3 shadow-sm">
-	<div class="mb-2 flex items-center justify-between">
-		<p class="text-xs font-medium text-muted-foreground">Model selector</p>
-
-		<Button
-			aria-label="Refresh model list"
-			class="h-7 w-7"
-			disabled={loading}
-			onclick={handleRefresh}
-			size="icon"
-			variant="ghost"
-		>
-			{#if loading}
-				<Loader2 class="h-4 w-4 animate-spin" />
-			{:else}
-				<RefreshCw class="h-4 w-4" />
-			{/if}
-		</Button>
+{#if loading && options.length === 0 && !isMounted}
+	<div class="flex items-center gap-2 text-xs text-muted-foreground">
+		<Loader2 class="h-4 w-4 animate-spin" />
+		Loading models…
 	</div>
-
-	{#if loading && options.length === 0 && !isMounted}
-		<div class="flex items-center gap-2 text-xs text-muted-foreground">
-			<Loader2 class="h-4 w-4 animate-spin" />
-			Loading models…
-		</div>
-	{:else if options.length === 0}
-		<p class="text-xs text-muted-foreground">No models available.</p>
-	{:else}
-		{@const selectedOption = getDisplayOption()}
-
-		<Select.Root
-			type="single"
-			value={selectedOption?.id ?? ''}
-			onValueChange={handleSelect}
-			disabled={loading || updating}
-		>
-			<Select.Trigger class="h-9 w-full justify-between">
-				<span class="truncate text-sm font-medium">{selectedOption?.name || 'Select model'}</span>
-
-				{#if updating}
-					<Loader2 class="h-4 w-4 animate-spin text-muted-foreground" />
-				{/if}
-			</Select.Trigger>
-
-			<Select.Content class="z-[100000]">
-				{#each options as option (option.id)}
-					<Select.Item value={option.id} label={option.name}>
-						<div class="flex flex-col gap-1">
-							<span class="text-sm font-medium">{option.name}</span>
-
-							{#if option.description}
-								<span class="text-xs text-muted-foreground">{option.description}</span>
-							{/if}
-
-							{#if option.capabilities.length > 0}
-								<div class="flex flex-wrap gap-1">
-									{#each option.capabilities as capability (capability)}
-										<Badge variant="secondary" class="text-[10px]">
-											{getCapabilityLabel(capability)}
-										</Badge>
-									{/each}
-								</div>
-							{/if}
-						</div>
-					</Select.Item>
-				{/each}
-			</Select.Content>
-		</Select.Root>
-
-		{#if selectedOption?.capabilities.length}
-			<div class="mt-3 flex flex-wrap gap-1">
-				{#each selectedOption.capabilities as capability (capability)}
-					<Badge variant="outline" class="text-[10px]">
-						{getCapabilityLabel(capability)}
-					</Badge>
-				{/each}
-			</div>
-		{/if}
-	{/if}
-
-	{#if error}
-		<p class="mt-2 text-xs text-destructive">{error}</p>
-	{/if}
-</div>
+{:else if options.length === 0}
+	<p class="text-xs text-muted-foreground">No models available.</p>
+{:else}
+	{@const selectedOption = getDisplayOption()}
+
+	<Select.Root
+		type="single"
+		value={selectedOption?.id ?? ''}
+		onValueChange={handleSelect}
+		disabled={loading || updating}
+	>
+		<Select.Trigger class="h-9 w-full justify-between">
+			<span class="truncate text-sm font-medium">{selectedOption?.name || 'Select model'}</span>
+
+			{#if updating}
+				<Loader2 class="h-4 w-4 animate-spin text-muted-foreground" />
+			{/if}
+		</Select.Trigger>
+
+		<Select.Content class="z-[100000]">
+			{#each options as option (option.id)}
+				<Select.Item value={option.id} label={option.name}>
+					<span class="text-sm font-medium">{option.name}</span>
+
+					{#if option.description}
+						<span class="text-xs text-muted-foreground">{option.description}</span>
+					{/if}
+				</Select.Item>
+			{/each}
+		</Select.Content>
+	</Select.Root>
+{/if}
+
+{#if error}
+	<p class="mt-2 text-xs text-destructive">{error}</p>
+{/if}
