Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion lib/core/services/settings_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class SettingsService {
PreferenceKeys.voiceSilenceDuration;
static const String _androidAssistantTriggerKey =
PreferenceKeys.androidAssistantTrigger;
static const String _androidAssistantModelKey = 'android_assistant_model';
static Box<dynamic> _preferencesBox() =>
Hive.box<dynamic>(HiveBoxNames.preferences);

Expand Down Expand Up @@ -145,6 +146,23 @@ class SettingsService {
return box.delete(_defaultModelKey);
}

/// Get Android Assistant Model preference
static Future<String?> getAndroidAssistantModelId() {
final value = _preferencesBox().get(_androidAssistantModelKey) as String?;
return Future.value(value);
}



/// Set Android Assistant Model preference
static Future<void> setAndroidAssistantModelId(String? modelId) {
final box = _preferencesBox();
if (modelId != null) {
return box.put(_androidAssistantModelKey, modelId);
}
return box.delete(_androidAssistantModelKey);
}

/// Load all settings
static Future<AppSettings> loadSettings() {
final box = _preferencesBox();
Expand Down Expand Up @@ -184,6 +202,12 @@ class SettingsService {
await box.delete(_defaultModelKey);
}

if (settings.androidAssistantModelId != null) {
await box.put(_androidAssistantModelKey, settings.androidAssistantModelId);
} else {
await box.delete(_androidAssistantModelKey);
}

if (settings.voiceLocaleId != null && settings.voiceLocaleId!.isNotEmpty) {
await box.put(_voiceLocaleKey, settings.voiceLocaleId);
} else {
Expand Down Expand Up @@ -436,6 +460,7 @@ class SettingsService {
androidAssistantTrigger: _parseAndroidAssistantTrigger(
box.get(_androidAssistantTriggerKey) as String?,
),
androidAssistantModelId: box.get(_androidAssistantModelKey) as String?,
voiceSilenceDuration: (box.get(_voiceSilenceDurationKey) as int? ?? 2000)
.clamp(300, 3000),
);
Expand Down Expand Up @@ -471,6 +496,7 @@ class AppSettings {
final String? ttsServerVoiceId;
final String? ttsServerVoiceName;
final AndroidAssistantTrigger androidAssistantTrigger;
final String? androidAssistantModelId;
final int voiceSilenceDuration;
const AppSettings({
this.reduceMotion = false,
Expand All @@ -495,6 +521,7 @@ class AppSettings {
this.ttsServerVoiceId,
this.ttsServerVoiceName,
this.androidAssistantTrigger = AndroidAssistantTrigger.overlay,
this.androidAssistantModelId,
this.voiceSilenceDuration = 2000,
});

Expand Down Expand Up @@ -522,6 +549,7 @@ class AppSettings {
Object? ttsServerVoiceName = const _DefaultValue(),
int? voiceSilenceDuration,
AndroidAssistantTrigger? androidAssistantTrigger,
Object? androidAssistantModelId = const _DefaultValue(),
}) {
return AppSettings(
reduceMotion: reduceMotion ?? this.reduceMotion,
Expand Down Expand Up @@ -555,6 +583,9 @@ class AppSettings {
: ttsServerVoiceName as String?,
androidAssistantTrigger:
androidAssistantTrigger ?? this.androidAssistantTrigger,
androidAssistantModelId: androidAssistantModelId is _DefaultValue
? this.androidAssistantModelId
: androidAssistantModelId as String?,
voiceSilenceDuration: voiceSilenceDuration ?? this.voiceSilenceDuration,
);
}
Expand All @@ -581,8 +612,10 @@ class AppSettings {
other.ttsVolume == ttsVolume &&
other.ttsEngine == ttsEngine &&
other.ttsServerVoiceId == ttsServerVoiceId &&
other.ttsServerVoiceName == ttsServerVoiceName &&


other.androidAssistantTrigger == androidAssistantTrigger &&
other.androidAssistantModelId == androidAssistantModelId &&
other.voiceSilenceDuration == voiceSilenceDuration &&
_listEquals(other.quickPills, quickPills);
// socketTransportMode intentionally not included in == to avoid frequent rebuilds
Expand Down Expand Up @@ -611,7 +644,9 @@ class AppSettings {
ttsEngine,
ttsServerVoiceId,
ttsServerVoiceName,

androidAssistantTrigger,
androidAssistantModelId,
voiceSilenceDuration,
Object.hashAllUnordered(quickPills),
]);
Expand Down Expand Up @@ -796,6 +831,11 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
await SettingsService.setAndroidAssistantTrigger(trigger);
}

Future<void> setAndroidAssistantModelId(String? modelId) async {
state = state.copyWith(androidAssistantModelId: modelId);
await SettingsService.setAndroidAssistantModelId(modelId);
}

Future<void> resetToDefaults() async {
const defaultSettings = AppSettings();
await SettingsService.saveSettings(defaultSettings);
Expand Down
29 changes: 20 additions & 9 deletions lib/core/utils/android_assistant_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../services/navigation_service.dart';
import '../../shared/services/tasks/task_queue.dart';
import '../providers/app_providers.dart';
import '../../features/auth/providers/unified_auth_providers.dart';
import '../services/settings_service.dart';
import 'debug_logger.dart';

final androidAssistantProvider = Provider(
Expand Down Expand Up @@ -78,8 +79,11 @@ class AndroidAssistantHandler {
return;
}

// Start a fresh chat context
startNewChat(_ref);
// Start a fresh chat context with specific model preference
final settings = _ref.read(appSettingsProvider);
final specificModelId = settings.androidAssistantModelId;

startNewChat(_ref, specificModelId: specificModelId);

// Add screenshot as attachment
final file = File(screenshotPath);
Expand Down Expand Up @@ -189,20 +193,27 @@ class AndroidAssistantHandler {
DebugLogger.log('Starting new chat from assistant', scope: 'assistant');

final navState = _ref.read(authNavigationStateProvider);
final model = _ref.read(selectedModelProvider);

if (navState != AuthNavigationState.authenticated || model == null) {
DebugLogger.log('App not ready for new chat', scope: 'assistant');
return;
// final model = _ref.read(selectedModelProvider); // We verify model availability inside startNewChat or by checking if we have *any* model later,
// but for "readiness" we mainly care about auth.
// Actually, existing check wanted `model != null`.
// If we are overriding, we might not have a selected model yet.

if (navState != AuthNavigationState.authenticated) {
DebugLogger.log('App not ready for new chat (auth)', scope: 'assistant');
return;
}

final isOnChatRoute = NavigationService.currentRoute == Routes.chat;
if (!isOnChatRoute) {
await NavigationService.navigateToChat();
}

startNewChat(_ref);
DebugLogger.log('New chat started from assistant', scope: 'assistant');
// Check for specific assistant model preference
final settings = _ref.read(appSettingsProvider);
final specificModelId = settings.androidAssistantModelId;

startNewChat(_ref, specificModelId: specificModelId);
DebugLogger.log('New chat started from assistant (model: $specificModelId)', scope: 'assistant');
} catch (e) {
DebugLogger.log('Failed to start new chat: $e', scope: 'assistant');
}
Expand Down
27 changes: 23 additions & 4 deletions lib/features/chat/providers/chat_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:yaml/yaml.dart' as yaml;
import '../../../core/auth/auth_state_manager.dart';
import '../../../core/models/chat_message.dart';
import '../../../core/models/conversation.dart';
import '../../../core/models/model.dart';
import '../../../core/providers/app_providers.dart';
import '../../../core/services/conversation_delta_listener.dart';
import '../../../core/services/settings_service.dart';
Expand Down Expand Up @@ -887,7 +888,7 @@ String? _extractSystemPromptFromSettings(Map<String, dynamic>? settings) {
}

// Start a new chat (unified function for both "New Chat" button and home screen)
void startNewChat(dynamic ref) {
void startNewChat(dynamic ref, {String? specificModelId}) {
// Clear active conversation
ref.read(activeConversationProvider.notifier).clear();

Expand All @@ -901,15 +902,33 @@ void startNewChat(dynamic ref) {
ref.read(pendingFolderIdProvider.notifier).clear();

// Reset to default model for new conversations (fixes #296)
restoreDefaultModel(ref);
restoreDefaultModel(ref, specificModelId: specificModelId);
}

/// Restores the selected model to the user's configured default model.
/// Restores the selected model to the user's configured default model or a specific one.
/// Call this when starting a new conversation or when settings change.
Future<void> restoreDefaultModel(dynamic ref) async {
Future<void> restoreDefaultModel(dynamic ref, {String? specificModelId}) async {
// Mark that this is not a manual selection
ref.read(isManualModelSelectionProvider.notifier).set(false);

if (specificModelId != null) {
// Attempt to set specific model
final modelsAsync = ref.read(modelsProvider);
List<Model> models = [];
if (modelsAsync.hasValue) {
models = modelsAsync.value!;
} else {
models = await ref.read(modelsProvider.future);
}

final specificModel = models.where((m) => m.id == specificModelId).firstOrNull;
if (specificModel != null) {
ref.read(selectedModelProvider.notifier).set(specificModel);
return;
}
// Fallback if specific model not found: continue to standard default logic
}

// If auto-select (no explicit default), clear the cached default model
// so defaultModelProvider will fetch from server
final settingsDefault = ref.read(appSettingsProvider).defaultModel;
Expand Down
Loading