webui: load the model selected via ?model= when ?load=true (#26707)

* webui: load the model selected via ?model=

Opening the WebUI with ?model= selects the model but doesn't load it. The load only starts when you send your first message, so you wait for it then.

This loads it as soon as the page opens, while you're still typing your prompt. It's what the model dropdown already does, and it isn't awaited, so the UI still works while the model loads.

This is the path the Llama macOS app uses to open the WebUI, so it's a common way in.

* webui: gate the load behind ?load=true

Loading on landing is opt-in, so a plain ?model= link behaves as before and doesn't allocate memory on its own.

* webui: name the chat URL params

Collects the query params the chat routes read into a URL_PARAMS constant, instead of repeating the literals across three files. NEW_CHAT_PARAM folds into it.
This commit is contained in:
Emanuil Rusev
2026-08-07 15:31:40 +02:00
committed by GitHub
parent 34e9ee57f5
commit f4f7758cae
4 changed files with 37 additions and 15 deletions
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes'; import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
import { URL_PARAMS } from '$lib/constants';
import * as AlertDialog from '$lib/components/ui/alert-dialog'; import * as AlertDialog from '$lib/components/ui/alert-dialog';
import { AlertTriangle, ArrowRight } from '@lucide/svelte'; import { AlertTriangle, ArrowRight } from '@lucide/svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
@@ -22,7 +23,7 @@
function handleSelectModel(model: string) { function handleSelectModel(model: string) {
// Build URL with selected model, preserving other params // Build URL with selected model, preserving other params
const url = new URL(page.url); const url = new URL(page.url);
url.searchParams.set('model', model); url.searchParams.set(URL_PARAMS.MODEL, model);
handleOpenChange(false); handleOpenChange(false);
goto(url.toString()); goto(url.toString());
+12 -2
View File
@@ -1,4 +1,14 @@
export const NEW_CHAT_PARAM = 'new_chat'; /** Query params the chat routes read from the URL. */
export const URL_PARAMS = {
/** Prompt to send on arrival. */
QUERY: 'q',
/** Model to select. */
MODEL: 'model',
/** Load the selected model instead of waiting for the first message. */
LOAD: 'load',
/** Start a new chat. */
NEW_CHAT: 'new_chat'
} as const;
/** Settings section slugs — used for routes and navigation. */ /** Settings section slugs — used for routes and navigation. */
export const SETTINGS_SECTION_SLUGS = { export const SETTINGS_SECTION_SLUGS = {
@@ -16,7 +26,7 @@ export const ROUTES = {
/** Root — start of the app. */ /** Root — start of the app. */
START: '#/', START: '#/',
/** New chat — root with new chat query param. */ /** New chat — root with new chat query param. */
NEW_CHAT: `?${NEW_CHAT_PARAM}=true#/`, NEW_CHAT: `?${URL_PARAMS.NEW_CHAT}=true#/`,
/** Chat base — for dynamic chat URLs use RouterService. */ /** Chat base — for dynamic chat URLs use RouterService. */
CHAT: '#/chat', CHAT: '#/chat',
/** MCP servers. */ /** MCP servers. */
+18 -7
View File
@@ -3,14 +3,16 @@
import { chatStore } from '$lib/stores/chat.svelte'; import { chatStore } from '$lib/stores/chat.svelte';
import { conversationsStore, isConversationsInitialized } from '$lib/stores/conversations.svelte'; import { conversationsStore, isConversationsInitialized } from '$lib/stores/conversations.svelte';
import { modelsStore, modelOptions } from '$lib/stores/models.svelte'; import { modelsStore, modelOptions } from '$lib/stores/models.svelte';
import { isRouterMode } from '$lib/stores/server.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { page } from '$app/state'; import { page } from '$app/state';
import { replaceState } from '$app/navigation'; import { replaceState } from '$app/navigation';
import { APP_NAME, NEW_CHAT_PARAM } from '$lib/constants'; import { APP_NAME, URL_PARAMS } from '$lib/constants';
let qParam = $derived(page.url.searchParams.get('q')); let qParam = $derived(page.url.searchParams.get(URL_PARAMS.QUERY));
let modelParam = $derived(page.url.searchParams.get('model')); let modelParam = $derived(page.url.searchParams.get(URL_PARAMS.MODEL));
let newChatParam = $derived(page.url.searchParams.get(NEW_CHAT_PARAM)); let newChatParam = $derived(page.url.searchParams.get(URL_PARAMS.NEW_CHAT));
let loadParam = $derived(page.url.searchParams.get(URL_PARAMS.LOAD));
// Dialog state for model not available error // Dialog state for model not available error
let showModelNotAvailable = $state(false); let showModelNotAvailable = $state(false);
@@ -23,9 +25,10 @@
function clearUrlParams() { function clearUrlParams() {
const url = new URL(page.url); const url = new URL(page.url);
url.searchParams.delete('q'); url.searchParams.delete(URL_PARAMS.QUERY);
url.searchParams.delete('model'); url.searchParams.delete(URL_PARAMS.MODEL);
url.searchParams.delete(NEW_CHAT_PARAM); url.searchParams.delete(URL_PARAMS.LOAD);
url.searchParams.delete(URL_PARAMS.NEW_CHAT);
replaceState(url.toString(), {}); replaceState(url.toString(), {});
} }
@@ -39,6 +42,14 @@
if (model) { if (model) {
try { try {
await modelsStore.selectModelById(model.id); await modelsStore.selectModelById(model.id);
// with ?load=true, start loading right away so the model is ready sooner;
// not awaited, so the UI stays usable during the load
if (loadParam === 'true' && isRouterMode() && !modelsStore.isModelLoaded(model.id)) {
modelsStore
.loadModel(model.id)
.catch((error) => console.error('Failed to load model:', error));
}
} catch (error) { } catch (error) {
console.error('Failed to select model:', error); console.error('Failed to select model:', error);
requestedModelName = modelParam; requestedModelName = modelParam;
@@ -3,7 +3,7 @@
import { page } from '$app/state'; import { page } from '$app/state';
import { afterNavigate } from '$app/navigation'; import { afterNavigate } from '$app/navigation';
import { DialogModelNotAvailable } from '$lib/components/app'; import { DialogModelNotAvailable } from '$lib/components/app';
import { APP_NAME, ROUTES } from '$lib/constants'; import { APP_NAME, ROUTES, URL_PARAMS } from '$lib/constants';
import { chatStore } from '$lib/stores/chat.svelte'; import { chatStore } from '$lib/stores/chat.svelte';
import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte'; import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte';
import { modelsStore, modelOptions } from '$lib/stores/models.svelte'; import { modelsStore, modelOptions } from '$lib/stores/models.svelte';
@@ -12,8 +12,8 @@
let currentChatId: string | undefined = undefined; let currentChatId: string | undefined = undefined;
// URL parameters for prompt and model selection // URL parameters for prompt and model selection
let qParam = $derived(page.url.searchParams.get('q')); let qParam = $derived(page.url.searchParams.get(URL_PARAMS.QUERY));
let modelParam = $derived(page.url.searchParams.get('model')); let modelParam = $derived(page.url.searchParams.get(URL_PARAMS.MODEL));
// Dialog state for model not available error // Dialog state for model not available error
let showModelNotAvailable = $state(false); let showModelNotAvailable = $state(false);
@@ -28,8 +28,8 @@
*/ */
function clearUrlParams() { function clearUrlParams() {
const url = new URL(page.url); const url = new URL(page.url);
url.searchParams.delete('q'); url.searchParams.delete(URL_PARAMS.QUERY);
url.searchParams.delete('model'); url.searchParams.delete(URL_PARAMS.MODEL);
replaceState(url.toString(), {}); replaceState(url.toString(), {});
} }