feat(api): Optimize conversation parsing with worker-based decoding
This commit is contained in:
@@ -911,6 +911,26 @@ class ApiService {
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<List<Conversation>> _parseConversationSummaryList(
|
||||
List<dynamic> regular, {
|
||||
required String debugLabel,
|
||||
}) async {
|
||||
final payload = <String, dynamic>{
|
||||
'regular': List<dynamic>.from(regular),
|
||||
'pinned': const <dynamic>[],
|
||||
'archived': const <dynamic>[],
|
||||
};
|
||||
final parsed = await _workerManager
|
||||
.schedule<Map<String, dynamic>, List<Map<String, dynamic>>>(
|
||||
parseConversationSummariesWorker,
|
||||
payload,
|
||||
debugLabel: debugLabel,
|
||||
);
|
||||
return parsed
|
||||
.map((json) => Conversation.fromJson(json))
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
// Tools - Check available tools on server
|
||||
Future<List<Map<String, dynamic>>> getAvailableTools() async {
|
||||
_traceApi('Fetching available tools');
|
||||
@@ -1005,10 +1025,10 @@ class ApiService {
|
||||
final response = await _dio.get('/api/v1/chats/folder/$folderId');
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
return data.whereType<Map>().map((chatData) {
|
||||
final map = Map<String, dynamic>.from(chatData);
|
||||
return Conversation.fromJson(parseConversationSummary(map));
|
||||
}).toList();
|
||||
return _parseConversationSummaryList(
|
||||
data,
|
||||
debugLabel: 'parse_folder_$folderId',
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -1052,10 +1072,7 @@ class ApiService {
|
||||
final response = await _dio.get('/api/v1/chats/tags/$tag');
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
return data.whereType<Map>().map((chatData) {
|
||||
final map = Map<String, dynamic>.from(chatData);
|
||||
return Conversation.fromJson(parseConversationSummary(map));
|
||||
}).toList();
|
||||
return _parseConversationSummaryList(data, debugLabel: 'parse_tag_$tag');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -2738,8 +2755,11 @@ class ApiService {
|
||||
'/api/v1/chats/search',
|
||||
queryParameters: {'q': query},
|
||||
);
|
||||
final results = response.data as List;
|
||||
return results.map((c) => Conversation.fromJson(c)).toList();
|
||||
final results = response.data;
|
||||
if (results is List) {
|
||||
return _parseConversationSummaryList(results, debugLabel: 'parse_search');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Debug method to test API endpoints
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../persistence/hive_boxes.dart';
|
||||
import '../persistence/persistence_keys.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
import 'secure_credential_storage.dart';
|
||||
import 'worker_manager.dart';
|
||||
|
||||
/// Optimized storage service backed by Hive for non-sensitive data and
|
||||
/// FlutterSecureStorage for credentials.
|
||||
@@ -16,19 +17,22 @@ class OptimizedStorageService {
|
||||
OptimizedStorageService({
|
||||
required FlutterSecureStorage secureStorage,
|
||||
required HiveBoxes boxes,
|
||||
required WorkerManager workerManager,
|
||||
}) : _preferencesBox = boxes.preferences,
|
||||
_cachesBox = boxes.caches,
|
||||
_attachmentQueueBox = boxes.attachmentQueue,
|
||||
_metadataBox = boxes.metadata,
|
||||
_secureCredentialStorage = SecureCredentialStorage(
|
||||
instance: secureStorage,
|
||||
);
|
||||
),
|
||||
_workerManager = workerManager;
|
||||
|
||||
final Box<dynamic> _preferencesBox;
|
||||
final Box<dynamic> _cachesBox;
|
||||
final Box<dynamic> _attachmentQueueBox;
|
||||
final Box<dynamic> _metadataBox;
|
||||
final SecureCredentialStorage _secureCredentialStorage;
|
||||
final WorkerManager _workerManager;
|
||||
|
||||
static const String _authTokenKey = 'auth_token_v3';
|
||||
static const String _activeServerIdKey = PreferenceKeys.activeServerId;
|
||||
@@ -298,19 +302,13 @@ class OptimizedStorageService {
|
||||
if (stored == null) {
|
||||
return const [];
|
||||
}
|
||||
if (stored is String) {
|
||||
final decoded = jsonDecode(stored) as List<dynamic>;
|
||||
return decoded.map((item) => Conversation.fromJson(item)).toList();
|
||||
}
|
||||
if (stored is List) {
|
||||
return stored
|
||||
.map(
|
||||
(item) =>
|
||||
Conversation.fromJson(Map<String, dynamic>.from(item as Map)),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
return const [];
|
||||
final parsed = await _workerManager
|
||||
.schedule<Map<String, dynamic>, List<Map<String, dynamic>>>(
|
||||
_decodeStoredConversationsWorker,
|
||||
{'stored': stored},
|
||||
debugLabel: 'decode_local_conversations',
|
||||
);
|
||||
return parsed.map(Conversation.fromJson).toList(growable: false);
|
||||
} catch (error, stack) {
|
||||
DebugLogger.error(
|
||||
'Failed to retrieve local conversations',
|
||||
@@ -455,3 +453,28 @@ class OptimizedStorageService {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _decodeStoredConversationsWorker(
|
||||
Map<String, dynamic> payload,
|
||||
) {
|
||||
final stored = payload['stored'];
|
||||
if (stored is String) {
|
||||
final decoded = jsonDecode(stored);
|
||||
if (decoded is List) {
|
||||
return decoded
|
||||
.whereType<Map>()
|
||||
.map((item) => Map<String, dynamic>.from(item))
|
||||
.toList();
|
||||
}
|
||||
return <Map<String, dynamic>>[];
|
||||
}
|
||||
|
||||
if (stored is List) {
|
||||
return stored
|
||||
.whereType<Map>()
|
||||
.map((item) => Map<String, dynamic>.from(item))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return <Map<String, dynamic>>[];
|
||||
}
|
||||
|
||||
@@ -170,12 +170,14 @@ class WorkerManager {
|
||||
|
||||
/// Keep a single [WorkerManager] alive across the app.
|
||||
@Riverpod(keepAlive: true)
|
||||
// ignore: functional_ref
|
||||
WorkerManager workerManager(Ref ref) {
|
||||
final concurrency = kIsWeb ? 1 : WorkerManager._defaultMaxConcurrentTasks;
|
||||
final manager = WorkerManager(maxConcurrentTasks: concurrency);
|
||||
ref.onDispose(manager.dispose);
|
||||
return manager;
|
||||
class WorkerManagerNotifier extends _$WorkerManagerNotifier {
|
||||
@override
|
||||
WorkerManager build() {
|
||||
final concurrency = kIsWeb ? 1 : WorkerManager._defaultMaxConcurrentTasks;
|
||||
final manager = WorkerManager(maxConcurrentTasks: concurrency);
|
||||
ref.onDispose(manager.dispose);
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
class _EnqueuedJob {
|
||||
|
||||
Reference in New Issue
Block a user