Merge pull request #155 from cogwheel0/improve-conversation-fetching

feat(conversations): Improve conversation and folder fetching with concurrent requests
This commit is contained in:
cogwheel
2025-11-21 12:21:37 +05:30
committed by GitHub
2 changed files with 213 additions and 193 deletions

View File

@@ -984,15 +984,28 @@ class Conversations extends _$Conversations {
try { try {
DebugLogger.log('fetch-start', scope: 'conversations'); DebugLogger.log('fetch-start', scope: 'conversations');
final conversations = await api.getConversations(); final conversationsFuture = api.getConversations();
final foldersFuture = api.getFolders().catchError((error, stackTrace) {
DebugLogger.error(
'folders-fetch-failed',
scope: 'conversations',
error: error,
stackTrace: stackTrace,
);
return <Map<String, dynamic>>[];
});
final results = await Future.wait<dynamic>([
conversationsFuture,
foldersFuture,
]);
final conversations = results[0] as List<Conversation>;
final foldersData = results[1] as List<Map<String, dynamic>>;
DebugLogger.log( DebugLogger.log(
'fetch-ok', 'fetch-ok',
scope: 'conversations', scope: 'conversations',
data: {'count': conversations.length}, data: {'count': conversations.length},
); );
try {
final foldersData = await api.getFolders();
DebugLogger.log( DebugLogger.log(
'folders-fetched', 'folders-fetched',
scope: 'conversations', scope: 'conversations',
@@ -1081,7 +1094,7 @@ class Conversations extends _$Conversations {
List<Conversation> folderConvs = const []; List<Conversation> folderConvs = const [];
if (shouldFetchFolder) { if (shouldFetchFolder) {
try { try {
folderConvs = await api.getConversationsInFolder(folder.id); folderConvs = await api.getFolderConversationSummaries(folder.id);
DebugLogger.log( DebugLogger.log(
'folder-sync', 'folder-sync',
scope: 'conversations/map', scope: 'conversations/map',
@@ -1161,21 +1174,6 @@ class Conversations extends _$Conversations {
); );
_updateCacheTimestamp(DateTime.now()); _updateCacheTimestamp(DateTime.now());
return sortedConversations; return sortedConversations;
} catch (e) {
DebugLogger.error(
'folders-fetch-failed',
scope: 'conversations',
error: e,
);
final sorted = _sortByUpdatedAt(conversations.toList());
DebugLogger.log(
'sort',
scope: 'conversations',
data: {'source': 'fallback'},
);
_updateCacheTimestamp(DateTime.now());
return sorted;
}
} catch (e, stackTrace) { } catch (e, stackTrace) {
DebugLogger.error( DebugLogger.error(
'fetch-failed', 'fetch-failed',

View File

@@ -409,6 +409,15 @@ class ApiService {
// Conversations - Updated to use correct OpenWebUI API // Conversations - Updated to use correct OpenWebUI API
Future<List<Conversation>> getConversations({int? limit, int? skip}) async { Future<List<Conversation>> getConversations({int? limit, int? skip}) async {
final pinnedFuture = _fetchChatCollection(
'/api/v1/chats/pinned',
debugLabel: 'pinned chats',
);
final archivedFuture = _fetchChatCollection(
'/api/v1/chats/archived',
debugLabel: 'archived chats',
);
List<dynamic> allRegularChats = []; List<dynamic> allRegularChats = [];
if (limit == null) { if (limit == null) {
@@ -422,7 +431,11 @@ class ApiService {
while (true) { while (true) {
final response = await _dio.get( final response = await _dio.get(
'/api/v1/chats/', '/api/v1/chats/',
queryParameters: {'page': currentPage}, queryParameters: {
'page': currentPage,
'include_folders': true,
'include_pinned': true,
},
); );
if (response.data is! List) { if (response.data is! List) {
@@ -454,14 +467,21 @@ class ApiService {
); );
} else { } else {
// Original single page fetch // Original single page fetch
final pageQuery = <String, dynamic>{
'include_folders': true,
'include_pinned': true,
};
if (limit > 0) {
pageQuery['page'] = (((skip ?? 0) / limit).floor() + 1).clamp(
1,
1 << 30,
);
}
final regularResponse = await _dio.get( final regularResponse = await _dio.get(
'/api/v1/chats/', '/api/v1/chats/',
// Convert skip/limit to 1-based page index expected by OpenWebUI. // Convert skip/limit to 1-based page index expected by OpenWebUI.
// Example: skip=0 => page=1, skip=limit => page=2, etc. // Example: skip=0 => page=1, skip=limit => page=2, etc.
queryParameters: { queryParameters: pageQuery,
if (limit > 0)
'page': (((skip ?? 0) / limit).floor() + 1).clamp(1, 1 << 30),
},
); );
if (regularResponse.data is! List) { if (regularResponse.data is! List) {
@@ -473,14 +493,12 @@ class ApiService {
allRegularChats = regularResponse.data as List; allRegularChats = regularResponse.data as List;
} }
final pinnedChatList = await _fetchChatCollection( final pinnedAndArchived = await Future.wait<List<dynamic>>([
'/api/v1/chats/pinned', pinnedFuture,
debugLabel: 'pinned chats', archivedFuture,
); ]);
final archivedChatList = await _fetchChatCollection( final pinnedChatList = pinnedAndArchived[0];
'/api/v1/chats/all/archived', final archivedChatList = pinnedAndArchived[1];
debugLabel: 'archived chats',
);
final regularChatList = allRegularChats; final regularChatList = allRegularChats;
DebugLogger.log( DebugLogger.log(
@@ -1035,17 +1053,21 @@ class ApiService {
); );
} }
Future<List<Conversation>> getConversationsInFolder(String folderId) async { Future<List<Conversation>> getFolderConversationSummaries(
_traceApi('Fetching conversations in folder: $folderId'); String folderId,
final response = await _dio.get('/api/v1/chats/folder/$folderId'); ) async {
_traceApi('Fetching conversation summaries in folder: $folderId');
final response = await _dio.get('/api/v1/chats/folder/$folderId/list');
final data = response.data; final data = response.data;
if (data is List) { if (data is! List) {
return _parseConversationSummaryList( return const [];
data,
debugLabel: 'parse_folder_$folderId',
);
} }
return []; final normalized = data
.whereType<Map<String, dynamic>>()
.map(parseConversationSummary)
.map(Conversation.fromJson)
.toList(growable: false);
return normalized;
} }
// Tags // Tags