feat(model): Update auto-select model description and behavior
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<void>((ref) {
|
||||
ref.listen<AppSettings>(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<void>((ref) {
|
||||
// Prevent disposal so listeners remain active throughout app lifecycle
|
||||
@@ -1039,11 +1028,29 @@ final defaultModelAutoSelectionProvider = Provider<void>((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<Conversation> loadConversation(Ref ref, String conversationId) async {
|
||||
Future<Model?> 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<Model?> 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<Model?> 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',
|
||||
|
||||
@@ -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<void> 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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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도 사용 가능",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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<ConduitContextMenuAction> 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<ConduitContextMenuAction> 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<void> _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()),
|
||||
|
||||
Reference in New Issue
Block a user