perf(ui): non-blocking model and tool server loading for unreachable connections (#26289)
- Move setModels() and setToolServers() out of the loaded gate in +layout.svelte so the page renders immediately instead of blocking behind slow/unreachable connection timeouts - Track failed Ollama backend URLs from /api/tags and skip them in the subsequent /api/ps fan-out, eliminating a redundant second timeout - Silence the autocomplete toast that fires during the transient empty model state
This commit is contained in:
3 files changed
+29
-16
No files matched your search
@@ -357,8 +357,10 @@ async def get_all_models(request: Request, user: UserModel | None = None):
|
|||||||
|
|
||||||
# Fan-out tag requests to every backend
|
# Fan-out tag requests to every backend
|
||||||
tasks = []
|
tasks = []
|
||||||
for idx, url in enumerate(await Config.get('ollama.base_urls', [])):
|
base_urls = await Config.get('ollama.base_urls', [])
|
||||||
api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, url)
|
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:
|
if not api_config:
|
||||||
tasks.append(send_get_request(f'{url}/api/tags', user=user))
|
tasks.append(send_get_request(f'{url}/api/tags', user=user))
|
||||||
elif api_config.get('enable', True):
|
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)
|
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
|
# Post-process each response: apply prefix_id, tags, model filtering
|
||||||
for idx, response in enumerate(responses):
|
for idx, response in enumerate(responses):
|
||||||
if not response:
|
if not response:
|
||||||
|
failed_idxs.add(idx)
|
||||||
continue
|
continue
|
||||||
url = (await Config.get('ollama.base_urls', []))[idx]
|
url = base_urls[idx]
|
||||||
api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, url)
|
api_config = resolve_api_config(api_configs, idx, url)
|
||||||
|
|
||||||
connection_type = api_config.get('connection_type', 'local')
|
connection_type = api_config.get('connection_type', 'local')
|
||||||
prefix_id = api_config.get('prefix_id')
|
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
|
# Annotate with expiry info from loaded-model state
|
||||||
try:
|
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}
|
expires_map = {m['model']: m['expires_at'] for m in loaded['models'] if 'expires_at' in m}
|
||||||
for m in models_dict['models']:
|
for m in models_dict['models']:
|
||||||
if m['model'] in expires_map:
|
if m['model'] in expires_map:
|
||||||
@@ -457,14 +463,20 @@ async def get_ollama_tags(
|
|||||||
async def get_ollama_loaded_models(
|
async def get_ollama_loaded_models(
|
||||||
request: Request,
|
request: Request,
|
||||||
user=Depends(get_admin_user),
|
user=Depends(get_admin_user),
|
||||||
|
skip_idxs: set[int] | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""List models currently loaded in Ollama memory across all backends."""
|
"""List models currently loaded in Ollama memory across all backends."""
|
||||||
if not await Config.get('ollama.enable'):
|
if not await Config.get('ollama.enable'):
|
||||||
return {'models': []}
|
return {'models': []}
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
for idx, url in enumerate(await Config.get('ollama.base_urls', [])):
|
base_urls = await Config.get('ollama.base_urls', [])
|
||||||
api_config = resolve_api_config((await Config.get('ollama.api_configs', {})), idx, url)
|
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:
|
if not api_config:
|
||||||
tasks.append(send_get_request(f'{url}/api/ps', user=user))
|
tasks.append(send_get_request(f'{url}/api/ps', user=user))
|
||||||
elif api_config.get('enable', True):
|
elif api_config.get('enable', True):
|
||||||
@@ -477,7 +489,7 @@ async def get_ollama_loaded_models(
|
|||||||
for idx, response in enumerate(responses):
|
for idx, response in enumerate(responses):
|
||||||
if not response:
|
if not response:
|
||||||
continue
|
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')
|
prefix_id = api_config.get('prefix_id')
|
||||||
if prefix_id:
|
if prefix_id:
|
||||||
for m in response.get('models', []):
|
for m in response.get('models', []):
|
||||||
|
|||||||
@@ -1545,7 +1545,7 @@
|
|||||||
($settings?.promptAutocomplete ?? false)}
|
($settings?.promptAutocomplete ?? false)}
|
||||||
generateAutoCompletion={async (text) => {
|
generateAutoCompletion={async (text) => {
|
||||||
if (selectedModelIds.length === 0 || !selectedModelIds.at(0)) {
|
if (selectedModelIds.length === 0 || !selectedModelIds.at(0)) {
|
||||||
toast.error($i18n.t('Please select a model first.'));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await generateAutoCompletion(
|
const res = await generateAutoCompletion(
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setUserSettings = async (cb: () => Promise<void>) => {
|
const setUserSettings = async (cb?: () => Promise<void>) => {
|
||||||
let userSettings = await getUserSettings(localStorage.token).catch((error) => {
|
let userSettings = await getUserSettings(localStorage.token).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return null;
|
return null;
|
||||||
@@ -209,14 +209,15 @@
|
|||||||
checkLocalDBChats(),
|
checkLocalDBChats(),
|
||||||
setBanners().catch((e) => console.error('Failed to load banners:', e)),
|
setBanners().catch((e) => console.error('Failed to load banners:', e)),
|
||||||
setTools().catch((e) => console.error('Failed to load tools:', e)),
|
setTools().catch((e) => console.error('Failed to load tools:', e)),
|
||||||
setUserSettings(async () => {
|
setUserSettings().catch((e) => console.error('Failed to load user settings:', e))
|
||||||
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))
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 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
|
// Helper function to check if the pressed keys match the shortcut definition
|
||||||
const isShortcutMatch = (event: KeyboardEvent, shortcut): boolean => {
|
const isShortcutMatch = (event: KeyboardEvent, shortcut): boolean => {
|
||||||
const keys = shortcut?.keys || [];
|
const keys = shortcut?.keys || [];
|
||||||
|
|||||||
Reference in new issue
Block a user