This repository has been archived on 2026-06-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FabledApp/lib/providers/capture_work_queue_provider.dart
T
bvandeusen 4ebc57d2e5 feat: quick capture sends to chat instead of quick-capture endpoint
Replace POST /api/quick-capture with: create a conversation, send the
message, and fire-and-forget the SSE generation stream so the assistant
processes the request in the background without blocking the UI.
The new conversation appears immediately in the chat tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:00:33 -04:00

87 lines
3.0 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/exceptions.dart';
import 'api_client_provider.dart';
import 'chat_provider.dart';
/// Outcome of a single capture attempt — consumed by the UI for snackbars.
class CaptureResult {
final String message;
final bool isError;
const CaptureResult(this.message, {this.isError = false});
}
/// The most recent capture result. UI watches this to show snackbars.
/// Reset to null by the notifier before each new item so listeners always fire.
final captureResultProvider =
NotifierProvider<_CaptureResultNotifier, CaptureResult?>(
_CaptureResultNotifier.new);
class _CaptureResultNotifier extends Notifier<CaptureResult?> {
@override
CaptureResult? build() => null;
}
/// In-memory sequential work queue for quick captures.
final captureWorkQueueProvider =
NotifierProvider<CaptureWorkQueueNotifier, List<String>>(
CaptureWorkQueueNotifier.new,
);
class CaptureWorkQueueNotifier extends Notifier<List<String>> {
bool _running = false;
@override
List<String> build() => [];
/// Add text to the queue and start the drain loop if not already running.
void enqueue(String text) {
state = [...state, text];
_drain();
}
Future<void> _drain() async {
if (_running) return;
_running = true;
try {
while (state.isNotEmpty) {
final text = state.first;
// Signal "no result yet" so the same result value can re-trigger watch.
ref.read(captureResultProvider.notifier).state = null;
try {
// Create a new conversation, add it to the conversations list, then
// send the message and kick off generation in the background.
final conv =
await ref.read(conversationsProvider.notifier).create('');
final chatRepo = ref.read(chatRepositoryProvider);
await chatRepo.sendMessage(conv.id, text);
// Fire-and-forget: drain the SSE stream so the server generates a
// response (creating notes/tasks/etc.) without blocking the UI.
chatRepo.streamGeneration(conv.id).drain<void>().ignore();
state = state.length > 1 ? state.sublist(1) : [];
ref.read(captureResultProvider.notifier).state =
const CaptureResult('Sent to Fabled.');
} on NetworkException catch (_) {
state = state.length > 1 ? state.sublist(1) : [];
ref.read(captureResultProvider.notifier).state = const CaptureResult(
"You're offline — message not sent.",
isError: true,
);
} on AppException catch (e) {
state = state.length > 1 ? state.sublist(1) : [];
ref.read(captureResultProvider.notifier).state =
CaptureResult(e.message, isError: true);
} catch (_) {
state = state.length > 1 ? state.sublist(1) : [];
ref.read(captureResultProvider.notifier).state = const CaptureResult(
'Failed to send. Please try again.',
isError: true);
}
}
} finally {
_running = false;
}
}
}