refactor: Improve model tools auto-selection logic

This commit refactors the `modelToolsAutoSelectionProvider` to more robustly handle the automatic selection of tools when the model changes or the list of available tools is updated.

Key changes include:
- Triggering the auto-selection logic not only when the selected model changes, but also when the `toolsListProvider` finishes loading. This ensures tools are correctly applied even if they load after the model is selected.
- Restructuring the logic to handle asynchronous tool loading more gracefully, preventing race conditions.
- Using `Future.microtask` to schedule the tool application, ensuring state updates are handled correctly within the provider lifecycle.
- Clearing selected tools if the new model has no associated tools or if previously selected tools become invalid.
This commit is contained in:
cogwheel0
2025-10-13 15:25:20 +05:30
parent ea14ea6f09
commit c07ac6b427

View File

@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@@ -17,6 +18,7 @@ import '../models/chat_message.dart';
import '../models/folder.dart'; import '../models/folder.dart';
import '../models/user_settings.dart'; import '../models/user_settings.dart';
import '../models/file_info.dart'; import '../models/file_info.dart';
import '../models/tool.dart';
import '../models/knowledge_base.dart'; import '../models/knowledge_base.dart';
import '../services/settings_service.dart'; import '../services/settings_service.dart';
import '../services/optimized_storage_service.dart'; import '../services/optimized_storage_service.dart';
@@ -646,36 +648,84 @@ final _settingsWatcherProvider = Provider<void>((ref) {
}); });
}); });
// Auto-apply model-specific tools when model changes // Auto-apply model-specific tools when model changes or tools load
final modelToolsAutoSelectionProvider = Provider<void>((ref) { final modelToolsAutoSelectionProvider = Provider<void>((ref) {
ref.listen<Model?>(selectedModelProvider, (previous, next) { Future<void> applyTools(Model? model) async {
// Only react when the model actually changes if (model == null) {
if (previous?.id == next?.id) return; final current = ref.read(selectedToolIdsProvider);
if (next == null) return; if (current.isNotEmpty) {
ref.read(selectedToolIdsProvider.notifier).set([]);
}
return;
}
// Load tools configured for this model final modelToolIds = model.toolIds ?? [];
final modelToolIds = next.toolIds ?? []; if (modelToolIds.isEmpty) {
if (modelToolIds.isNotEmpty) { final current = ref.read(selectedToolIdsProvider);
// Filter to only include tools that are actually available if (current.isNotEmpty) {
final toolsAsync = ref.read(toolsListProvider); ref.read(selectedToolIdsProvider.notifier).set([]);
toolsAsync.whenData((availableTools) { }
return;
}
void updateSelection(List<Tool> availableTools) {
final validToolIds = modelToolIds final validToolIds = modelToolIds
.where((id) => availableTools.any((t) => t.id == id)) .where((id) => availableTools.any((tool) => tool.id == id))
.toList(); .toList();
if (validToolIds.isNotEmpty) { final currentSelection = ref.read(selectedToolIdsProvider);
if (validToolIds.isEmpty) {
if (currentSelection.isNotEmpty) {
ref.read(selectedToolIdsProvider.notifier).set([]);
}
return;
}
if (listEquals(currentSelection, validToolIds)) return;
ref.read(selectedToolIdsProvider.notifier).set(validToolIds); ref.read(selectedToolIdsProvider.notifier).set(validToolIds);
DebugLogger.log( DebugLogger.log(
'auto-apply-tools', 'auto-apply-tools',
scope: 'models/tools', scope: 'models/tools',
data: {'modelId': next.id, 'toolCount': validToolIds.length}, data: {'modelId': model.id, 'toolCount': validToolIds.length},
); );
} }
});
} else { final toolsAsync = ref.read(toolsListProvider);
// Clear tools if model has no configured tools if (toolsAsync.hasValue) {
ref.read(selectedToolIdsProvider.notifier).set([]); updateSelection(toolsAsync.value ?? const <Tool>[]);
return;
} }
try {
final availableTools = await ref.read(toolsListProvider.future);
if (!ref.mounted) return;
updateSelection(availableTools);
} catch (error, stackTrace) {
DebugLogger.error(
'auto-apply-tools-failed',
scope: 'models/tools',
error: error,
stackTrace: stackTrace,
);
}
}
Future<void> scheduleApply(Model? model) async {
await applyTools(model);
}
Future.microtask(() => scheduleApply(ref.read(selectedModelProvider)));
ref.listen<Model?>(selectedModelProvider, (previous, next) {
if (previous?.id == next?.id && previous != null) {
return;
}
Future.microtask(() => scheduleApply(next));
});
ref.listen(toolsListProvider, (previous, next) {
if (!next.hasValue) return;
Future.microtask(() => scheduleApply(ref.read(selectedModelProvider)));
}); });
}); });