feat: task based send flow

This commit is contained in:
cogwheel0
2025-09-01 23:41:22 +05:30
parent 53e16237df
commit 1b65743b06
6 changed files with 445 additions and 33 deletions

View File

@@ -13,6 +13,7 @@ import '../../../core/utils/stream_chunker.dart';
import '../../../core/services/persistent_streaming_service.dart';
import '../../../core/utils/debug_logger.dart';
import '../services/reviewer_mode_service.dart';
import '../../../shared/services/tasks/task_queue.dart';
const bool kSocketVerboseLogging = false;
@@ -1044,6 +1045,11 @@ Future<void> _sendMessageInternal(
'follow_up_generation': true,
};
// Determine if we need background task flow (tools/tool servers)
final bool isBackgroundToolsFlowPre =
(toolIdsForApi != null && toolIdsForApi.isNotEmpty) ||
(toolServers != null && toolServers.isNotEmpty);
final response = await api.sendMessage(
messages: conversationMessages,
model: selectedModel.id,
@@ -1054,7 +1060,9 @@ Future<void> _sendMessageInternal(
// handled via pre-stream client-side request above
enableImageGeneration: false,
modelItem: modelItem,
sessionIdOverride: socketSessionId,
// Only pass a session when we truly want task-based dynamic-channel
// behavior; for pure text flows prefer polling (if background mode).
sessionIdOverride: isBackgroundToolsFlowPre ? socketSessionId : null,
toolServers: toolServers,
backgroundTasks: bgTasks,
);
@@ -1078,9 +1086,7 @@ Future<void> _sendMessageInternal(
// Background-tools flow (tools/tool servers) relies on socket/dynamic channel for
// streaming content. Allow socket TEXT in that mode. For pure SSE flows, suppress
// socket TEXT to avoid duplicates (still surface tool_call status).
final bool isBackgroundToolsFlow =
(toolIdsForApi != null && toolIdsForApi.isNotEmpty) ||
(toolServers != null && toolServers.isNotEmpty);
final bool isBackgroundToolsFlow = isBackgroundToolsFlowPre;
bool suppressSocketContent = !isBackgroundToolsFlow; // allow socket text for tools
bool usingDynamicChannel = false; // set true when server provides a channel
if (socketService != null) {
@@ -2555,6 +2561,15 @@ final stopGenerationProvider = Provider<void Function()>((ref) {
}
} catch (_) {}
}());
// Also cancel local queue tasks for this conversation
try {
// Fire-and-forget local queue cancellation
// ignore: unawaited_futures
ref
.read(taskQueueProvider.notifier)
.cancelByConversation(activeConv.id);
} catch (_) {}
}
} catch (_) {}

View File

@@ -33,6 +33,7 @@ import '../../onboarding/views/onboarding_sheet.dart';
import '../../../shared/widgets/sheet_handle.dart';
import '../../../shared/widgets/conduit_components.dart';
import '../../../core/services/settings_service.dart';
import '../../../shared/services/tasks/task_queue.dart';
// Removed unused PlatformUtils import
import '../../../core/services/platform_service.dart' as ps;
import 'package:flutter/gestures.dart' show DragStartBehavior;
@@ -280,33 +281,30 @@ class _ChatPageState extends ConsumerState<ChatPage> {
}
try {
// Get attached files and use uploadedFileIds when sendMessage is updated to accept file IDs
// Get attached files and collect uploaded file IDs (including data URLs for images)
final attachedFiles = ref.read(attachedFilesProvider);
final uploadedFileIds = attachedFiles
.where(
(file) =>
file.status == FileUploadStatus.completed &&
file.fileId != null,
)
.where((file) =>
file.status == FileUploadStatus.completed && file.fileId != null)
.map((file) => file.fileId!)
.toList();
// Get selected tools
final toolIds = ref.read(selectedToolIdsProvider);
// Send message with file attachments and tools using existing provider logic
await sendMessage(
ref,
text,
uploadedFileIds.isNotEmpty ? uploadedFileIds : null,
toolIds.isNotEmpty ? toolIds : null,
);
// Enqueue task-based send to unify flow across text, images, and tools
final activeConv = ref.read(activeConversationProvider);
await ref.read(taskQueueProvider.notifier).enqueueSendText(
conversationId: activeConv?.id,
text: text,
attachments: uploadedFileIds.isNotEmpty ? uploadedFileIds : null,
toolIds: toolIds.isNotEmpty ? toolIds : null,
);
// Clear attachments after successful send
ref.read(attachedFilesProvider.notifier).clearAll();
// Scroll to bottom after sending message (only if user was near bottom)
// Scroll to bottom after enqueuing (only if user was near bottom)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
final maxScroll = _scrollController.position.maxScrollExtent;
@@ -879,7 +877,11 @@ class _ChatPageState extends ConsumerState<ChatPage> {
// Send the edited message
final selectedModel = ref.read(selectedModelProvider);
if (selectedModel != null) {
await sendMessage(ref, result, null);
final activeConv = ref.read(activeConversationProvider);
await ref.read(taskQueueProvider.notifier).enqueueSendText(
conversationId: activeConv?.id,
text: result,
);
if (mounted) {}
}