2 files changed
+57
-20
lib/features/chat/providers/model_selection_providers.dart
+31
-20
@@ -52,39 +52,50 @@ final activeChatTargetProvider = Provider<ActiveChatTarget>((ref) {
52
53
final selected = ref.watch(selectedModelProvider);
54
final selectedForServer = selected?.serverId == server.id ? selected : null;
55
- if (selectedForServer != null) {
55
+ if (server.type == ServerType.onDevice) {
56
+ final engine = ref.watch(onDeviceEngineProvider);
57
+ if (selectedForServer != null &&
58
+ engine.status == OnDeviceEngineStatus.loaded &&
59
+ engine.loadedModelId == selectedForServer.id) {
60
+ return ActiveChatTarget(
61
+ server: server,
62
+ selectedModel: selectedForServer,
63
+ effectiveModelId: selectedForServer.id,
64
+ modelLabel: selectedForServer.displayName,
65
+ );
66
+ }
67
+
68
+ final modelId = engine.status == OnDeviceEngineStatus.loaded
69
+ ? engine.loadedModelId
70
+ : null;
71
+ final model = modelId == null
72
+ ? null
73
+ : OnDeviceModel.allCuratedModels
74
+ .where((candidate) => candidate.id == modelId)
75
+ .firstOrNull;
76
+
77
return ActiveChatTarget(
78
server: server,
58
- selectedModel: selectedForServer,
59
- effectiveModelId: selectedForServer.id,
60
- modelLabel: selectedForServer.displayName,
79
+ selectedModel: null,
80
+ effectiveModelId: modelId,
81
+ modelLabel: model?.name ?? modelId ?? 'No model',
82
);
83
}
84
64
- if (server.type != ServerType.onDevice) {
85
+ if (selectedForServer != null) {
86
return ActiveChatTarget(
87
server: server,
67
- selectedModel: null,
68
- effectiveModelId: 'default',
69
- modelLabel: 'Default model',
88
+ selectedModel: selectedForServer,
89
+ effectiveModelId: selectedForServer.id,
90
+ modelLabel: selectedForServer.displayName,
91
);
92
}
93
73
- final engine = ref.watch(onDeviceEngineProvider);
74
- final modelId = engine.status == OnDeviceEngineStatus.loaded
75
- ? engine.loadedModelId
76
- : null;
77
- final model = modelId == null
78
- ? null
79
- : OnDeviceModel.allCuratedModels
80
- .where((candidate) => candidate.id == modelId)
81
- .firstOrNull;
82
-
94
return ActiveChatTarget(
95
server: server,
96
selectedModel: null,
86
- effectiveModelId: modelId,
87
- modelLabel: model?.name ?? modelId ?? 'No model',
97
+ effectiveModelId: 'default',
98
+ modelLabel: 'Default model',
99
);
100
});
101
test/features/chat/providers/active_chat_target_provider_test.dart
+26
@@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
2
import 'package:flutter_test/flutter_test.dart';
3
import 'package:localmind/core/models/enums.dart';
4
import 'package:localmind/features/chat/providers/model_selection_providers.dart';
5
+import 'package:localmind/features/models/data/models/model_info.dart';
6
import 'package:localmind/features/on_device/providers/on_device_providers.dart';
7
import 'package:localmind/features/servers/data/models/server.dart';
8
import 'package:localmind/features/servers/providers/server_providers.dart';
@@ -39,6 +40,31 @@ void main() {
40
expect(target.modelLabel, 'No model');
41
});
42
43
+ test('does not treat a selected on-device model as ready until loaded', () {
44
+ final container = ProviderContainer(
45
+ overrides: [
46
+ activeServerProvider.overrideWith(_OnDeviceServerNotifier.new),
47
+ onDeviceEngineProvider.overrideWith(_EmptyEngineNotifier.new),
48
+ ],
49
+ );
50
+ addTearDown(container.dispose);
51
+
52
+ container.read(selectedModelProvider.notifier).setModel(
53
+ ModelInfo(
54
+ id: 'apple-foundation-models',
55
+ name: 'Apple Foundation Models',
56
+ serverType: ServerType.onDevice,
57
+ serverId: 'on-device',
58
+ ),
59
+ );
60
+
61
+ final target = container.read(activeChatTargetProvider);
62
+
63
+ expect(target.isReady, isFalse);
64
+ expect(target.effectiveModelId, isNull);
65
+ expect(target.modelLabel, 'No model');
66
+ });
67
+
68
test('keeps default routing for a selected remote server', () {
69
final container = ProviderContainer(
70
overrides: [activeServerProvider.overrideWith(_RemoteServerNotifier.new)],