chore: formatting
This commit is contained in:
@@ -97,8 +97,12 @@ class ApiService {
|
|||||||
final dataMap = options.data as Map<String, dynamic>;
|
final dataMap = options.data as Map<String, dynamic>;
|
||||||
debugPrint('Data type: Map');
|
debugPrint('Data type: Map');
|
||||||
debugPrint('Data keys: ${dataMap.keys.toList()}');
|
debugPrint('Data keys: ${dataMap.keys.toList()}');
|
||||||
debugPrint('Has background_tasks: ${dataMap.containsKey('background_tasks')}');
|
debugPrint(
|
||||||
debugPrint('Has session_id: ${dataMap.containsKey('session_id')}');
|
'Has background_tasks: ${dataMap.containsKey('background_tasks')}',
|
||||||
|
);
|
||||||
|
debugPrint(
|
||||||
|
'Has session_id: ${dataMap.containsKey('session_id')}',
|
||||||
|
);
|
||||||
debugPrint('Has id: ${dataMap.containsKey('id')}');
|
debugPrint('Has id: ${dataMap.containsKey('id')}');
|
||||||
debugPrint('Full data: ${jsonEncode(dataMap)}');
|
debugPrint('Full data: ${jsonEncode(dataMap)}');
|
||||||
} else {
|
} else {
|
||||||
@@ -338,11 +342,15 @@ class ApiService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data is! List) {
|
if (response.data is! List) {
|
||||||
throw Exception('Expected array of chats, got ${response.data.runtimeType}');
|
throw Exception(
|
||||||
|
'Expected array of chats, got ${response.data.runtimeType}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final pageChats = response.data as List;
|
final pageChats = response.data as List;
|
||||||
debugPrint('DEBUG: Page $currentPage returned ${pageChats.length} conversations');
|
debugPrint(
|
||||||
|
'DEBUG: Page $currentPage returned ${pageChats.length} conversations',
|
||||||
|
);
|
||||||
|
|
||||||
if (pageChats.isEmpty) {
|
if (pageChats.isEmpty) {
|
||||||
debugPrint('DEBUG: No more conversations, stopping pagination');
|
debugPrint('DEBUG: No more conversations, stopping pagination');
|
||||||
@@ -354,24 +362,27 @@ class ApiService {
|
|||||||
|
|
||||||
// Safety break to avoid infinite loops (adjust as needed)
|
// Safety break to avoid infinite loops (adjust as needed)
|
||||||
if (currentPage > 100) {
|
if (currentPage > 100) {
|
||||||
debugPrint('WARNING: Reached maximum page limit (100), stopping pagination');
|
debugPrint(
|
||||||
|
'WARNING: Reached maximum page limit (100), stopping pagination',
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint('DEBUG: Fetched total of ${allRegularChats.length} conversations across $currentPage pages');
|
debugPrint(
|
||||||
|
'DEBUG: Fetched total of ${allRegularChats.length} conversations across $currentPage pages',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Original single page fetch
|
// Original single page fetch
|
||||||
final regularResponse = await _dio.get(
|
final regularResponse = await _dio.get(
|
||||||
'/api/v1/chats/',
|
'/api/v1/chats/',
|
||||||
queryParameters: {
|
queryParameters: {if (limit > 0) 'page': ((skip ?? 0) / limit).floor()},
|
||||||
if (limit > 0)
|
|
||||||
'page': ((skip ?? 0) / limit).floor(),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (regularResponse.data is! List) {
|
if (regularResponse.data is! List) {
|
||||||
throw Exception('Expected array of chats, got ${regularResponse.data.runtimeType}');
|
throw Exception(
|
||||||
|
'Expected array of chats, got ${regularResponse.data.runtimeType}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
allRegularChats = regularResponse.data as List;
|
allRegularChats = regularResponse.data as List;
|
||||||
@@ -381,7 +392,9 @@ class ApiService {
|
|||||||
final archivedResponse = await _dio.get('/api/v1/chats/all/archived');
|
final archivedResponse = await _dio.get('/api/v1/chats/all/archived');
|
||||||
|
|
||||||
debugPrint('DEBUG: Pinned response status: ${pinnedResponse.statusCode}');
|
debugPrint('DEBUG: Pinned response status: ${pinnedResponse.statusCode}');
|
||||||
debugPrint('DEBUG: Archived response status: ${archivedResponse.statusCode}');
|
debugPrint(
|
||||||
|
'DEBUG: Archived response status: ${archivedResponse.statusCode}',
|
||||||
|
);
|
||||||
|
|
||||||
if (pinnedResponse.data is! List) {
|
if (pinnedResponse.data is! List) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
@@ -438,14 +451,21 @@ class ApiService {
|
|||||||
for (final chatData in regularChatList) {
|
for (final chatData in regularChatList) {
|
||||||
try {
|
try {
|
||||||
// Debug: Check if conversation has folder_id in raw data
|
// Debug: Check if conversation has folder_id in raw data
|
||||||
if (chatData.containsKey('folder_id') && chatData['folder_id'] != null) {
|
if (chatData.containsKey('folder_id') &&
|
||||||
debugPrint('🔍 DEBUG: Found conversation with folder_id in raw data: ${chatData['id']} -> ${chatData['folder_id']}');
|
chatData['folder_id'] != null) {
|
||||||
|
debugPrint(
|
||||||
|
'🔍 DEBUG: Found conversation with folder_id in raw data: ${chatData['id']} -> ${chatData['folder_id']}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Check what fields are available in the chat data
|
// Debug: Check what fields are available in the chat data
|
||||||
if (regularChatList.indexOf(chatData) == 0) {
|
if (regularChatList.indexOf(chatData) == 0) {
|
||||||
debugPrint('🔍 DEBUG: Sample chat data fields: ${chatData.keys.toList()}');
|
debugPrint(
|
||||||
debugPrint('🔍 DEBUG: Sample chat data: ${chatData.toString().substring(0, 200)}...');
|
'🔍 DEBUG: Sample chat data fields: ${chatData.keys.toList()}',
|
||||||
|
);
|
||||||
|
debugPrint(
|
||||||
|
'🔍 DEBUG: Sample chat data: ${chatData.toString().substring(0, 200)}...',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final conversation = _parseOpenWebUIChat(chatData);
|
final conversation = _parseOpenWebUIChat(chatData);
|
||||||
@@ -524,7 +544,9 @@ class ApiService {
|
|||||||
|
|
||||||
// Debug logging for folder assignment
|
// Debug logging for folder assignment
|
||||||
if (folderId != null) {
|
if (folderId != null) {
|
||||||
debugPrint('🔍 DEBUG: Conversation ${id.substring(0, 8)} has folderId: $folderId');
|
debugPrint(
|
||||||
|
'🔍 DEBUG: Conversation ${id.substring(0, 8)} has folderId: $folderId',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint(
|
debugPrint(
|
||||||
@@ -614,7 +636,9 @@ class ApiService {
|
|||||||
// Convert map to list format to use common parsing logic
|
// Convert map to list format to use common parsing logic
|
||||||
messagesList = [];
|
messagesList = [];
|
||||||
for (final entry in messagesMap.entries) {
|
for (final entry in messagesMap.entries) {
|
||||||
final msgData = Map<String, dynamic>.from(entry.value as Map<String, dynamic>);
|
final msgData = Map<String, dynamic>.from(
|
||||||
|
entry.value as Map<String, dynamic>,
|
||||||
|
);
|
||||||
msgData['id'] = entry.key; // Use the key as the message ID
|
msgData['id'] = entry.key; // Use the key as the message ID
|
||||||
messagesList.add(msgData);
|
messagesList.add(msgData);
|
||||||
}
|
}
|
||||||
@@ -793,7 +817,9 @@ class ApiService {
|
|||||||
|
|
||||||
final response = await _dio.post('/api/v1/chats/new', data: chatData);
|
final response = await _dio.post('/api/v1/chats/new', data: chatData);
|
||||||
|
|
||||||
debugPrint('DEBUG: Create conversation response status: ${response.statusCode}');
|
debugPrint(
|
||||||
|
'DEBUG: Create conversation response status: ${response.statusCode}',
|
||||||
|
);
|
||||||
debugPrint('DEBUG: Create conversation response data: ${response.data}');
|
debugPrint('DEBUG: Create conversation response data: ${response.data}');
|
||||||
|
|
||||||
// Parse the response
|
// Parse the response
|
||||||
@@ -831,7 +857,8 @@ class ApiService {
|
|||||||
'content': msg.content,
|
'content': msg.content,
|
||||||
'timestamp': msg.timestamp.millisecondsSinceEpoch ~/ 1000,
|
'timestamp': msg.timestamp.millisecondsSinceEpoch ~/ 1000,
|
||||||
if (msg.role == 'assistant' && msg.model != null) 'model': msg.model,
|
if (msg.role == 'assistant' && msg.model != null) 'model': msg.model,
|
||||||
if (msg.role == 'assistant' && msg.model != null) 'modelName': msg.model,
|
if (msg.role == 'assistant' && msg.model != null)
|
||||||
|
'modelName': msg.model,
|
||||||
if (msg.role == 'assistant') 'modelIdx': 0,
|
if (msg.role == 'assistant') 'modelIdx': 0,
|
||||||
if (msg.role == 'assistant') 'done': true,
|
if (msg.role == 'assistant') 'done': true,
|
||||||
if (msg.role == 'user' && model != null) 'models': [model],
|
if (msg.role == 'user' && model != null) 'models': [model],
|
||||||
@@ -853,7 +880,8 @@ class ApiService {
|
|||||||
'content': msg.content,
|
'content': msg.content,
|
||||||
'timestamp': msg.timestamp.millisecondsSinceEpoch ~/ 1000,
|
'timestamp': msg.timestamp.millisecondsSinceEpoch ~/ 1000,
|
||||||
if (msg.role == 'assistant' && msg.model != null) 'model': msg.model,
|
if (msg.role == 'assistant' && msg.model != null) 'model': msg.model,
|
||||||
if (msg.role == 'assistant' && msg.model != null) 'modelName': msg.model,
|
if (msg.role == 'assistant' && msg.model != null)
|
||||||
|
'modelName': msg.model,
|
||||||
if (msg.role == 'assistant') 'modelIdx': 0,
|
if (msg.role == 'assistant') 'modelIdx': 0,
|
||||||
if (msg.role == 'assistant') 'done': true,
|
if (msg.role == 'assistant') 'done': true,
|
||||||
if (msg.role == 'user' && model != null) 'models': [model],
|
if (msg.role == 'user' && model != null) 'models': [model],
|
||||||
@@ -868,7 +896,7 @@ class ApiService {
|
|||||||
// Create the chat data structure matching OpenWebUI format exactly
|
// Create the chat data structure matching OpenWebUI format exactly
|
||||||
final chatData = {
|
final chatData = {
|
||||||
'chat': {
|
'chat': {
|
||||||
if (title != null) 'title': title, // Include the title if provided
|
if (title != null) 'title': title, // Include the title if provided
|
||||||
'models': model != null ? [model] : [],
|
'models': model != null ? [model] : [],
|
||||||
'messages': messagesArray,
|
'messages': messagesArray,
|
||||||
'history': {
|
'history': {
|
||||||
@@ -950,8 +978,6 @@ class ApiService {
|
|||||||
await _dio.post('/api/v1/users/user/settings', data: settings);
|
await _dio.post('/api/v1/users/user/settings', data: settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Suggestions
|
// Suggestions
|
||||||
Future<List<String>> getSuggestions() async {
|
Future<List<String>> getSuggestions() async {
|
||||||
debugPrint('DEBUG: Fetching conversation suggestions');
|
debugPrint('DEBUG: Fetching conversation suggestions');
|
||||||
@@ -1496,7 +1522,9 @@ class ApiService {
|
|||||||
Map<String, dynamic>? modelItem,
|
Map<String, dynamic>? modelItem,
|
||||||
String? sessionId,
|
String? sessionId,
|
||||||
}) async {
|
}) async {
|
||||||
debugPrint('DEBUG: Sending chat completed notification (optional endpoint)');
|
debugPrint(
|
||||||
|
'DEBUG: Sending chat completed notification (optional endpoint)',
|
||||||
|
);
|
||||||
|
|
||||||
// This endpoint appears to be optional or deprecated in newer OpenWebUI versions
|
// This endpoint appears to be optional or deprecated in newer OpenWebUI versions
|
||||||
// The main chat synchronization happens through /api/v1/chats/{id} updates
|
// The main chat synchronization happens through /api/v1/chats/{id} updates
|
||||||
@@ -1509,7 +1537,8 @@ class ApiService {
|
|||||||
// Don't include 'id' - it causes 400 error with detail: 'id'
|
// Don't include 'id' - it causes 400 error with detail: 'id'
|
||||||
'role': msg['role'],
|
'role': msg['role'],
|
||||||
'content': msg['content'],
|
'content': msg['content'],
|
||||||
'timestamp': msg['timestamp'] ?? DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
'timestamp':
|
||||||
|
msg['timestamp'] ?? DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add model info for assistant messages
|
// Add model info for assistant messages
|
||||||
@@ -1525,11 +1554,12 @@ class ApiService {
|
|||||||
|
|
||||||
// Include the message ID and session ID at the top level - server expects these
|
// Include the message ID and session ID at the top level - server expects these
|
||||||
final requestData = {
|
final requestData = {
|
||||||
'id': messageId, // The server expects the assistant message ID here
|
'id': messageId, // The server expects the assistant message ID here
|
||||||
'chat_id': chatId,
|
'chat_id': chatId,
|
||||||
'model': model,
|
'model': model,
|
||||||
'messages': formattedMessages,
|
'messages': formattedMessages,
|
||||||
'session_id': sessionId ?? const Uuid().v4().substring(0, 20), // Add session_id
|
'session_id':
|
||||||
|
sessionId ?? const Uuid().v4().substring(0, 20), // Add session_id
|
||||||
// Don't include model_item as it might not be expected
|
// Don't include model_item as it might not be expected
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1541,7 +1571,9 @@ class ApiService {
|
|||||||
debugPrint('DEBUG: Chat completed response: ${response.statusCode}');
|
debugPrint('DEBUG: Chat completed response: ${response.statusCode}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// This is a non-critical endpoint - main sync happens via /api/v1/chats/{id}
|
// This is a non-critical endpoint - main sync happens via /api/v1/chats/{id}
|
||||||
debugPrint('DEBUG: Chat completed endpoint not available or failed (non-critical): $e');
|
debugPrint(
|
||||||
|
'DEBUG: Chat completed endpoint not available or failed (non-critical): $e',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2379,8 +2411,7 @@ class ApiService {
|
|||||||
|
|
||||||
// Send message with SSE streaming
|
// Send message with SSE streaming
|
||||||
// Returns a record with (stream, messageId, sessionId)
|
// Returns a record with (stream, messageId, sessionId)
|
||||||
({Stream<String> stream, String messageId, String sessionId})
|
({Stream<String> stream, String messageId, String sessionId}) sendMessage({
|
||||||
sendMessage({
|
|
||||||
required List<Map<String, dynamic>> messages,
|
required List<Map<String, dynamic>> messages,
|
||||||
required String model,
|
required String model,
|
||||||
String? conversationId,
|
String? conversationId,
|
||||||
@@ -2478,7 +2509,9 @@ class ApiService {
|
|||||||
|
|
||||||
// Debug the data being sent
|
// Debug the data being sent
|
||||||
debugPrint('DEBUG: SSE request data keys: ${data.keys.toList()}');
|
debugPrint('DEBUG: SSE request data keys: ${data.keys.toList()}');
|
||||||
debugPrint('DEBUG: Has background_tasks: ${data.containsKey('background_tasks')}');
|
debugPrint(
|
||||||
|
'DEBUG: Has background_tasks: ${data.containsKey('background_tasks')}',
|
||||||
|
);
|
||||||
debugPrint('DEBUG: Has session_id: ${data.containsKey('session_id')}');
|
debugPrint('DEBUG: Has session_id: ${data.containsKey('session_id')}');
|
||||||
debugPrint('DEBUG: background_tasks value: ${data['background_tasks']}');
|
debugPrint('DEBUG: background_tasks value: ${data['background_tasks']}');
|
||||||
debugPrint('DEBUG: session_id value: ${data['session_id']}');
|
debugPrint('DEBUG: session_id value: ${data['session_id']}');
|
||||||
@@ -2535,25 +2568,31 @@ class ApiService {
|
|||||||
String? persistentStreamId;
|
String? persistentStreamId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
debugPrint('DEBUG: Making SSE request with parser to /api/chat/completions');
|
debugPrint(
|
||||||
|
'DEBUG: Making SSE request with parser to /api/chat/completions',
|
||||||
|
);
|
||||||
|
|
||||||
// Create a fresh Dio instance optimized for SSE streaming
|
// Create a fresh Dio instance optimized for SSE streaming
|
||||||
final streamDio = Dio(BaseOptions(
|
final streamDio = Dio(
|
||||||
baseUrl: serverConfig.url,
|
BaseOptions(
|
||||||
connectTimeout: const Duration(seconds: 60), // Longer for initial connection
|
baseUrl: serverConfig.url,
|
||||||
receiveTimeout: null, // No timeout for streaming
|
connectTimeout: const Duration(
|
||||||
sendTimeout: const Duration(seconds: 30),
|
seconds: 60,
|
||||||
headers: {
|
), // Longer for initial connection
|
||||||
'Authorization': 'Bearer ${_authInterceptor.authToken}',
|
receiveTimeout: null, // No timeout for streaming
|
||||||
'Accept': 'text/event-stream',
|
sendTimeout: const Duration(seconds: 30),
|
||||||
'Cache-Control': 'no-cache',
|
headers: {
|
||||||
'Connection': 'keep-alive',
|
'Authorization': 'Bearer ${_authInterceptor.authToken}',
|
||||||
...serverConfig.customHeaders, // Include any custom headers
|
'Accept': 'text/event-stream',
|
||||||
},
|
'Cache-Control': 'no-cache',
|
||||||
validateStatus: (status) => status != null && status < 400,
|
'Connection': 'keep-alive',
|
||||||
followRedirects: true,
|
...serverConfig.customHeaders, // Include any custom headers
|
||||||
maxRedirects: 3,
|
},
|
||||||
));
|
validateStatus: (status) => status != null && status < 400,
|
||||||
|
followRedirects: true,
|
||||||
|
maxRedirects: 3,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
debugPrint('DEBUG: Sending SSE request with data: ${jsonEncode(data)}');
|
debugPrint('DEBUG: Sending SSE request with data: ${jsonEncode(data)}');
|
||||||
|
|
||||||
@@ -2568,17 +2607,23 @@ class ApiService {
|
|||||||
|
|
||||||
debugPrint('DEBUG: SSE response status: ${response.statusCode}');
|
debugPrint('DEBUG: SSE response status: ${response.statusCode}');
|
||||||
debugPrint('DEBUG: SSE response headers: ${response.headers}');
|
debugPrint('DEBUG: SSE response headers: ${response.headers}');
|
||||||
debugPrint('DEBUG: SSE content-type: ${response.headers.value('content-type')}');
|
debugPrint(
|
||||||
|
'DEBUG: SSE content-type: ${response.headers.value('content-type')}',
|
||||||
|
);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw Exception('HTTP ${response.statusCode}: Failed to start streaming');
|
throw Exception(
|
||||||
|
'HTTP ${response.statusCode}: Failed to start streaming',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we got SSE or JSON response
|
// Check if we got SSE or JSON response
|
||||||
final contentType = response.headers.value('content-type') ?? '';
|
final contentType = response.headers.value('content-type') ?? '';
|
||||||
if (!contentType.contains('text/event-stream')) {
|
if (!contentType.contains('text/event-stream')) {
|
||||||
debugPrint('WARNING: Expected SSE but got content-type: $contentType');
|
debugPrint('WARNING: Expected SSE but got content-type: $contentType');
|
||||||
debugPrint('WARNING: This usually means the server didn\'t receive the streaming parameters');
|
debugPrint(
|
||||||
|
'WARNING: This usually means the server didn\'t receive the streaming parameters',
|
||||||
|
);
|
||||||
|
|
||||||
// Try to read the response to see what we got
|
// Try to read the response to see what we got
|
||||||
final stream = response.data.stream as Stream<List<int>>;
|
final stream = response.data.stream as Stream<List<int>>;
|
||||||
@@ -2608,7 +2653,9 @@ class ApiService {
|
|||||||
final message = choice['message'] as Map<String, dynamic>;
|
final message = choice['message'] as Map<String, dynamic>;
|
||||||
final content = message['content']?.toString() ?? '';
|
final content = message['content']?.toString() ?? '';
|
||||||
if (content.isNotEmpty) {
|
if (content.isNotEmpty) {
|
||||||
debugPrint('DEBUG: Successfully extracted content from JSON response');
|
debugPrint(
|
||||||
|
'DEBUG: Successfully extracted content from JSON response',
|
||||||
|
);
|
||||||
// Stream the content word by word for better UX
|
// Stream the content word by word for better UX
|
||||||
final words = content.split(' ');
|
final words = content.split(' ');
|
||||||
for (final word in words) {
|
for (final word in words) {
|
||||||
@@ -2627,7 +2674,9 @@ class ApiService {
|
|||||||
|
|
||||||
// Check if it's a task-based response
|
// Check if it's a task-based response
|
||||||
if (json is Map && json.containsKey('task_id')) {
|
if (json is Map && json.containsKey('task_id')) {
|
||||||
debugPrint('DEBUG: Got task-based response with task_id: ${json['task_id']}');
|
debugPrint(
|
||||||
|
'DEBUG: Got task-based response with task_id: ${json['task_id']}',
|
||||||
|
);
|
||||||
debugPrint('DEBUG: Status: ${json['status']}');
|
debugPrint('DEBUG: Status: ${json['status']}');
|
||||||
// This might be a polling-based async pattern
|
// This might be a polling-based async pattern
|
||||||
// TODO: Implement polling for task completion
|
// TODO: Implement polling for task completion
|
||||||
@@ -2636,7 +2685,9 @@ class ApiService {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('ERROR: Failed to parse JSON response: $e');
|
debugPrint('ERROR: Failed to parse JSON response: $e');
|
||||||
// Try to show something to the user
|
// Try to show something to the user
|
||||||
streamController.add('Response received but could not be parsed properly.');
|
streamController.add(
|
||||||
|
'Response received but could not be parsed properly.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not JSON, might be plain text
|
// Not JSON, might be plain text
|
||||||
@@ -2662,7 +2713,9 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse SSE events with enhanced parser (includes heartbeat monitoring)
|
// Parse SSE events with enhanced parser (includes heartbeat monitoring)
|
||||||
final sseParser = SSEParser(heartbeatTimeout: const Duration(seconds: 45));
|
final sseParser = SSEParser(
|
||||||
|
heartbeatTimeout: const Duration(seconds: 45),
|
||||||
|
);
|
||||||
int contentIndex = 0;
|
int contentIndex = 0;
|
||||||
int chunkSequence = 0;
|
int chunkSequence = 0;
|
||||||
String accumulatedContent = '';
|
String accumulatedContent = '';
|
||||||
@@ -2673,7 +2726,9 @@ class ApiService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
sseParser.reconnectRequests.listen((lastEventId) {
|
sseParser.reconnectRequests.listen((lastEventId) {
|
||||||
debugPrint('Persistent: SSE reconnection requested, lastEventId: $lastEventId');
|
debugPrint(
|
||||||
|
'Persistent: SSE reconnection requested, lastEventId: $lastEventId',
|
||||||
|
);
|
||||||
// The persistent service will handle the reconnection
|
// The persistent service will handle the reconnection
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2705,8 +2760,11 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update recovery state
|
// Update recovery state
|
||||||
recoveryService.updateStreamProgress(streamId, event.data, contentIndex++);
|
recoveryService.updateStreamProgress(
|
||||||
|
streamId,
|
||||||
|
event.data,
|
||||||
|
contentIndex++,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Persistent: Error processing SSE event: $e');
|
debugPrint('Persistent: Error processing SSE event: $e');
|
||||||
streamController.addError(e);
|
streamController.addError(e);
|
||||||
@@ -2757,7 +2815,8 @@ class ApiService {
|
|||||||
streamController.addError(error);
|
streamController.addError(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancelOnError: false, // Continue processing despite individual event errors
|
cancelOnError:
|
||||||
|
false, // Continue processing despite individual event errors
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register with persistent streaming service now that subscription is created
|
// Register with persistent streaming service now that subscription is created
|
||||||
@@ -2775,7 +2834,6 @@ class ApiService {
|
|||||||
'requestData': data,
|
'requestData': data,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Persistent: Failed to create SSE stream: $e');
|
debugPrint('Persistent: Failed to create SSE stream: $e');
|
||||||
if (persistentStreamId != null) {
|
if (persistentStreamId != null) {
|
||||||
@@ -2802,7 +2860,9 @@ class ApiService {
|
|||||||
PersistentStreamingService persistentService,
|
PersistentStreamingService persistentService,
|
||||||
String persistentStreamId,
|
String persistentStreamId,
|
||||||
) {
|
) {
|
||||||
debugPrint('Persistent: SSE event - type: ${event.event}, data: ${event.data}');
|
debugPrint(
|
||||||
|
'Persistent: SSE event - type: ${event.event}, data: ${event.data}',
|
||||||
|
);
|
||||||
|
|
||||||
// Handle completion signal
|
// Handle completion signal
|
||||||
if (event.data == '[DONE]') {
|
if (event.data == '[DONE]') {
|
||||||
@@ -2824,109 +2884,112 @@ class ApiService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle content streaming
|
// Handle content streaming
|
||||||
if (json.containsKey('choices')) {
|
if (json.containsKey('choices')) {
|
||||||
final choices = json['choices'] as List?;
|
final choices = json['choices'] as List?;
|
||||||
if (choices != null && choices.isNotEmpty) {
|
if (choices != null && choices.isNotEmpty) {
|
||||||
final choice = choices[0] as Map<String, dynamic>;
|
final choice = choices[0] as Map<String, dynamic>;
|
||||||
|
|
||||||
if (choice.containsKey('delta')) {
|
if (choice.containsKey('delta')) {
|
||||||
final delta = choice['delta'] as Map<String, dynamic>;
|
final delta = choice['delta'] as Map<String, dynamic>;
|
||||||
|
|
||||||
// Extract content
|
// Extract content
|
||||||
if (delta.containsKey('content')) {
|
if (delta.containsKey('content')) {
|
||||||
final content = delta['content'] as String?;
|
final content = delta['content'] as String?;
|
||||||
if (content != null && content.isNotEmpty) {
|
if (content != null && content.isNotEmpty) {
|
||||||
debugPrint('Persistent: SSE content chunk: "$content"');
|
debugPrint('Persistent: SSE content chunk: "$content"');
|
||||||
|
|
||||||
// Add content to stream
|
// Add content to stream
|
||||||
if (!streamController.isClosed) {
|
if (!streamController.isClosed) {
|
||||||
streamController.add(content);
|
streamController.add(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update persistent service progress
|
// Update persistent service progress
|
||||||
persistentService.updateStreamProgress(
|
persistentService.updateStreamProgress(
|
||||||
persistentStreamId,
|
persistentStreamId,
|
||||||
chunkSequence: chunkSequence,
|
chunkSequence: chunkSequence,
|
||||||
appendedContent: content,
|
appendedContent: content,
|
||||||
);
|
);
|
||||||
|
|
||||||
accumulatedContent += content;
|
accumulatedContent += content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for completion in delta
|
// Check for completion in delta
|
||||||
if (delta.containsKey('finish_reason')) {
|
if (delta.containsKey('finish_reason')) {
|
||||||
final finishReason = delta['finish_reason'];
|
final finishReason = delta['finish_reason'];
|
||||||
debugPrint('Persistent: Stream finished with reason: $finishReason');
|
debugPrint(
|
||||||
if (!streamController.isClosed) {
|
'Persistent: Stream finished with reason: $finishReason',
|
||||||
streamController.close();
|
);
|
||||||
}
|
if (!streamController.isClosed) {
|
||||||
return;
|
streamController.close();
|
||||||
}
|
}
|
||||||
} else if (choice.containsKey('finish_reason')) {
|
return;
|
||||||
// Check for completion at choice level
|
}
|
||||||
final finishReason = choice['finish_reason'];
|
} else if (choice.containsKey('finish_reason')) {
|
||||||
if (finishReason != null) {
|
// Check for completion at choice level
|
||||||
debugPrint('Persistent: Stream finished with reason: $finishReason');
|
final finishReason = choice['finish_reason'];
|
||||||
if (!streamController.isClosed) {
|
if (finishReason != null) {
|
||||||
streamController.close();
|
debugPrint(
|
||||||
}
|
'Persistent: Stream finished with reason: $finishReason',
|
||||||
return;
|
);
|
||||||
}
|
if (!streamController.isClosed) {
|
||||||
}
|
streamController.close();
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
// Handle streaming chat/completions format variations
|
}
|
||||||
if (json.containsKey('delta')) {
|
}
|
||||||
final delta = json['delta'] as Map<String, dynamic>;
|
|
||||||
if (delta.containsKey('content')) {
|
|
||||||
final content = delta['content'] as String?;
|
|
||||||
if (content != null && content.isNotEmpty) {
|
|
||||||
debugPrint('Persistent: Direct delta content: "$content"');
|
|
||||||
|
|
||||||
if (!streamController.isClosed) {
|
|
||||||
streamController.add(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
persistentService.updateStreamProgress(
|
|
||||||
persistentStreamId,
|
|
||||||
chunkSequence: chunkSequence,
|
|
||||||
appendedContent: content,
|
|
||||||
);
|
|
||||||
|
|
||||||
accumulatedContent += content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle OpenRouter-style streaming
|
|
||||||
if (json.containsKey('message')) {
|
|
||||||
final message = json['message'] as Map<String, dynamic>;
|
|
||||||
if (message.containsKey('content')) {
|
|
||||||
final content = message['content'] as String?;
|
|
||||||
if (content != null && content.isNotEmpty) {
|
|
||||||
debugPrint('Persistent: Message content: "$content"');
|
|
||||||
|
|
||||||
if (!streamController.isClosed) {
|
|
||||||
streamController.add(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
persistentService.updateStreamProgress(
|
|
||||||
persistentStreamId,
|
|
||||||
chunkSequence: chunkSequence,
|
|
||||||
content: content, // Full content, not appended
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Persistent: Error parsing SSE event data: $e');
|
|
||||||
// Don't fail the entire stream for one bad event
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle streaming chat/completions format variations
|
||||||
|
if (json.containsKey('delta')) {
|
||||||
|
final delta = json['delta'] as Map<String, dynamic>;
|
||||||
|
if (delta.containsKey('content')) {
|
||||||
|
final content = delta['content'] as String?;
|
||||||
|
if (content != null && content.isNotEmpty) {
|
||||||
|
debugPrint('Persistent: Direct delta content: "$content"');
|
||||||
|
|
||||||
|
if (!streamController.isClosed) {
|
||||||
|
streamController.add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
persistentService.updateStreamProgress(
|
||||||
|
persistentStreamId,
|
||||||
|
chunkSequence: chunkSequence,
|
||||||
|
appendedContent: content,
|
||||||
|
);
|
||||||
|
|
||||||
|
accumulatedContent += content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle OpenRouter-style streaming
|
||||||
|
if (json.containsKey('message')) {
|
||||||
|
final message = json['message'] as Map<String, dynamic>;
|
||||||
|
if (message.containsKey('content')) {
|
||||||
|
final content = message['content'] as String?;
|
||||||
|
if (content != null && content.isNotEmpty) {
|
||||||
|
debugPrint('Persistent: Message content: "$content"');
|
||||||
|
|
||||||
|
if (!streamController.isClosed) {
|
||||||
|
streamController.add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
persistentService.updateStreamProgress(
|
||||||
|
persistentStreamId,
|
||||||
|
chunkSequence: chunkSequence,
|
||||||
|
content: content, // Full content, not appended
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Persistent: Error parsing SSE event data: $e');
|
||||||
|
// Don't fail the entire stream for one bad event
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy Socket.IO and older SSE methods removed
|
// Legacy Socket.IO and older SSE methods removed
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user