From 9e73fc93c66278f0679b78ac25ef3b8a80754d30 Mon Sep 17 00:00:00 2001 From: cogwheel <172976095+cogwheel0@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:00:23 +0530 Subject: [PATCH] feat(chat): Refactor model selection to use shared restore logic --- lib/core/providers/app_providers.dart | 126 ++++-------------- .../chat/providers/chat_providers.dart | 19 +++ lib/features/chat/views/chat_page.dart | 100 +------------- .../navigation/widgets/chats_drawer.dart | 3 + .../utils/conversation_context_menu.dart | 2 + 5 files changed, 56 insertions(+), 194 deletions(-) diff --git a/lib/core/providers/app_providers.dart b/lib/core/providers/app_providers.dart index 2248b6e..b0efd78 100644 --- a/lib/core/providers/app_providers.dart +++ b/lib/core/providers/app_providers.dart @@ -1561,7 +1561,34 @@ Future defaultModel(Ref ref) async { DebugLogger.log('api-available', scope: 'models/default'); try { - // Warm restore from cached resolved default model + // Respect manual selection if present + if (ref.read(isManualModelSelectionProvider)) { + final current = ref.read(selectedModelProvider); + if (current != null) return current; + } + + // 1) Priority: user's configured default from app settings + // This ensures new chats use the user's preference (fixes #296) + final settingsDefaultId = ref.read(appSettingsProvider).defaultModel; + final storedDefaultId = settingsDefaultId ?? + await SettingsService.getDefaultModel().catchError((_) => null); + + if (storedDefaultId != null && storedDefaultId.isNotEmpty) { + // Try cached models first for speed + final cachedMatch = await selectCachedModel(storage, storedDefaultId); + if (cachedMatch != null && !ref.read(isManualModelSelectionProvider)) { + ref.read(selectedModelProvider.notifier).set(cachedMatch); + unawaited(storage.saveLocalDefaultModel(cachedMatch).catchError((_) {})); + DebugLogger.log( + 'settings-default', + scope: 'models/default', + data: {'name': cachedMatch.name, 'source': 'settings'}, + ); + return cachedMatch; + } + } + + // 2) Fallback: cached resolved default model (for offline/fast startup) try { final cached = await storage.getLocalDefaultModel(); if (cached != null && !ref.read(isManualModelSelectionProvider)) { @@ -1575,102 +1602,7 @@ Future defaultModel(Ref ref) async { } } catch (_) {} - // Respect manual selection if present - if (ref.read(isManualModelSelectionProvider)) { - final current = ref.read(selectedModelProvider); - if (current != null) return current; - } - - // 1) Fast path: read stored default model ID directly and select optimistically - try { - final storedDefaultId = await SettingsService.getDefaultModel(); - if (storedDefaultId != null && storedDefaultId.isNotEmpty) { - if (!ref.read(isManualModelSelectionProvider)) { - final cachedMatch = await selectCachedModel(storage, storedDefaultId); - if (cachedMatch != null) { - ref.read(selectedModelProvider.notifier).set(cachedMatch); - unawaited( - storage.saveLocalDefaultModel(cachedMatch).onError(( - error, - stack, - ) { - DebugLogger.error( - 'Failed to save default model to cache', - scope: 'models/default', - error: error, - stackTrace: stack, - ); - }), - ); - DebugLogger.log( - 'cache-select', - scope: 'models/default', - data: {'name': cachedMatch.name, 'source': 'cache'}, - ); - } else { - DebugLogger.log( - 'cache-skip-missing', - scope: 'models/default', - data: {'id': storedDefaultId}, - ); - } - } - // Reconcile against real models in background - Future.microtask(() async { - try { - if (!ref.mounted) return; - List models; - final modelsAsync = ref.read(modelsProvider); - if (modelsAsync.hasValue) { - models = modelsAsync.value ?? const []; - } else { - models = await ref.read(modelsProvider.future); - } - if (!ref.mounted) return; - - Model? resolved; - try { - resolved = models.firstWhere((m) => m.id == storedDefaultId); - } catch (_) { - final byName = models - .where((m) => m.name == storedDefaultId) - .toList(); - if (byName.length == 1) resolved = byName.first; - } - resolved ??= models.isNotEmpty ? models.first : null; - - if (!ref.mounted) return; - if (resolved != null && !ref.read(isManualModelSelectionProvider)) { - ref.read(selectedModelProvider.notifier).set(resolved); - unawaited( - storage.saveLocalDefaultModel(resolved).onError((error, stack) { - DebugLogger.error( - 'Failed to save default model to cache', - scope: 'models/default', - error: error, - stackTrace: stack, - ); - }), - ); - DebugLogger.log( - 'reconcile', - scope: 'models/default', - data: {'name': resolved.name, 'source': 'stored'}, - ); - } - } catch (e) { - DebugLogger.error( - 'reconcile-failed', - scope: 'models/default', - error: e, - ); - } - }); - return ref.read(selectedModelProvider); - } - } catch (_) {} - - // 2) Fast server path: query server default ID without listing all models + // 3) Fast server path: query server default ID without listing all models try { final serverDefault = await api.getDefaultModel(); if (serverDefault != null && serverDefault.isNotEmpty) { diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index 1992e80..064f781 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -990,8 +990,27 @@ void startNewChat(dynamic ref) { ref.read(pendingFolderIdProvider.notifier).clear(); // Reset to default model for new conversations (fixes #296) + restoreDefaultModel(ref); +} + +/// Restores the selected model to the user's configured default model. +/// Call this when starting a new conversation. +Future restoreDefaultModel(dynamic ref) async { + // Mark that this is not a manual selection ref.read(isManualModelSelectionProvider.notifier).set(false); + + // Invalidate and re-read to force defaultModelProvider to use settings priority ref.invalidate(defaultModelProvider); + + try { + await ref.read(defaultModelProvider.future); + } catch (e) { + DebugLogger.error( + 'restore-default-failed', + scope: 'chat/model', + error: e, + ); + } } // Available tools provider diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index bde963a..7ba7699 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -83,42 +83,6 @@ class _ChatPageState extends ConsumerState { return fileSize <= (maxSizeMB * 1024 * 1024); } - Future _trySelectCachedModel() async { - final existing = ref.read(selectedModelProvider); - if (existing != null) { - return existing; - } - - try { - final storage = ref.read(optimizedStorageServiceProvider); - // Prefer the stored default model ID/name if available - final settingsDesired = ref.read(appSettingsProvider).defaultModel; - final storedDesired = await SettingsService.getDefaultModel().catchError( - (_) => null, - ); - final desiredId = settingsDesired ?? storedDesired; - - final match = await selectCachedModel(storage, desiredId); - if (match != null) { - ref.read(selectedModelProvider.notifier).set(match); - DebugLogger.log( - 'cache-select', - scope: 'chat/model', - data: {'name': match.name, 'source': 'cache'}, - ); - } - return match; - } catch (error, stackTrace) { - DebugLogger.error( - 'cache-select-failed', - scope: 'chat/model', - error: error, - stackTrace: stackTrace, - ); - return null; - } - } - void startNewChat() { // Clear current conversation ref.read(chatMessagesProvider.notifier).clearMessages(); @@ -131,8 +95,7 @@ class _ChatPageState extends ConsumerState { ref.read(pendingFolderIdProvider.notifier).clear(); // Reset to default model for new conversations (fixes #296) - ref.read(isManualModelSelectionProvider.notifier).set(false); - ref.invalidate(defaultModelProvider); + restoreDefaultModel(ref); // Scroll to top if (_scrollController.hasClients) { @@ -157,65 +120,8 @@ class _ChatPageState extends ConsumerState { return; } - // Fast path: try cached models + stored default before waiting on providers - final cached = await _trySelectCachedModel(); - if (cached != null) { - // Still continue to reconcile against remote models below - DebugLogger.log( - 'cache-hit', - scope: 'chat/model', - data: {'name': cached.name}, - ); - } - - DebugLogger.log('auto-select-start', scope: 'chat/model'); - - try { - // First ensure models are loaded - final modelsAsync = ref.read(modelsProvider); - List models; - - if (modelsAsync.hasValue) { - models = modelsAsync.value!; - } else { - DebugLogger.log('models-fetch', scope: 'chat/model'); - models = await ref.read(modelsProvider.future); - } - - DebugLogger.log( - 'models-count', - scope: 'chat/model', - data: {'count': models.length}, - ); - - if (models.isEmpty) { - DebugLogger.warning('models-empty', scope: 'chat/model'); - return; - } - - // Try to use the default model provider - try { - final Model? model = await ref.read(defaultModelProvider.future); - if (model != null) { - DebugLogger.log( - 'auto-select', - scope: 'chat/model', - data: {'name': model.name}, - ); - } - } catch (e) { - DebugLogger.warning('provider-fallback', scope: 'chat/model'); - // Fallback: select the first available model - ref.read(selectedModelProvider.notifier).set(models.first); - DebugLogger.log( - 'fallback', - scope: 'chat/model', - data: {'name': models.first.name}, - ); - } - } catch (e) { - DebugLogger.error('auto-select-failed', scope: 'chat/model', error: e); - } + // Use shared restore logic which handles settings priority and fallbacks + await restoreDefaultModel(ref); } Future _checkAndShowOnboarding() async { diff --git a/lib/features/navigation/widgets/chats_drawer.dart b/lib/features/navigation/widgets/chats_drawer.dart index fe83dd4..4ff6e1b 100644 --- a/lib/features/navigation/widgets/chats_drawer.dart +++ b/lib/features/navigation/widgets/chats_drawer.dart @@ -1342,6 +1342,9 @@ class _ChatsDrawerState extends ConsumerState { // Clear context attachments (web pages, YouTube, knowledge base docs) ref.read(contextAttachmentsProvider.notifier).clear(); + // Reset to default model for new conversations (fixes #296) + chat.restoreDefaultModel(ref); + // Close drawer using the responsive layout (same pattern as _selectConversation) if (mounted) { final mediaQuery = MediaQuery.maybeOf(context); diff --git a/lib/shared/utils/conversation_context_menu.dart b/lib/shared/utils/conversation_context_menu.dart index f597e8b..8ec5b01 100644 --- a/lib/shared/utils/conversation_context_menu.dart +++ b/lib/shared/utils/conversation_context_menu.dart @@ -523,6 +523,8 @@ Future _confirmAndDeleteConversation( if (active?.id == conversationId) { ref.read(activeConversationProvider.notifier).clear(); ref.read(chat.chatMessagesProvider.notifier).clearMessages(); + // Reset to default model for new conversations (fixes #296) + chat.restoreDefaultModel(ref); } refreshConversationsCache(ref); } catch (_) {