diff --git a/lib/core/providers/app_providers.dart b/lib/core/providers/app_providers.dart index bf0be92..62910ca 100644 --- a/lib/core/providers/app_providers.dart +++ b/lib/core/providers/app_providers.dart @@ -333,7 +333,7 @@ final defaultModelAutoSelectionProvider = Provider((ref) { final desired = next.defaultModel; if (desired == null || desired.isEmpty) return; - // Resolve the desired model against available models + // Resolve the desired model against available models (by ID only) Future(() async { try { // Prefer already-loaded models to avoid unnecessary fetches @@ -346,14 +346,10 @@ final defaultModelAutoSelectionProvider = Provider((ref) { } Model? selected; try { - selected = models.firstWhere( - (model) => - model.id == desired || - model.name == desired || - model.id.contains(desired) || - model.name.contains(desired), - ); - } catch (_) {} + selected = models.firstWhere((model) => model.id == desired); + } catch (_) { + selected = null; + } // Fallback: keep current selection or pick first available selected ??= ref.read(selectedModelProvider) ?? @@ -362,7 +358,7 @@ final defaultModelAutoSelectionProvider = Provider((ref) { if (selected != null) { ref.read(selectedModelProvider.notifier).state = selected; foundation.debugPrint( - 'DEBUG: Auto-applied default model from settings: ${selected.name}', + 'DEBUG: Auto-applied default model (by ID): ${selected.name}', ); } } catch (e) { @@ -669,27 +665,41 @@ final defaultModelProvider = FutureProvider((ref) async { Model? selectedModel; - // First check user's preferred default model + // First check user's preferred default model (ID only). If an older + // name-based value is found, migrate it once to the correct ID. final userSettings = ref.read(appSettingsProvider); final userDefaultModelId = userSettings.defaultModel; if (userDefaultModelId != null && userDefaultModelId.isNotEmpty) { try { - selectedModel = models.firstWhere( - (model) => - model.id == userDefaultModelId || - model.name == userDefaultModelId || - model.id.contains(userDefaultModelId) || - model.name.contains(userDefaultModelId), - ); + // Exact ID match only + selectedModel = + models.firstWhere((model) => model.id == userDefaultModelId); foundation.debugPrint( - 'DEBUG: Found user default model: ${selectedModel.name}', + 'DEBUG: Found user default model by ID: ${selectedModel.name}', ); } catch (e) { - foundation.debugPrint( - 'DEBUG: User default model "$userDefaultModelId" not found in available models', - ); - selectedModel = null; // Will fall back to server default or first model + // Attempt a one-time migration if the stored value was a model name + // from older versions. Only migrate on exact, unique name match. + final nameMatches = + models.where((m) => m.name == userDefaultModelId).toList(); + if (nameMatches.length == 1) { + selectedModel = nameMatches.first; + foundation.debugPrint( + 'DEBUG: Migrating user default model name to ID: ' + '${nameMatches.first.name} -> ${nameMatches.first.id}', + ); + // Persist the migrated ID + await ref + .read(appSettingsProvider.notifier) + .setDefaultModel(nameMatches.first.id); + } else { + foundation.debugPrint( + 'DEBUG: User default model "$userDefaultModelId" not found by ID and ' + 'no unique name match. Ignoring.', + ); + selectedModel = null; // Will fall back to server default or first model + } } } @@ -699,23 +709,29 @@ final defaultModelProvider = FutureProvider((ref) async { final defaultModelId = await api.getDefaultModel(); if (defaultModelId != null && defaultModelId.isNotEmpty) { - // Find the model that matches the default model ID + // Find the model that matches the default model ID (ID only) try { - selectedModel = models.firstWhere( - (model) => - model.id == defaultModelId || - model.name == defaultModelId || - model.id.contains(defaultModelId) || - model.name.contains(defaultModelId), - ); + selectedModel = + models.firstWhere((model) => model.id == defaultModelId); foundation.debugPrint( - 'DEBUG: Found server default model: ${selectedModel.name}', + 'DEBUG: Found server default model by ID: ${selectedModel.name}', ); } catch (e) { - foundation.debugPrint( - 'DEBUG: Server default model "$defaultModelId" not found in available models', - ); - selectedModel = models.first; + // If server returned a name instead of ID, attempt exact name match. + final byName = models.where((m) => m.name == defaultModelId).toList(); + if (byName.length == 1) { + selectedModel = byName.first; + foundation.debugPrint( + 'DEBUG: Server default "$defaultModelId" matched by name; ' + 'selected ${selectedModel.name} (${selectedModel.id})', + ); + } else { + foundation.debugPrint( + 'DEBUG: Server default model "$defaultModelId" not found by ID; ' + 'falling back to first available', + ); + selectedModel = models.first; + } } } else { // No server default, use first available model diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index c2d304d..fb83384 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -406,7 +406,7 @@ Future regenerateMessage( role: 'assistant', content: '', timestamp: DateTime.now(), - model: selectedModel.name, + model: selectedModel.id, isStreaming: true, ); ref.read(chatMessagesProvider.notifier).addMessage(assistantMessage); @@ -477,7 +477,7 @@ Future regenerateMessage( role: 'assistant', content: '', timestamp: DateTime.now(), - model: selectedModel.name, + model: selectedModel.id, isStreaming: true, ); ref.read(chatMessagesProvider.notifier).addMessage(assistantMessage); @@ -547,6 +547,7 @@ Future _sendMessageInternal( role: 'user', content: message, timestamp: DateTime.now(), + model: selectedModel.id, attachmentIds: attachments, ); @@ -649,7 +650,7 @@ Future _sendMessageInternal( role: 'assistant', content: '', timestamp: DateTime.now(), - model: selectedModel.name, + model: selectedModel.id, isStreaming: true, ); ref.read(chatMessagesProvider.notifier).addMessage(assistantMessage); @@ -883,7 +884,7 @@ Future _sendMessageInternal( role: 'assistant', content: '', timestamp: DateTime.now(), - model: selectedModel.name, + model: selectedModel.id, isStreaming: true, ); ref.read(chatMessagesProvider.notifier).addMessage(imageOnlyAssistant); @@ -1063,7 +1064,7 @@ Future _sendMessageInternal( role: 'assistant', content: '', timestamp: DateTime.now(), - model: selectedModel.name, + model: selectedModel.id, isStreaming: true, ); ref.read(chatMessagesProvider.notifier).addMessage(assistantMessage); @@ -2378,7 +2379,7 @@ final regenerateLastMessageProvider = Provider Function()>((ref) { role: 'assistant', content: '', timestamp: DateTime.now(), - model: selectedModel.name, + model: selectedModel.id, isStreaming: true, ); ref.read(chatMessagesProvider.notifier).addMessage(placeholder); diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 8616466..7447e9f 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -688,6 +688,29 @@ class _ChatPageState extends ConsumerState { final isSelected = _selectedMessageIds.contains(message.id); + // Resolve a friendly model display name for message headers + String? displayModelName; + final rawModel = message.model; + if (rawModel != null && rawModel.isNotEmpty) { + final modelsAsync = ref.watch(modelsProvider); + if (modelsAsync.hasValue) { + final models = modelsAsync.value!; + try { + // Prefer exact ID match; fall back to exact name match + final match = models.firstWhere( + (m) => m.id == rawModel || m.name == rawModel, + ); + displayModelName = match.name; + } catch (_) { + // As a fallback, format the raw value to be more readable + displayModelName = _formatModelDisplayName(rawModel); + } + } else { + // Models not loaded yet; format raw value for readability + displayModelName = _formatModelDisplayName(rawModel); + } + } + // Wrap message in selection container if in selection mode Widget messageWidget; @@ -698,7 +721,7 @@ class _ChatPageState extends ConsumerState { message: message, isUser: isUser, isStreaming: isStreaming, - modelName: message.model, + modelName: displayModelName, onCopy: () => _copyMessage(message.content), onEdit: () => _editMessage(message), onRegenerate: () => _regenerateMessage(message), @@ -710,7 +733,7 @@ class _ChatPageState extends ConsumerState { key: ValueKey('assistant-${message.id}'), message: message, isStreaming: isStreaming, - modelName: message.model, + modelName: displayModelName, onCopy: () => _copyMessage(message.content), onRegenerate: () => _regenerateMessage(message), onLike: () => _likeMessage(message),