diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 7fc75996ae..b755acf80f 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -357,8 +357,10 @@ async def get_all_models(request: Request, user: UserModel | None = None): # Fan-out tag requests to every backend tasks = [] - for idx, url in enumerate(await Config.get('ollama.base_urls', [])): - api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, url) + base_urls = await Config.get('ollama.base_urls', []) + api_configs = await Config.get('ollama.api_configs', {}) + for idx, url in enumerate(base_urls): + api_config = resolve_api_config(api_configs, idx, url) if not api_config: tasks.append(send_get_request(f'{url}/api/tags', user=user)) elif api_config.get('enable', True): @@ -368,12 +370,16 @@ async def get_all_models(request: Request, user: UserModel | None = None): responses = await asyncio.gather(*tasks) + # Track which backends failed so we can skip them for /api/ps + failed_idxs: set[int] = set() + # Post-process each response: apply prefix_id, tags, model filtering for idx, response in enumerate(responses): if not response: + failed_idxs.add(idx) continue - url = (await Config.get('ollama.base_urls', []))[idx] - api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, url) + url = base_urls[idx] + api_config = resolve_api_config(api_configs, idx, url) connection_type = api_config.get('connection_type', 'local') prefix_id = api_config.get('prefix_id') @@ -395,7 +401,7 @@ async def get_all_models(request: Request, user: UserModel | None = None): # Annotate with expiry info from loaded-model state try: - loaded = await get_ollama_loaded_models(request, user=user) + loaded = await get_ollama_loaded_models(request, user=user, skip_idxs=failed_idxs) expires_map = {m['model']: m['expires_at'] for m in loaded['models'] if 'expires_at' in m} for m in models_dict['models']: if m['model'] in expires_map: @@ -457,14 +463,20 @@ async def get_ollama_tags( async def get_ollama_loaded_models( request: Request, user=Depends(get_admin_user), + skip_idxs: set[int] | None = None, ) -> dict: """List models currently loaded in Ollama memory across all backends.""" if not await Config.get('ollama.enable'): return {'models': []} tasks = [] - for idx, url in enumerate(await Config.get('ollama.base_urls', [])): - api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, url) + base_urls = await Config.get('ollama.base_urls', []) + api_configs = await Config.get('ollama.api_configs', {}) + for idx, url in enumerate(base_urls): + if skip_idxs and idx in skip_idxs: + tasks.append(asyncio.ensure_future(asyncio.sleep(0, None))) + continue + api_config = resolve_api_config(api_configs, idx, url) if not api_config: tasks.append(send_get_request(f'{url}/api/ps', user=user)) elif api_config.get('enable', True): @@ -477,7 +489,7 @@ async def get_ollama_loaded_models( for idx, response in enumerate(responses): if not response: continue - api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, (await Config.get('ollama.base_urls', []))[idx]) + api_config = resolve_api_config(api_configs, idx, base_urls[idx]) prefix_id = api_config.get('prefix_id') if prefix_id: for m in response.get('models', []): diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 46cd0af3a7..4cdabb64f9 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1545,7 +1545,7 @@ ($settings?.promptAutocomplete ?? false)} generateAutoCompletion={async (text) => { if (selectedModelIds.length === 0 || !selectedModelIds.at(0)) { - toast.error($i18n.t('Please select a model first.')); + return null; } const res = await generateAutoCompletion( diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index f0e3a8dbbb..24ffc017dd 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -87,7 +87,7 @@ } }; - const setUserSettings = async (cb: () => Promise) => { + const setUserSettings = async (cb?: () => Promise) => { let userSettings = await getUserSettings(localStorage.token).catch((error) => { console.error(error); return null; @@ -209,14 +209,15 @@ checkLocalDBChats(), setBanners().catch((e) => console.error('Failed to load banners:', e)), setTools().catch((e) => console.error('Failed to load tools:', e)), - setUserSettings(async () => { - await Promise.all([ - setModels().catch((e) => console.error('Failed to load models:', e)), - setToolServers().catch((e) => console.error('Failed to load tool servers:', e)) - ]); - }).catch((e) => console.error('Failed to load user settings:', e)) + setUserSettings().catch((e) => console.error('Failed to load user settings:', e)) ]); + // Load models and tool servers in the background — don't block page render. + // These contact external services (Ollama, OpenAI, tool servers, terminal + // servers) that may be slow or unreachable. + setModels().catch((e) => console.error('Failed to load models:', e)); + setToolServers().catch((e) => console.error('Failed to load tool servers:', e)); + // Helper function to check if the pressed keys match the shortcut definition const isShortcutMatch = (event: KeyboardEvent, shortcut): boolean => { const keys = shortcut?.keys || [];