feat: implement service failure handling in background streaming

- Added a method to send failure notifications to Flutter when the background service fails to enter the foreground.
- Implemented a broadcast receiver to handle service failure notifications and notify Flutter about the failure.
- Enhanced the persistent streaming service to attempt recovery for failed streams.
- Introduced heartbeat monitoring for SSE streams to detect stale connections and trigger recovery actions.
This commit is contained in:
cogwheel0
2025-10-28 13:59:17 +05:30
parent 81eb38dc52
commit 7fb199b2e4
7 changed files with 265 additions and 25 deletions

View File

@@ -16,6 +16,7 @@ import '../../shared/theme/theme_extensions.dart';
import '../utils/debug_logger.dart';
import '../utils/openwebui_source_parser.dart';
import 'streaming_response_controller.dart';
import 'api_service.dart';
// Keep local verbosity toggle for socket logs
const bool kSocketVerboseLogging = false;
@@ -67,7 +68,7 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
required Map<String, dynamic> modelItem,
required String sessionId,
required String? activeConversationId,
required dynamic api,
required ApiService api,
required SocketService? socketService,
RegisterConversationDeltaListener? registerDeltaListener,
// Message update callbacks
@@ -169,6 +170,7 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
'modelId': modelId,
},
);
api.registerPersistentStreamForMessage(assistantMessageId, streamId);
InactivityWatchdog? socketWatchdog;
final socketSubscriptions = <VoidCallback>[];
@@ -318,8 +320,6 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
if (chatId == null || chatId.isEmpty) {
return;
}
if (api == null) return;
refreshingSnapshot = true;
try {
final conversation = await api.getConversation(chatId);
@@ -376,7 +376,7 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
try {
// Fire and forget
// ignore: unawaited_futures
api?.sendChatCompleted(
api.sendChatCompleted(
chatId: activeConversationId ?? '',
messageId: assistantMessageId,
messages: const [],
@@ -397,7 +397,7 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
} catch (_) {}
try {
// ignore: unawaited_futures
api?.sendChatCompleted(
api.sendChatCompleted(
chatId: activeConversationId ?? '',
messageId: assistantMessageId,
messages: const [],
@@ -594,7 +594,7 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
if (payload['done'] == true) {
try {
// ignore: unawaited_futures
api?.sendChatCompleted(
api.sendChatCompleted(
chatId: activeConversationId ?? '',
messageId: assistantMessageId,
messages: const [],
@@ -614,8 +614,8 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
try {
final chatId = activeConversationId;
if (chatId != null && chatId.isNotEmpty) {
final resp = await api?.dio.get('/api/v1/chats/$chatId');
final data = resp?.data as Map<String, dynamic>?;
final resp = await api.dio.get('/api/v1/chats/$chatId');
final data = resp.data as Map<String, dynamic>?;
String content = '';
final chatObj = data?['chat'] as Map<String, dynamic>?;
if (chatObj != null) {
@@ -1137,6 +1137,10 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
}
},
onComplete: () {
api.clearPersistentStreamForMessage(
assistantMessageId,
expectedStreamId: streamId,
);
// Unregister from persistent service
persistentService.unregisterStream(streamId);
@@ -1159,6 +1163,10 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
},
);
api.clearPersistentStreamForMessage(
assistantMessageId,
expectedStreamId: streamId,
);
try {
persistentService.unregisterStream(streamId);
} catch (_) {}