diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 813f652..18414c2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -55,8 +55,6 @@ PODS: - SDWebImageWebPCoder - flutter_local_notifications (0.0.1): - Flutter - - flutter_native_splash (2.4.3): - - Flutter - flutter_secure_storage_darwin (10.0.0): - Flutter - FlutterMacOS @@ -148,7 +146,6 @@ DEPENDENCIES: - flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`) - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`) @@ -203,8 +200,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_image_compress_common/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" - flutter_native_splash: - :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage_darwin: :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" flutter_tts: @@ -262,7 +257,6 @@ SPEC CHECKSUMS: flutter_callkit_incoming: cb8138af67cda6dd981f7101a5d709003af21502 flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833 home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f diff --git a/lib/core/providers/app_providers.dart b/lib/core/providers/app_providers.dart index b0efd78..0b9b199 100644 --- a/lib/core/providers/app_providers.dart +++ b/lib/core/providers/app_providers.dart @@ -877,17 +877,6 @@ class IsManualModelSelection extends _$IsManualModelSelection { void set(bool value) => state = value; } -// Listen for settings changes and reset manual selection when default model changes -// keepAlive to maintain listener throughout app lifecycle -final _settingsWatcherProvider = Provider((ref) { - ref.listen(appSettingsProvider, (previous, next) { - if (previous?.defaultModel != next.defaultModel) { - // Reset manual selection when default model changes - ref.read(isManualModelSelectionProvider.notifier).set(false); - } - }); -}); - // Auto-apply model-specific tools when model changes or tools load final modelToolsAutoSelectionProvider = Provider((ref) { // Prevent disposal so listeners remain active throughout app lifecycle @@ -1039,11 +1028,29 @@ final defaultModelAutoSelectionProvider = Provider((ref) { // Only react when default model value changes if (previous?.defaultModel == next.defaultModel) return; - // Do not override manual selections - if (ref.read(isManualModelSelectionProvider)) return; + // Reset manual selection flag when default model setting changes + ref.read(isManualModelSelectionProvider.notifier).set(false); final desired = next.defaultModel; - if (desired == null || desired.isEmpty) return; + + // If auto-select (null), invalidate defaultModelProvider to re-fetch server default + if (desired == null || desired.isEmpty) { + DebugLogger.log('auto-select-enabled', scope: 'models/default'); + ref.invalidate(defaultModelProvider); + // Trigger re-read to apply server default + Future(() async { + try { + await ref.read(defaultModelProvider.future); + } catch (e) { + DebugLogger.error( + 'auto-select-failed', + scope: 'models/default', + error: e, + ); + } + }); + return; + } // Resolve the desired model against available models (by ID only) Future(() async { @@ -1514,8 +1521,6 @@ Future loadConversation(Ref ref, String conversationId) async { Future defaultModel(Ref ref) async { DebugLogger.log('provider-called', scope: 'models/default'); - // Initialize the settings watcher (side-effect only) - ref.read(_settingsWatcherProvider); final storage = ref.read(optimizedStorageServiceProvider); // Read settings without subscribing to rebuilds to avoid watch/await hazards final reviewerMode = ref.read(reviewerModeProvider); @@ -1570,7 +1575,8 @@ Future defaultModel(Ref ref) async { // 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 ?? + final storedDefaultId = + settingsDefaultId ?? await SettingsService.getDefaultModel().catchError((_) => null); if (storedDefaultId != null && storedDefaultId.isNotEmpty) { @@ -1578,7 +1584,9 @@ Future defaultModel(Ref ref) async { final cachedMatch = await selectCachedModel(storage, storedDefaultId); if (cachedMatch != null && !ref.read(isManualModelSelectionProvider)) { ref.read(selectedModelProvider.notifier).set(cachedMatch); - unawaited(storage.saveLocalDefaultModel(cachedMatch).catchError((_) {})); + unawaited( + storage.saveLocalDefaultModel(cachedMatch).catchError((_) {}), + ); DebugLogger.log( 'settings-default', scope: 'models/default', diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index a4612f3..fdbd094 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -12,6 +12,7 @@ import '../../../core/models/chat_message.dart'; import '../../../core/models/conversation.dart'; import '../../../core/providers/app_providers.dart'; import '../../../core/services/conversation_delta_listener.dart'; +import '../../../core/services/settings_service.dart'; import '../../../core/services/streaming_helper.dart'; import '../../../core/services/streaming_response_controller.dart'; import '../../../core/services/worker_manager.dart'; @@ -904,11 +905,20 @@ void startNewChat(dynamic ref) { } /// Restores the selected model to the user's configured default model. -/// Call this when starting a new conversation. +/// Call this when starting a new conversation or when settings change. Future restoreDefaultModel(dynamic ref) async { // Mark that this is not a manual selection ref.read(isManualModelSelectionProvider.notifier).set(false); + // If auto-select (no explicit default), clear the cached default model + // so defaultModelProvider will fetch from server + final settingsDefault = ref.read(appSettingsProvider).defaultModel; + if (settingsDefault == null || settingsDefault.isEmpty) { + final storage = ref.read(optimizedStorageServiceProvider); + await storage.saveLocalDefaultModel(null); + DebugLogger.log('cleared-cached-default', scope: 'chat/model'); + } + // Invalidate and re-read to force defaultModelProvider to use settings priority ref.invalidate(defaultModelProvider); diff --git a/lib/features/profile/views/profile_page.dart b/lib/features/profile/views/profile_page.dart index f1213a3..cf3dea5 100644 --- a/lib/features/profile/views/profile_page.dart +++ b/lib/features/profile/views/profile_page.dart @@ -16,6 +16,7 @@ import '../../../shared/widgets/sheet_handle.dart'; import '../../../shared/widgets/conduit_components.dart'; import '../../../core/providers/app_providers.dart'; import '../../../core/services/navigation_service.dart'; +import '../../chat/providers/chat_providers.dart' show restoreDefaultModel; import '../../auth/providers/unified_auth_providers.dart'; import '../../../core/services/settings_service.dart'; import '../../../core/models/model.dart'; @@ -648,6 +649,9 @@ class ProfilePage extends ConsumerWidget { await ref .read(appSettingsProvider.notifier) .setDefaultModel(modelIdToSave); + + // Immediately apply the new default model selection + await restoreDefaultModel(ref); } } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6f15ec2..87ff683 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -24,7 +24,7 @@ "signOut": "Abmelden", "endYourSession": "Sitzung beenden", "defaultModel": "Standardmodell", - "autoSelect": "Automatische Auswahl", + "autoSelect": "Serverstandard", "loadingModels": "Modelle werden geladen...", "failedToLoadModels": "Modelle konnten nicht geladen werden", "availableModels": "Verfügbare Modelle", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "Lass die App das beste Modell auswählen", + "autoSelectDescription": "Das auf dem Server konfigurierte Standardmodell verwenden", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "Engine", "@ttsEngineLabel": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8a731e5..4f64584 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -72,7 +72,7 @@ "@defaultModel": { "description": "Label for choosing a default AI model." }, - "autoSelect": "Auto-select", + "autoSelect": "Server provided", "@autoSelect": { "description": "Option to let the app pick a suitable model automatically." }, @@ -1297,9 +1297,9 @@ } } }, - "autoSelectDescription": "Let the app choose the best model", + "autoSelectDescription": "Use the default model configured on the server", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "chatSettings": "Chat", "@chatSettings": { diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9568b51..9745b09 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -24,7 +24,7 @@ "signOut": "Cerrar sesión", "endYourSession": "Finalizar tu sesión", "defaultModel": "Modelo predeterminado", - "autoSelect": "Selección automática", + "autoSelect": "Predeterminado del servidor", "loadingModels": "Cargando modelos...", "failedToLoadModels": "No se pudieron cargar los modelos", "availableModels": "Modelos disponibles", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "Deja que la aplicación elija el mejor modelo", + "autoSelectDescription": "Usar el modelo predeterminado configurado en el servidor", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "Motor", "@ttsEngineLabel": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index efe353c..ffe87eb 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -24,7 +24,7 @@ "signOut": "Se déconnecter", "endYourSession": "Terminer votre session", "defaultModel": "Modèle par défaut", - "autoSelect": "Sélection automatique", + "autoSelect": "Fourni par le serveur", "loadingModels": "Chargement des modèles...", "failedToLoadModels": "Échec du chargement des modèles", "availableModels": "Modèles disponibles", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "Laissez l'application choisir le meilleur modèle", + "autoSelectDescription": "Utiliser le modèle par défaut configuré sur le serveur", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "Moteur", "@ttsEngineLabel": { diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 8a61065..60ddcaa 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -24,7 +24,7 @@ "signOut": "Esci", "endYourSession": "Termina la sessione", "defaultModel": "Modello predefinito", - "autoSelect": "Selezione automatica", + "autoSelect": "Predefinito del server", "loadingModels": "Caricamento modelli...", "failedToLoadModels": "Impossibile caricare i modelli", "availableModels": "Modelli disponibili", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "Lascia che l'app scelga il modello migliore", + "autoSelectDescription": "Usa il modello predefinito configurato sul server", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "Motore", "@ttsEngineLabel": { diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 533cfac..bc07612 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -18,7 +18,7 @@ "signOut": "로그아웃", "endYourSession": "세션 종료", "defaultModel": "기본 모델", - "autoSelect": "자동 선택", + "autoSelect": "서버 기본값", "loadingModels": "모델 로딩 중...", "failedToLoadModels": "모델을 불러오지 못했습니다", "availableModels": "사용 가능한 모델", @@ -436,7 +436,7 @@ } } }, - "autoSelectDescription": "앱이 최적의 모델을 선택하도록 허용", + "autoSelectDescription": "서버에 구성된 기본 모델 사용", "chatSettings": "채팅", "sendOnEnter": "Enter로 전송", "sendOnEnterDescription": "Enter로 전송 (소프트 키보드). Cmd/Ctrl+Enter도 사용 가능", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 36f3316..2318405 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -24,7 +24,7 @@ "signOut": "Uitloggen", "endYourSession": "Beëindig je sessie", "defaultModel": "Standaardmodel", - "autoSelect": "Automatisch selecteren", + "autoSelect": "Serverstandaard", "loadingModels": "Modellen laden...", "failedToLoadModels": "Kan modellen niet laden", "availableModels": "Beschikbare modellen", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "Laat de app het beste model kiezen", + "autoSelectDescription": "Gebruik het standaardmodel dat op de server is geconfigureerd", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "Engine", "@ttsEngineLabel": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 93bd205..6541ce9 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -24,7 +24,7 @@ "signOut": "Выйти", "endYourSession": "Завершить сеанс", "defaultModel": "Модель по умолчанию", - "autoSelect": "Автовыбор", + "autoSelect": "По умолчанию сервера", "loadingModels": "Загрузка моделей...", "failedToLoadModels": "Не удалось загрузить модели", "availableModels": "Доступные модели", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "Позвольте приложению выбрать лучшую модель", + "autoSelectDescription": "Использовать модель по умолчанию, настроенную на сервере", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "Движок", "@ttsEngineLabel": { diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 1da326e..e45bcb6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -24,7 +24,7 @@ "signOut": "退出登录", "endYourSession": "结束您的会话", "defaultModel": "默认模型", - "autoSelect": "自动选择", + "autoSelect": "服务器默认", "loadingModels": "加载模型中...", "failedToLoadModels": "无法加载模型", "availableModels": "可用模型", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "让应用自动选择最佳模型", + "autoSelectDescription": "使用服务器上配置的默认模型", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "引擎", "@ttsEngineLabel": { diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index e461298..1544b49 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -24,7 +24,7 @@ "signOut": "退出登錄", "endYourSession": "結束您的會話", "defaultModel": "默認模型", - "autoSelect": "自動選擇", + "autoSelect": "伺服器預設", "loadingModels": "加載模型中...", "failedToLoadModels": "無法加載模型", "availableModels": "可用模型", @@ -364,9 +364,9 @@ } } }, - "autoSelectDescription": "讓應用自動選擇最佳模型", + "autoSelectDescription": "使用伺服器上配置的預設模型", "@autoSelectDescription": { - "description": "Explains what the auto-select model setting does." + "description": "Explains what the server-provided model setting does." }, "ttsEngineLabel": "引擎", "@ttsEngineLabel": { diff --git a/lib/shared/utils/conversation_context_menu.dart b/lib/shared/utils/conversation_context_menu.dart index 8ec5b01..480ebcc 100644 --- a/lib/shared/utils/conversation_context_menu.dart +++ b/lib/shared/utils/conversation_context_menu.dart @@ -159,9 +159,7 @@ class _ConduitMobileMenuBuilder extends mobile.MobileMenuWidgetBuilder { final veilColor = theme.isDark ? const Color(0x4D000000) // ~30% black : const Color(0x4D424242); // ~30% grey - return SizedBox.expand( - child: ColoredBox(color: veilColor), - ); + return SizedBox.expand(child: ColoredBox(color: veilColor)); } @override @@ -189,10 +187,7 @@ class _ConduitMobileMenuBuilder extends mobile.MobileMenuWidgetBuilder { Padding( padding: const EdgeInsets.only(right: Spacing.md), child: IconTheme( - data: IconThemeData( - color: iconColor, - size: IconSize.medium, - ), + data: IconThemeData(color: iconColor, size: IconSize.medium), child: imageWidget, ), ), @@ -218,10 +213,7 @@ class _ConduitMobileMenuBuilder extends mobile.MobileMenuWidgetBuilder { ); if (state.pressed) { - content = ColoredBox( - color: theme.surfaceContainer, - child: content, - ); + content = ColoredBox(color: theme.surfaceContainer, child: content); } return content; @@ -295,9 +287,7 @@ class _ConduitMobileMenuBuilder extends mobile.MobileMenuWidgetBuilder { // even when the overlay is visually transparent return GestureDetector( behavior: HitTestBehavior.opaque, - child: SizedBox.expand( - child: ColoredBox(color: overlayColor), - ), + child: SizedBox.expand(child: ColoredBox(color: overlayColor)), ); } @@ -398,10 +388,10 @@ List buildConversationActions({ return [ ConduitContextMenuAction( - cupertinoIcon: - isPinned ? CupertinoIcons.pin_slash : CupertinoIcons.pin_fill, - materialIcon: - isPinned ? Icons.push_pin_outlined : Icons.push_pin_rounded, + cupertinoIcon: isPinned + ? CupertinoIcons.pin_slash + : CupertinoIcons.pin_fill, + materialIcon: isPinned ? Icons.push_pin_outlined : Icons.push_pin_rounded, label: isPinned ? l10n.unpin : l10n.pin, onBeforeClose: () => HapticFeedback.lightImpact(), onSelected: togglePin, @@ -410,8 +400,9 @@ List buildConversationActions({ cupertinoIcon: isArchived ? CupertinoIcons.archivebox_fill : CupertinoIcons.archivebox, - materialIcon: - isArchived ? Icons.unarchive_rounded : Icons.archive_rounded, + materialIcon: isArchived + ? Icons.unarchive_rounded + : Icons.archive_rounded, label: isArchived ? l10n.unarchive : l10n.archive, onBeforeClose: () => HapticFeedback.lightImpact(), onSelected: toggleArchive, @@ -477,7 +468,9 @@ Future _renameConversation( if (api == null) throw Exception('No API service'); await api.updateConversation(conversationId, title: newName); HapticFeedback.selectionClick(); - ref.read(conversationsProvider.notifier).updateConversation( + ref + .read(conversationsProvider.notifier) + .updateConversation( conversationId, (conversation) => conversation.copyWith(title: newName, updatedAt: DateTime.now()),