From 9c22768f28d1bf199da546cb3b80500910466a05 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Sat, 18 Apr 2026 13:11:15 -0500 Subject: [PATCH] fix: resolve default model selection bug across page reloads --- backend/src/apis/app_api/chat/routes.py | 32 ++++++++++++++ .../session/services/model/model.service.ts | 42 +++++++++++++++++-- .../chat-preferences-settings.page.ts | 4 +- 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/backend/src/apis/app_api/chat/routes.py b/backend/src/apis/app_api/chat/routes.py index 839e24f9..eb09d4bd 100644 --- a/backend/src/apis/app_api/chat/routes.py +++ b/backend/src/apis/app_api/chat/routes.py @@ -360,6 +360,36 @@ async def chat_stream(request: ChatRequest, current_user: User = Depends(get_cur logger.warning(f"Failed to resolve file upload IDs: {e}") # Continue without files rather than failing the request + # Resolve model selection: if the request omitted a model_id, fall back to the + # user's persisted default from settings before letting get_agent use the + # hardcoded system default. Provider is resolved from the managed models table + # when possible so the correct client/formatter is used. + model_id = request.model_id + provider = request.provider + + if model_id is None: + try: + from apis.shared.user_settings.repository import UserSettingsRepository + + user_settings_repo = UserSettingsRepository() + user_settings = await user_settings_repo.get_settings(user_id) + saved_default = user_settings.get("defaultModelId") + if saved_default: + model_id = saved_default + if provider is None: + try: + from apis.shared.models.managed_models import get_managed_model + + managed = await get_managed_model(saved_default) + if managed is not None: + provider = getattr(managed, "provider", None) or provider + except Exception as provider_err: + logger.debug(f"Could not resolve provider for {saved_default}: {provider_err}") + logger.info(f"Applied user's persisted default model {saved_default} for user {user_id}") + except Exception as e: + logger.error(f"Error loading user default model for user {user_id}: {e}", exc_info=True) + # Continue without saved default - agent will use system default + try: # Get agent instance (with or without tool filtering) # Use assistant's system prompt if provided @@ -368,6 +398,8 @@ async def chat_stream(request: ChatRequest, current_user: User = Depends(get_cur user_id=user_id, enabled_tools=authorized_tools, # Filtered by RBAC (may be None for all allowed) system_prompt=system_prompt, # Assistant instructions if assistant is attached + model_id=model_id, + provider=provider, ) # Wrap stream to ensure flush on disconnect and prevent further processing diff --git a/frontend/ai.client/src/app/session/services/model/model.service.ts b/frontend/ai.client/src/app/session/services/model/model.service.ts index 17980fb0..65a231b2 100644 --- a/frontend/ai.client/src/app/session/services/model/model.service.ts +++ b/frontend/ai.client/src/app/session/services/model/model.service.ts @@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http'; import { firstValueFrom } from 'rxjs'; import { ConfigService } from '../../../services/config.service'; import { AuthService } from '../../../auth/auth.service'; +import { UserSettingsService } from '../../../services/user-settings.service'; import { ManagedModel } from '../../../admin/manage-models/models/managed-model.model'; interface ManagedModelsListResponse { @@ -17,6 +18,7 @@ export class ModelService { private http = inject(HttpClient); private authService = inject(AuthService); private config = inject(ConfigService); + private userSettingsService = inject(UserSettingsService); private readonly baseUrl = computed(() => `${this.config.appApiUrl()}/models`); // Session storage key for persisting model selection @@ -128,10 +130,16 @@ export class ModelService { this._selectedModel.set(savedModel); this.usingDefaultModel.set(false); } else { - // Find admin-configured default model, or fall back to first available - const defaultModel = enabledModels.find(m => m.isDefault); - this._selectedModel.set(defaultModel || enabledModels[0]); + // Set provisional selection synchronously so the UI has a value immediately. + // Priority: admin-configured default (isDefault: true), else first available. + const adminDefault = enabledModels.find(m => m.isDefault); + this._selectedModel.set(adminDefault || enabledModels[0]); this.usingDefaultModel.set(false); + + // Then asynchronously upgrade to the user's persisted default if one is set. + // We don't await inside the synchronous selection path so callers (and tests) + // see a valid selection without waiting on the settings HTTP call. + this.applyUserDefaultModel(enabledModels); } } else { // No models available, use system default @@ -221,6 +229,34 @@ export class ModelService { } } + /** + * Asynchronously applies the user's persisted default model (from the settings API) + * on top of an already-set provisional selection. Runs as a fire-and-forget task. + * Only upgrades the selection if: + * - The user has a defaultModelId set + * - That model exists in the supplied enabled list + * - The user hasn't already made a different explicit selection while loading + */ + private async applyUserDefaultModel(enabledModels: ManagedModel[]): Promise { + const selectionBeforeFetch = this._selectedModel(); + try { + const settings = await this.userSettingsService.fetchSettings(); + const userDefaultId = settings?.defaultModelId; + if (!userDefaultId) return; + + // Skip if the user changed the selection while we were fetching. + if (this._selectedModel() !== selectionBeforeFetch) return; + + const userDefault = enabledModels.find(m => m.modelId === userDefaultId); + if (userDefault) { + this._selectedModel.set(userDefault); + this.usingDefaultModel.set(false); + } + } catch (e) { + console.warn('Could not load user default model from settings:', e); + } + } + /** * Retrieves the saved model ID from sessionStorage */ diff --git a/frontend/ai.client/src/app/settings/pages/chat-preferences/chat-preferences-settings.page.ts b/frontend/ai.client/src/app/settings/pages/chat-preferences/chat-preferences-settings.page.ts index 11c24f19..990089c5 100644 --- a/frontend/ai.client/src/app/settings/pages/chat-preferences/chat-preferences-settings.page.ts +++ b/frontend/ai.client/src/app/settings/pages/chat-preferences/chat-preferences-settings.page.ts @@ -185,7 +185,9 @@ export class ChatPreferencesSettingsPage { readonly currentDefaultModelId = computed(() => { const settings = this.userSettingsService.settingsResource.value(); - return settings?.defaultModelId ?? ''; + const models = this.modelService.availableModels(); + if (!settings || models.length === 0) return ''; + return settings.defaultModelId ?? ''; }); async onModelChange(event: Event): Promise {