1 file changed
+34
-11
lib/features/servers/data/server_api_service.dart
+34
-11
@@ -1,5 +1,6 @@
1
import 'package:dio/dio.dart';
2
3
+import '../../../core/logger/app_logger.dart';
4
import '../../../core/models/enums.dart';
5
import '../../models/data/models/model_info.dart';
6
import 'models/server.dart';
@@ -48,7 +49,7 @@ class ServerApiService {
49
switch (server.type) {
50
case ServerType.lmStudio:
51
case ServerType.openAICompatible:
51
- return _parseLMStudioModels(response.data, server);
52
+ return _parseOpenAICompatibleModels(response.data, server);
53
case ServerType.ollama:
54
return _parseOllamaModels(response.data, server);
55
case ServerType.openRouter:
@@ -75,7 +76,7 @@ class ServerApiService {
76
switch (server.type) {
77
case ServerType.lmStudio:
78
case ServerType.openAICompatible:
78
- return _parseRunningLMStudioModels(response.data);
79
+ return _parseRunningOpenAICompatibleModels(response.data);
80
case ServerType.ollama:
81
return _parseRunningOllamaModels(response.data);
82
case ServerType.openRouter:
@@ -106,12 +107,14 @@ class ServerApiService {
107
} catch (_) {
108
// Not all OpenAI-compatible servers support this endpoint
109
}
110
+ break;
111
case ServerType.ollama:
112
await _dio.post(
113
server.loadModelEndpoint,
114
data: {'model': modelId},
115
options: Options(headers: _getAuthHeaders(server)),
116
);
117
+ break;
118
case ServerType.openRouter:
119
case ServerType.onDevice:
120
break;
@@ -172,19 +175,21 @@ class ServerApiService {
175
} catch (_) {
176
// Not all OpenAI-compatible servers support this endpoint
177
}
178
+ break;
179
case ServerType.ollama:
180
await _dio.post(
181
server.unloadModelEndpoint,
182
data: {'model': modelId, 'keep_alive': 0},
183
options: Options(headers: _getAuthHeaders(server)),
184
);
185
+ break;
186
case ServerType.openRouter:
187
case ServerType.onDevice:
188
break;
189
}
190
}
191
187
- Set<String> _parseRunningLMStudioModels(dynamic data) {
192
+ Set<String> _parseRunningOpenAICompatibleModels(dynamic data) {
193
final runningModels = <String>{};
194
if (data['models'] != null) {
195
for (final item in data['models']) {
@@ -192,10 +197,14 @@ class ServerApiService {
197
item['name'] as String? ??
198
item['id'] as String? ??
199
'';
200
+ if (id.isEmpty) continue;
201
+
202
final loadedInstances = item['loaded_instances'] as List?;
196
- if (loadedInstances != null && loadedInstances.isNotEmpty) {
197
- runningModels.add(id);
198
- } else if (id.isNotEmpty) {
203
+ if (loadedInstances != null) {
204
+ if (loadedInstances.isNotEmpty) {
205
+ runningModels.add(id);
206
+ }
207
+ } else {
208
// Servers without loaded_instances (e.g. llama.cpp) always have
209
// their model running if it appears in the model list
210
runningModels.add(id);
@@ -222,7 +231,7 @@ class ServerApiService {
231
return null;
232
}
233
225
- List<ModelInfo> _parseLMStudioModels(dynamic data, Server server) {
234
+ List<ModelInfo> _parseOpenAICompatibleModels(dynamic data, Server server) {
235
final List<ModelInfo> models = [];
236
237
// Build a lookup from the OpenAI-style 'data' array for metadata
@@ -243,10 +252,17 @@ class ServerApiService {
252
item['name'] as String? ??
253
item['id'] as String? ??
254
'';
255
+ if (id.isEmpty) continue;
256
+
257
final displayName = item['display_name'] as String?;
258
final quantization = item['quantization'];
259
final paramsString = item['params_string'] as String?;
249
- final meta = metaById[id] ?? {};
260
+
261
+ final meta = metaById[id];
262
+ if (meta == null && metaById.isNotEmpty) {
263
+ Log.warning('Metadata join failed for model: $id');
264
+ }
265
+ final finalMeta = meta ?? {};
266
267
models.add(
268
ModelInfo(
@@ -254,11 +270,11 @@ class ServerApiService {
270
name: displayName ?? _formatModelName(id),
271
description: item['description'] as String?,
272
parameterCount: _parseParameterString(paramsString) ??
257
- _paramCountFromMeta(meta['n_params']),
273
+ _paramCountFromMeta(finalMeta['n_params']),
274
contextLength: item['max_context_length'] as int? ??
259
- meta['n_ctx_train'] as int?,
275
+ finalMeta['n_ctx_train'] as int?,
276
fileSize: item['size_bytes'] as int? ??
261
- meta['size'] as int?,
277
+ finalMeta['size'] as int?,
278
quantization: quantization?['name'] as String?,
279
architecture: item['architecture'] as String?,
280
serverType: server.type,
@@ -273,6 +289,13 @@ class ServerApiService {
289
int? _paramCountFromMeta(dynamic nParams) {
290
if (nParams == null) return null;
291
if (nParams is int) return nParams ~/ 1000000000;
292
+ if (nParams is double) return (nParams / 1000000000).round();
293
+ if (nParams is String) {
294
+ final parsedInt = int.tryParse(nParams);
295
+ if (parsedInt != null) return parsedInt ~/ 1000000000;
296
+ final parsedDouble = double.tryParse(nParams);
297
+ if (parsedDouble != null) return (parsedDouble / 1000000000).round();
298
+ }
299
return null;
300
}
301