refactor: all logging
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/foundation.dart' hide debugPrint;
|
||||
import 'package:dio/dio.dart';
|
||||
// import 'package:http_parser/http_parser.dart';
|
||||
// Removed legacy websocket/socket.io imports
|
||||
@@ -17,6 +17,17 @@ import '../error/api_error_interceptor.dart';
|
||||
import 'persistent_streaming_service.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
const bool _enableLegacyApiLogs = false;
|
||||
const bool _traceConversationParsing = false;
|
||||
const bool _traceFullChatParsing = false;
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (!_enableLegacyApiLogs || message == null) {
|
||||
return;
|
||||
}
|
||||
DebugLogger.fromLegacy(message, scope: 'api/legacy');
|
||||
}
|
||||
|
||||
class ApiService {
|
||||
final Dio _dio;
|
||||
final ServerConfig serverConfig;
|
||||
@@ -187,7 +198,7 @@ class ApiService {
|
||||
// User info
|
||||
Future<User> getCurrentUser() async {
|
||||
final response = await _dio.get('/api/v1/auths/');
|
||||
DebugLogger.log('User info retrieved successfully');
|
||||
DebugLogger.log('user-info', scope: 'api/user');
|
||||
return User.fromJson(response.data);
|
||||
}
|
||||
|
||||
@@ -204,11 +215,15 @@ class ApiService {
|
||||
// Response is a direct array
|
||||
models = response.data as List;
|
||||
} else {
|
||||
DebugLogger.error('Unexpected models response format');
|
||||
DebugLogger.error('models-format', scope: 'api/models');
|
||||
return [];
|
||||
}
|
||||
|
||||
DebugLogger.log('Found ${models.length} models');
|
||||
DebugLogger.log(
|
||||
'models-count',
|
||||
scope: 'api/models',
|
||||
data: {'count': models.length},
|
||||
);
|
||||
return models.map((m) => Model.fromJson(m)).toList();
|
||||
}
|
||||
|
||||
@@ -217,12 +232,14 @@ class ApiService {
|
||||
try {
|
||||
final response = await _dio.get('/api/v1/users/user/settings');
|
||||
|
||||
DebugLogger.log('User settings retrieved successfully');
|
||||
DebugLogger.log('settings-ok', scope: 'api/user-settings');
|
||||
|
||||
final data = response.data;
|
||||
if (data is! Map<String, dynamic>) {
|
||||
DebugLogger.warning(
|
||||
'User settings response is empty or unexpected type: ${data.runtimeType}',
|
||||
'settings-format',
|
||||
scope: 'api/user-settings',
|
||||
data: {'type': data.runtimeType},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -235,16 +252,22 @@ class ApiService {
|
||||
// Return the first model in the user's preferred models list
|
||||
final defaultModel = models.first.toString();
|
||||
DebugLogger.log(
|
||||
'Found default model from user settings: $defaultModel',
|
||||
'default-model',
|
||||
scope: 'api/user-settings',
|
||||
data: {'id': defaultModel},
|
||||
);
|
||||
return defaultModel;
|
||||
}
|
||||
}
|
||||
|
||||
DebugLogger.log('No default model found in user settings');
|
||||
DebugLogger.warning('default-model-missing', scope: 'api/user-settings');
|
||||
return null;
|
||||
} catch (e) {
|
||||
DebugLogger.error('Error fetching default model from user settings', e);
|
||||
DebugLogger.error(
|
||||
'default-model-error',
|
||||
scope: 'api/user-settings',
|
||||
error: e,
|
||||
);
|
||||
// Do not call admin-only configs endpoint here; let the caller
|
||||
// handle fallback (e.g., first available model from /api/models).
|
||||
return null;
|
||||
@@ -327,9 +350,15 @@ class ApiService {
|
||||
);
|
||||
final regularChatList = allRegularChats;
|
||||
|
||||
debugPrint('DEBUG: Found ${regularChatList.length} regular chats');
|
||||
debugPrint('DEBUG: Found ${pinnedChatList.length} pinned chats');
|
||||
debugPrint('DEBUG: Found ${archivedChatList.length} archived chats');
|
||||
DebugLogger.log(
|
||||
'summary',
|
||||
scope: 'api/conversations',
|
||||
data: {
|
||||
'regular': regularChatList.length,
|
||||
'pinned': pinnedChatList.length,
|
||||
'archived': archivedChatList.length,
|
||||
},
|
||||
);
|
||||
|
||||
// Convert OpenWebUI chat format to our Conversation format
|
||||
final conversations = <Conversation>[];
|
||||
@@ -345,7 +374,12 @@ class ApiService {
|
||||
conversations.add(pinnedConversation);
|
||||
pinnedIds.add(conversation.id);
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Error parsing pinned chat ${chatData['id']}: $e');
|
||||
DebugLogger.error(
|
||||
'parse-pinned-failed',
|
||||
scope: 'api/conversations',
|
||||
error: e,
|
||||
data: {'conversationId': chatData['id']},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +392,12 @@ class ApiService {
|
||||
conversations.add(archivedConversation);
|
||||
archivedIds.add(conversation.id);
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Error parsing archived chat ${chatData['id']}: $e');
|
||||
DebugLogger.error(
|
||||
'parse-archived-failed',
|
||||
scope: 'api/conversations',
|
||||
error: e,
|
||||
data: {'conversationId': chatData['id']},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,21 +408,28 @@ class ApiService {
|
||||
// Debug: Check if conversation has folder_id in raw data
|
||||
if (chatData.containsKey('folder_id') &&
|
||||
chatData['folder_id'] != null) {
|
||||
debugPrint(
|
||||
'🔍 DEBUG: Found conversation with folder_id in raw data: ${chatData['id']} -> ${chatData['folder_id']}',
|
||||
DebugLogger.log(
|
||||
'folder-ref',
|
||||
scope: 'api/conversations',
|
||||
data: {
|
||||
'conversationId': chatData['id'],
|
||||
'folderId': chatData['folder_id'],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!loggedSampleChat) {
|
||||
if (!loggedSampleChat && _traceConversationParsing) {
|
||||
loggedSampleChat = true;
|
||||
debugPrint(
|
||||
'🔍 DEBUG: Sample chat data fields: ${chatData.keys.toList()}',
|
||||
DebugLogger.log(
|
||||
'sample-keys',
|
||||
scope: 'api/conversations',
|
||||
data: {'keys': chatData.keys.take(6).toList()},
|
||||
);
|
||||
DebugLogger.log(
|
||||
'sample-data',
|
||||
scope: 'api/conversations',
|
||||
data: {'preview': chatData.toString()},
|
||||
);
|
||||
final samplePreviewSource = chatData.toString();
|
||||
final preview = samplePreviewSource.length > 200
|
||||
? samplePreviewSource.substring(0, 200)
|
||||
: samplePreviewSource;
|
||||
debugPrint('🔍 DEBUG: Sample chat data: $preview...');
|
||||
}
|
||||
|
||||
final conversation = _parseOpenWebUIChat(chatData);
|
||||
@@ -393,13 +439,24 @@ class ApiService {
|
||||
conversations.add(conversation);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Error parsing chat ${chatData['id']}: $e');
|
||||
DebugLogger.error(
|
||||
'parse-regular-failed',
|
||||
scope: 'api/conversations',
|
||||
error: e,
|
||||
data: {'conversationId': chatData['id']},
|
||||
);
|
||||
// Continue with other chats even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'DEBUG: Successfully parsed ${conversations.length} conversations (${pinnedIds.length} pinned, ${archivedIds.length} archived)',
|
||||
DebugLogger.log(
|
||||
'parse-complete',
|
||||
scope: 'api/conversations',
|
||||
data: {
|
||||
'total': conversations.length,
|
||||
'pinned': pinnedIds.length,
|
||||
'archived': archivedIds.length,
|
||||
},
|
||||
);
|
||||
return conversations;
|
||||
}
|
||||
@@ -408,21 +465,30 @@ class ApiService {
|
||||
String path, {
|
||||
required String debugLabel,
|
||||
}) async {
|
||||
final scope = 'api/collection/${debugLabel.replaceAll(' ', '-')}';
|
||||
try {
|
||||
final response = await _dio.get(path);
|
||||
DebugLogger.log('$debugLabel response status: ${response.statusCode}');
|
||||
DebugLogger.log(
|
||||
'status',
|
||||
scope: scope,
|
||||
data: {'code': response.statusCode},
|
||||
);
|
||||
if (response.data is List) {
|
||||
return (response.data as List).cast<dynamic>();
|
||||
}
|
||||
DebugLogger.warning(
|
||||
'Expected array for $debugLabel, got ${response.data.runtimeType}',
|
||||
'unexpected-type',
|
||||
scope: scope,
|
||||
data: {'type': response.data.runtimeType},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
DebugLogger.warning(
|
||||
'Skipping $debugLabel due to network error: ${e.message}',
|
||||
'network-skip',
|
||||
scope: scope,
|
||||
data: {'message': e.message},
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.warning('Skipping $debugLabel due to error: $e');
|
||||
DebugLogger.warning('error-skip', scope: scope, data: {'error': e});
|
||||
}
|
||||
return <dynamic>[];
|
||||
}
|
||||
@@ -484,14 +550,22 @@ class ApiService {
|
||||
final folderId = chatData['folder_id'] as String?;
|
||||
|
||||
// Debug logging for folder assignment
|
||||
if (folderId != null) {
|
||||
if (_traceConversationParsing && folderId != null) {
|
||||
final idPreview = id.length > 8 ? id.substring(0, 8) : id;
|
||||
debugPrint('🔍 DEBUG: Conversation $idPreview has folderId: $folderId');
|
||||
DebugLogger.log(
|
||||
'folder-ref',
|
||||
scope: 'api/conversations',
|
||||
data: {'conversationId': idPreview, 'folderId': folderId},
|
||||
);
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived',
|
||||
);
|
||||
if (_traceConversationParsing) {
|
||||
DebugLogger.log(
|
||||
'parsed',
|
||||
scope: 'api/conversations',
|
||||
data: {'id': id, 'pinned': pinned, 'archived': archived},
|
||||
);
|
||||
}
|
||||
|
||||
String? systemPrompt;
|
||||
final chatObject = chatData['chat'] as Map<String, dynamic>?;
|
||||
@@ -522,10 +596,10 @@ class ApiService {
|
||||
}
|
||||
|
||||
Future<Conversation> getConversation(String id) async {
|
||||
DebugLogger.log('Fetching individual chat: $id');
|
||||
DebugLogger.log('fetch', scope: 'api/chat', data: {'id': id});
|
||||
final response = await _dio.get('/api/v1/chats/$id');
|
||||
|
||||
DebugLogger.log('Chat response received successfully');
|
||||
DebugLogger.log('fetch-ok', scope: 'api/chat');
|
||||
|
||||
// Parse OpenWebUI ChatResponse format
|
||||
final chatData = response.data as Map<String, dynamic>;
|
||||
@@ -534,14 +608,24 @@ class ApiService {
|
||||
|
||||
// Parse full OpenWebUI chat with messages
|
||||
Conversation _parseFullOpenWebUIChat(Map<String, dynamic> chatData) {
|
||||
debugPrint('DEBUG: Parsing full OpenWebUI chat data');
|
||||
debugPrint('DEBUG: Chat data keys: ${chatData.keys.toList()}');
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'parse-full',
|
||||
scope: 'api/chat',
|
||||
data: {'keys': chatData.keys.take(8).toList()},
|
||||
);
|
||||
}
|
||||
|
||||
final id = chatData['id'] as String;
|
||||
final title = chatData['title'] as String;
|
||||
|
||||
debugPrint('DEBUG: Parsed chat ID: $id');
|
||||
debugPrint('DEBUG: Parsed chat title: $title');
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'chat-meta',
|
||||
scope: 'api/chat',
|
||||
data: {'id': id, 'title': title},
|
||||
);
|
||||
}
|
||||
|
||||
// Safely parse timestamps with validation
|
||||
final updatedAt = _parseTimestamp(chatData['updated_at']);
|
||||
@@ -573,7 +657,13 @@ class ApiService {
|
||||
final models = chatObject['models'] as List?;
|
||||
if (models != null && models.isNotEmpty) {
|
||||
model = models.first as String;
|
||||
debugPrint('DEBUG: Extracted model from chat.models: $model');
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'model',
|
||||
scope: 'api/chat',
|
||||
data: {'id': id, 'model': model},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,24 +681,40 @@ class ApiService {
|
||||
final currentId = history['currentId']?.toString();
|
||||
if (currentId != null && currentId.isNotEmpty) {
|
||||
messagesList = _buildMessagesListFromHistory(history);
|
||||
debugPrint(
|
||||
'DEBUG: Built ${messagesList.length} messages from history chain to currentId=$currentId',
|
||||
);
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'history-chain',
|
||||
scope: 'api/chat',
|
||||
data: {
|
||||
'id': id,
|
||||
'count': messagesList.length,
|
||||
'currentId': currentId,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to chat.messages (list format) if history is missing or empty
|
||||
if (((messagesList?.isEmpty ?? true)) && chatObject['messages'] != null) {
|
||||
messagesList = chatObject['messages'] as List;
|
||||
debugPrint(
|
||||
'DEBUG: Found ${messagesList.length} messages in chat.messages (fallback)',
|
||||
);
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'messages-fallback',
|
||||
scope: 'api/chat',
|
||||
data: {'id': id, 'count': messagesList.length},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (chatData['messages'] != null) {
|
||||
messagesList = chatData['messages'] as List;
|
||||
debugPrint(
|
||||
'DEBUG: Found ${messagesList.length} messages in top-level messages',
|
||||
);
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'messages-top-level',
|
||||
scope: 'api/chat',
|
||||
data: {'id': id, 'count': messagesList.length},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse messages from list format only (avoiding duplication)
|
||||
@@ -616,9 +722,18 @@ class ApiService {
|
||||
for (int idx = 0; idx < messagesList.length; idx++) {
|
||||
final msgData = messagesList[idx] as Map<String, dynamic>;
|
||||
try {
|
||||
debugPrint(
|
||||
'DEBUG: Parsing message: ${msgData['id']} - role: ${msgData['role']} - content length: ${msgData['content']?.toString().length ?? 0}',
|
||||
);
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'message-parse',
|
||||
scope: 'api/chat',
|
||||
data: {
|
||||
'chatId': id,
|
||||
'messageId': msgData['id'],
|
||||
'role': msgData['role'],
|
||||
'contentLen': msgData['content']?.toString().length ?? 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// If this assistant message includes tool_calls, merge following tool results
|
||||
final historyMsg = historyMessagesMap != null
|
||||
@@ -667,9 +782,13 @@ class ApiService {
|
||||
|
||||
// Skip the tool messages we just merged
|
||||
idx = j - 1;
|
||||
debugPrint(
|
||||
'DEBUG: Successfully parsed tool_call assistant turn: ${message.id}',
|
||||
);
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'message-tool-call',
|
||||
scope: 'api/chat',
|
||||
data: {'chatId': id, 'messageId': message.id},
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -679,16 +798,35 @@ class ApiService {
|
||||
historyMsg: historyMsg,
|
||||
);
|
||||
messages.add(message);
|
||||
debugPrint(
|
||||
'DEBUG: Successfully parsed message: ${message.id} - ${message.role}',
|
||||
);
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'message',
|
||||
scope: 'api/chat',
|
||||
data: {
|
||||
'chatId': id,
|
||||
'messageId': message.id,
|
||||
'role': message.role,
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Error parsing message: $e');
|
||||
DebugLogger.error(
|
||||
'message-parse-failed',
|
||||
scope: 'api/chat',
|
||||
error: e,
|
||||
data: {'chatId': id, 'messageId': msgData['id']},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('DEBUG: Total parsed messages: ${messages.length}');
|
||||
if (_traceFullChatParsing) {
|
||||
DebugLogger.log(
|
||||
'message-count',
|
||||
scope: 'api/chat',
|
||||
data: {'chatId': id, 'count': messages.length},
|
||||
);
|
||||
}
|
||||
|
||||
return Conversation(
|
||||
id: id,
|
||||
@@ -1188,9 +1326,11 @@ class ApiService {
|
||||
final response = await _dio.post('/api/v1/chats/new', data: chatData);
|
||||
|
||||
DebugLogger.log(
|
||||
'Create conversation response status: ${response.statusCode}',
|
||||
'create-status',
|
||||
scope: 'api/conversation',
|
||||
data: {'code': response.statusCode},
|
||||
);
|
||||
DebugLogger.log('Create conversation response received successfully');
|
||||
DebugLogger.log('create-ok', scope: 'api/conversation');
|
||||
|
||||
// Parse the response
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
@@ -1290,7 +1430,7 @@ class ApiService {
|
||||
// OpenWebUI uses POST not PUT for updating chats
|
||||
await _dio.post('/api/v1/chats/$conversationId', data: chatData);
|
||||
|
||||
DebugLogger.log('Sync conversation response received successfully');
|
||||
DebugLogger.log('sync-ok', scope: 'api/conversation');
|
||||
}
|
||||
|
||||
Future<void> updateConversation(
|
||||
@@ -1381,21 +1521,28 @@ class ApiService {
|
||||
// Folders
|
||||
Future<List<Map<String, dynamic>>> getFolders() async {
|
||||
try {
|
||||
debugPrint('DEBUG: Fetching folders from /api/v1/folders/');
|
||||
final response = await _dio.get('/api/v1/folders/');
|
||||
DebugLogger.log('Folders response status: ${response.statusCode}');
|
||||
DebugLogger.log('Folders response received successfully');
|
||||
DebugLogger.log(
|
||||
'fetch-status',
|
||||
scope: 'api/folders',
|
||||
data: {'code': response.statusCode},
|
||||
);
|
||||
DebugLogger.log('fetch-ok', scope: 'api/folders');
|
||||
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
debugPrint('DEBUG: Found ${data.length} folders');
|
||||
return data.cast<Map<String, dynamic>>();
|
||||
} else {
|
||||
DebugLogger.log('Response data is not a list: ${data.runtimeType}');
|
||||
DebugLogger.warning(
|
||||
'unexpected-type',
|
||||
scope: 'api/folders',
|
||||
data: {'type': data.runtimeType},
|
||||
);
|
||||
return [];
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Error in getFolders: $e');
|
||||
DebugLogger.error('fetch-failed', scope: 'api/folders', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -1783,17 +1930,23 @@ class ApiService {
|
||||
data: {'queries': queries},
|
||||
);
|
||||
|
||||
DebugLogger.log('Web search response status: ${response.statusCode}');
|
||||
DebugLogger.log('Web search response type: ${response.data.runtimeType}');
|
||||
DebugLogger.log('Web search response received successfully');
|
||||
DebugLogger.log(
|
||||
'status',
|
||||
scope: 'api/web-search',
|
||||
data: {'code': response.statusCode},
|
||||
);
|
||||
DebugLogger.log(
|
||||
'response-type',
|
||||
scope: 'api/web-search',
|
||||
data: {'type': response.data.runtimeType},
|
||||
);
|
||||
DebugLogger.log('fetch-ok', scope: 'api/web-search');
|
||||
|
||||
return response.data as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint('DEBUG: Web search API error: $e');
|
||||
if (e is DioException) {
|
||||
DebugLogger.error(
|
||||
'Web search error response available (truncated for security)',
|
||||
);
|
||||
DebugLogger.error('error-response', scope: 'api/web-search', error: e);
|
||||
debugPrint('DEBUG: Web search error status: ${e.response?.statusCode}');
|
||||
}
|
||||
rethrow;
|
||||
@@ -1810,7 +1963,7 @@ class ApiService {
|
||||
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
final modelData = response.data as Map<String, dynamic>;
|
||||
DebugLogger.log('Model details for $modelId retrieved successfully');
|
||||
DebugLogger.log('details', scope: 'api/models', data: {'id': modelId});
|
||||
return modelData;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1911,7 +2064,11 @@ class ApiService {
|
||||
debugPrint(
|
||||
'DEBUG: Collection query response type: ${response.data.runtimeType}',
|
||||
);
|
||||
DebugLogger.log('Collection query response received successfully');
|
||||
DebugLogger.log(
|
||||
'query-ok',
|
||||
scope: 'api/collection',
|
||||
data: {'name': collectionName},
|
||||
);
|
||||
|
||||
if (response.data is List) {
|
||||
return response.data as List<dynamic>;
|
||||
@@ -1951,7 +2108,7 @@ class ApiService {
|
||||
debugPrint(
|
||||
'DEBUG: Retrieval config response status: ${response.statusCode}',
|
||||
);
|
||||
DebugLogger.log('Retrieval config response received successfully');
|
||||
DebugLogger.log('config-ok', scope: 'api/retrieval');
|
||||
|
||||
return response.data as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
@@ -2036,8 +2193,10 @@ class ApiService {
|
||||
} on DioException catch (e) {
|
||||
debugPrint('DEBUG: images/generations failed: ${e.response?.statusCode}');
|
||||
DebugLogger.error(
|
||||
'Image generation request to /api/v1/images/generations failed',
|
||||
e,
|
||||
'images-generate-failed',
|
||||
scope: 'api/images',
|
||||
error: e,
|
||||
data: {'status': e.response?.statusCode},
|
||||
);
|
||||
// Do not attempt singular fallback here - surface the original error
|
||||
rethrow;
|
||||
@@ -3325,8 +3484,12 @@ class ApiService {
|
||||
debugPrint('DEBUG: Uploading to /api/v1/files/');
|
||||
final response = await _dio.post('/api/v1/files/', data: formData);
|
||||
|
||||
DebugLogger.log('Upload response status: ${response.statusCode}');
|
||||
DebugLogger.log('Upload response received successfully');
|
||||
DebugLogger.log(
|
||||
'upload-status',
|
||||
scope: 'api/files',
|
||||
data: {'code': response.statusCode},
|
||||
);
|
||||
DebugLogger.log('upload-ok', scope: 'api/files');
|
||||
|
||||
if (response.data is Map && response.data['id'] != null) {
|
||||
final fileId = response.data['id'] as String;
|
||||
@@ -3336,7 +3499,7 @@ class ApiService {
|
||||
throw Exception('Invalid response format: missing file ID');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('ERROR: File upload failed: $e');
|
||||
DebugLogger.error('upload-failed', scope: 'api/files', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -3370,17 +3533,35 @@ class ApiService {
|
||||
debugPrint('Testing endpoint: $endpoint');
|
||||
final response = await _dio.get(endpoint);
|
||||
debugPrint('✅ $endpoint - Status: ${response.statusCode}');
|
||||
DebugLogger.log(' Response type: ${response.data.runtimeType}');
|
||||
DebugLogger.log(
|
||||
'response-type',
|
||||
scope: 'api/diagnostics',
|
||||
data: {'endpoint': endpoint, 'type': response.data.runtimeType},
|
||||
);
|
||||
if (response.data is List) {
|
||||
DebugLogger.log(' Array length: ${(response.data as List).length}');
|
||||
DebugLogger.log(
|
||||
'array-length',
|
||||
scope: 'api/diagnostics',
|
||||
data: {
|
||||
'endpoint': endpoint,
|
||||
'count': (response.data as List).length,
|
||||
},
|
||||
);
|
||||
} else if (response.data is Map) {
|
||||
DebugLogger.log(' Object keys: ${(response.data as Map).keys}');
|
||||
DebugLogger.log(
|
||||
'object-keys',
|
||||
scope: 'api/diagnostics',
|
||||
data: {
|
||||
'endpoint': endpoint,
|
||||
'keys': (response.data as Map).keys.take(5).toList(),
|
||||
},
|
||||
);
|
||||
}
|
||||
final dataSampleSource = response.data.toString();
|
||||
final dataPreview = dataSampleSource.length > 200
|
||||
? dataSampleSource.substring(0, 200)
|
||||
: dataSampleSource;
|
||||
DebugLogger.log(' Sample data: $dataPreview...');
|
||||
DebugLogger.log(
|
||||
'sample',
|
||||
scope: 'api/diagnostics',
|
||||
data: {'endpoint': endpoint, 'preview': response.data.toString()},
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ $endpoint - Error: $e');
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'attachments/queue');
|
||||
}
|
||||
|
||||
/// Status of a queued attachment upload
|
||||
enum QueuedAttachmentStatus { pending, uploading, completed, failed, cancelled }
|
||||
|
||||
@@ -42,7 +42,9 @@ class BackgroundStreamingHandler {
|
||||
final String reason = args['reason'] as String;
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Streams suspending - $streamIds (reason: $reason)',
|
||||
'suspending',
|
||||
scope: 'background',
|
||||
data: {'count': streamIds.length, 'reason': reason},
|
||||
);
|
||||
onStreamsSuspending?.call(streamIds);
|
||||
|
||||
@@ -51,7 +53,7 @@ class BackgroundStreamingHandler {
|
||||
break;
|
||||
|
||||
case 'backgroundTaskExpiring':
|
||||
DebugLogger.stream('Background: Background task expiring');
|
||||
DebugLogger.stream('task-expiring', scope: 'background');
|
||||
onBackgroundTaskExpiring?.call();
|
||||
break;
|
||||
}
|
||||
@@ -70,10 +72,17 @@ class BackgroundStreamingHandler {
|
||||
});
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Started background execution for ${streamIds.length} streams',
|
||||
'start',
|
||||
scope: 'background',
|
||||
data: {'count': streamIds.length},
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Background: Failed to start background execution', e);
|
||||
DebugLogger.error(
|
||||
'start-failed',
|
||||
scope: 'background',
|
||||
error: e,
|
||||
data: {'count': streamIds.length},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,10 +99,17 @@ class BackgroundStreamingHandler {
|
||||
});
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Stopped background execution for ${streamIds.length} streams',
|
||||
'stop',
|
||||
scope: 'background',
|
||||
data: {'count': streamIds.length},
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Background: Failed to stop background execution', e);
|
||||
DebugLogger.error(
|
||||
'stop-failed',
|
||||
scope: 'background',
|
||||
error: e,
|
||||
data: {'count': streamIds.length},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +172,7 @@ class BackgroundStreamingHandler {
|
||||
try {
|
||||
await _channel.invokeMethod('keepAlive');
|
||||
} catch (e) {
|
||||
DebugLogger.error('Background: Failed to keep alive', e);
|
||||
DebugLogger.error('keepalive-failed', scope: 'background', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,11 +197,13 @@ class BackgroundStreamingHandler {
|
||||
}
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Recovered ${recovered.length} stream states',
|
||||
'recovered',
|
||||
scope: 'background',
|
||||
data: {'count': recovered.length},
|
||||
);
|
||||
return recovered;
|
||||
} catch (e) {
|
||||
DebugLogger.error('Background: Failed to recover stream states', e);
|
||||
DebugLogger.error('recover-failed', scope: 'background', error: e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -207,7 +225,12 @@ class BackgroundStreamingHandler {
|
||||
'reason': reason,
|
||||
});
|
||||
} catch (e) {
|
||||
DebugLogger.error('Background: Failed to save stream states', e);
|
||||
DebugLogger.error(
|
||||
'save-states-failed',
|
||||
scope: 'background',
|
||||
error: e,
|
||||
data: {'count': streamIds.length, 'reason': reason},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +313,7 @@ class StreamState {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to parse StreamState from map', e);
|
||||
DebugLogger.error('parse-failed', scope: 'background', error: e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'secure_credential_storage.dart';
|
||||
import '../models/server_config.dart';
|
||||
import '../models/conversation.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'storage/optimized');
|
||||
}
|
||||
|
||||
/// Optimized storage service with single secure storage implementation
|
||||
/// Eliminates dual storage overhead and improves performance
|
||||
|
||||
@@ -312,8 +312,10 @@ class PersistentStreamingService with WidgetsBindingObserver {
|
||||
_retryAttempts.remove(streamId);
|
||||
} catch (e) {
|
||||
DebugLogger.error(
|
||||
'PersistentStreaming: Failed to recover stream $streamId',
|
||||
e,
|
||||
'recover-failed',
|
||||
scope: 'streaming/persistent',
|
||||
error: e,
|
||||
data: {'streamId': streamId},
|
||||
);
|
||||
|
||||
// Schedule next retry if under limit
|
||||
@@ -405,11 +407,12 @@ class PersistentStreamingService with WidgetsBindingObserver {
|
||||
void _enableWakeLock() async {
|
||||
try {
|
||||
await WakelockPlus.enable();
|
||||
DebugLogger.stream('PersistentStreamingService: Wake lock enabled');
|
||||
DebugLogger.stream('wake-lock-enabled', scope: 'streaming/persistent');
|
||||
} catch (e) {
|
||||
DebugLogger.error(
|
||||
'PersistentStreamingService: Failed to enable wake lock',
|
||||
e,
|
||||
'wake-lock-enable-failed',
|
||||
scope: 'streaming/persistent',
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -417,11 +420,12 @@ class PersistentStreamingService with WidgetsBindingObserver {
|
||||
void _disableWakeLock() async {
|
||||
try {
|
||||
await WakelockPlus.disable();
|
||||
DebugLogger.stream('PersistentStreamingService: Wake lock disabled');
|
||||
DebugLogger.stream('wake-lock-disabled', scope: 'streaming/persistent');
|
||||
} catch (e) {
|
||||
DebugLogger.error(
|
||||
'PersistentStreamingService: Failed to disable wake lock',
|
||||
e,
|
||||
'wake-lock-disable-failed',
|
||||
scope: 'streaming/persistent',
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,13 @@ class SecureCredentialStorage {
|
||||
);
|
||||
}
|
||||
|
||||
DebugLogger.storage('Credentials saved and verified securely');
|
||||
DebugLogger.storage(
|
||||
'save-ok',
|
||||
scope: 'credentials',
|
||||
data: {'version': '2.0'},
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to save credentials', e);
|
||||
DebugLogger.error('save-failed', scope: 'credentials', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +96,7 @@ class SecureCredentialStorage {
|
||||
final decoded = jsonDecode(jsonString);
|
||||
|
||||
if (decoded is! Map<String, dynamic>) {
|
||||
DebugLogger.warning('Invalid credentials format');
|
||||
DebugLogger.warning('invalid-format', scope: 'credentials');
|
||||
await deleteSavedCredentials();
|
||||
return null;
|
||||
}
|
||||
@@ -104,7 +108,9 @@ class SecureCredentialStorage {
|
||||
|
||||
if (savedDeviceId != currentDeviceId) {
|
||||
DebugLogger.info(
|
||||
'Device fingerprint changed, but allowing credential access for better UX',
|
||||
'fingerprint-mismatch-allowed',
|
||||
scope: 'credentials',
|
||||
data: {'previous': savedDeviceId, 'current': currentDeviceId},
|
||||
);
|
||||
// Don't clear credentials immediately - allow the user to continue
|
||||
// They can re-login if needed, which will update the fingerprint
|
||||
@@ -115,9 +121,7 @@ class SecureCredentialStorage {
|
||||
if (!decoded.containsKey('serverId') ||
|
||||
!decoded.containsKey('username') ||
|
||||
!decoded.containsKey('password')) {
|
||||
DebugLogger.warning(
|
||||
'Invalid saved credentials format - missing required fields',
|
||||
);
|
||||
DebugLogger.warning('missing-fields', scope: 'credentials');
|
||||
await deleteSavedCredentials();
|
||||
return null;
|
||||
}
|
||||
@@ -133,11 +137,17 @@ class SecureCredentialStorage {
|
||||
// Warn if credentials are very old (but don't delete them)
|
||||
if (daysSinceCreated > 90) {
|
||||
DebugLogger.info(
|
||||
'Saved credentials are $daysSinceCreated days old',
|
||||
'credentials-old',
|
||||
scope: 'credentials',
|
||||
data: {'ageDays': daysSinceCreated},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
DebugLogger.warning('Could not parse savedAt timestamp: $e');
|
||||
DebugLogger.warning(
|
||||
'savedat-parse-failed',
|
||||
scope: 'credentials',
|
||||
data: {'raw': savedAt, 'error': e.toString()},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +158,7 @@ class SecureCredentialStorage {
|
||||
'savedAt': decoded['savedAt']?.toString() ?? '',
|
||||
};
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to retrieve credentials', e);
|
||||
DebugLogger.error('read-failed', scope: 'credentials', error: e);
|
||||
// Don't delete credentials on retrieval errors - they might be recoverable
|
||||
return null;
|
||||
}
|
||||
@@ -158,9 +168,9 @@ class SecureCredentialStorage {
|
||||
Future<void> deleteSavedCredentials() async {
|
||||
try {
|
||||
await _secureStorage.delete(key: _credentialsKey);
|
||||
DebugLogger.storage('Credentials deleted');
|
||||
DebugLogger.storage('delete-ok', scope: 'credentials');
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to delete credentials', e);
|
||||
DebugLogger.error('delete-failed', scope: 'credentials', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +180,11 @@ class SecureCredentialStorage {
|
||||
final encryptedToken = await _encryptData(token);
|
||||
await _secureStorage.write(key: _authTokenKey, value: encryptedToken);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to save auth token', e);
|
||||
DebugLogger.error(
|
||||
'save-token-failed',
|
||||
scope: 'credentials/token',
|
||||
error: e,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -183,7 +197,11 @@ class SecureCredentialStorage {
|
||||
|
||||
return await _decryptData(encryptedToken);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to retrieve auth token', e);
|
||||
DebugLogger.error(
|
||||
'read-token-failed',
|
||||
scope: 'credentials/token',
|
||||
error: e,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -193,7 +211,11 @@ class SecureCredentialStorage {
|
||||
try {
|
||||
await _secureStorage.delete(key: _authTokenKey);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to delete auth token', e);
|
||||
DebugLogger.error(
|
||||
'delete-token-failed',
|
||||
scope: 'credentials/token',
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +228,11 @@ class SecureCredentialStorage {
|
||||
value: encryptedConfigs,
|
||||
);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to save server configs', e);
|
||||
DebugLogger.error(
|
||||
'save-configs-failed',
|
||||
scope: 'credentials/server-configs',
|
||||
error: e,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +247,11 @@ class SecureCredentialStorage {
|
||||
|
||||
return await _decryptData(encryptedConfigs);
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to retrieve server configs', e);
|
||||
DebugLogger.error(
|
||||
'read-configs-failed',
|
||||
scope: 'credentials/server-configs',
|
||||
error: e,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +269,11 @@ class SecureCredentialStorage {
|
||||
|
||||
return result == testValue;
|
||||
} catch (e) {
|
||||
DebugLogger.warning('Secure storage not available: $e');
|
||||
DebugLogger.warning(
|
||||
'storage-unavailable',
|
||||
scope: 'credentials/health',
|
||||
data: {'error': e.toString()},
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -248,9 +282,9 @@ class SecureCredentialStorage {
|
||||
Future<void> clearAll() async {
|
||||
try {
|
||||
await _secureStorage.deleteAll();
|
||||
DebugLogger.storage('All secure data cleared');
|
||||
DebugLogger.storage('clear-ok', scope: 'credentials');
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to clear secure data', e);
|
||||
DebugLogger.error('clear-failed', scope: 'credentials', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +295,11 @@ class SecureCredentialStorage {
|
||||
// In a more advanced implementation, you could add an additional layer of AES encryption
|
||||
return data;
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to encrypt data', e);
|
||||
DebugLogger.error(
|
||||
'encrypt-failed',
|
||||
scope: 'credentials/crypto',
|
||||
error: e,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -273,7 +311,11 @@ class SecureCredentialStorage {
|
||||
// This matches the encryption method above
|
||||
return encryptedData;
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to decrypt data', e);
|
||||
DebugLogger.error(
|
||||
'decrypt-failed',
|
||||
scope: 'credentials/crypto',
|
||||
error: e,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -297,7 +339,11 @@ class SecureCredentialStorage {
|
||||
|
||||
return digest.toString();
|
||||
} catch (e) {
|
||||
DebugLogger.warning('Failed to generate device fingerprint: $e');
|
||||
DebugLogger.warning(
|
||||
'fingerprint-failed',
|
||||
scope: 'credentials',
|
||||
data: {'error': e.toString()},
|
||||
);
|
||||
// Return a consistent fallback fingerprint
|
||||
return 'stable_fallback_device_id';
|
||||
}
|
||||
@@ -315,11 +361,9 @@ class SecureCredentialStorage {
|
||||
username: oldCredentials['username'] ?? '',
|
||||
password: oldCredentials['password'] ?? '',
|
||||
);
|
||||
DebugLogger.storage(
|
||||
'Successfully migrated credentials to new secure format',
|
||||
);
|
||||
DebugLogger.storage('migrate-ok', scope: 'credentials');
|
||||
} catch (e) {
|
||||
DebugLogger.error('Failed to migrate credentials', e);
|
||||
DebugLogger.error('migrate-failed', scope: 'credentials', error: e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/foundation.dart' hide debugPrint;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:share_handler/share_handler.dart' as sh;
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../core/providers/app_providers.dart';
|
||||
import '../../shared/services/tasks/task_queue.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'navigation_service.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
// Server chat creation/title generation occur on first send via chat providers
|
||||
|
||||
/// Lightweight payload for a share event
|
||||
@@ -199,3 +200,8 @@ Future<void> _processPayload(Ref ref, SharedPayload payload) async {
|
||||
debugPrint('ShareReceiver: failed to process payload: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'share');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import 'package:socket_io_client/socket_io_client.dart' as io;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/server_config.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'socket');
|
||||
}
|
||||
|
||||
class SocketService {
|
||||
final ServerConfig serverConfig;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/server_config.dart';
|
||||
import '../models/conversation.dart';
|
||||
import 'secure_credential_storage.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'storage');
|
||||
}
|
||||
|
||||
class StorageService {
|
||||
final FlutterSecureStorage _secureStorage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide debugPrint;
|
||||
|
||||
import '../../core/models/chat_message.dart';
|
||||
import '../../core/services/persistent_streaming_service.dart';
|
||||
@@ -12,6 +12,12 @@ import '../../core/utils/tool_calls_parser.dart';
|
||||
import 'navigation_service.dart';
|
||||
import '../../shared/widgets/themed_dialogs.dart';
|
||||
import '../../shared/theme/theme_extensions.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'streaming/helper');
|
||||
}
|
||||
|
||||
// Keep local verbosity toggle for socket logs
|
||||
const bool kSocketVerboseLogging = false;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/foundation.dart' hide debugPrint;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import '../../shared/theme/theme_extensions.dart';
|
||||
import 'navigation_service.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
void debugPrint(String? message, {int? wrapWidth}) {
|
||||
if (message == null) return;
|
||||
DebugLogger.fromLegacy(message, scope: 'errors/user-friendly');
|
||||
}
|
||||
|
||||
/// User-friendly error messages and recovery actions
|
||||
class UserFriendlyErrorHandler {
|
||||
|
||||
Reference in New Issue
Block a user