refactor: image uploads

This commit is contained in:
cogwheel0
2025-09-02 19:08:23 +05:30
parent 77e6a15215
commit 4491fa5861
5 changed files with 199 additions and 37 deletions

View File

@@ -98,6 +98,20 @@ abstract class OutboundTask with _$OutboundTask {
String? error,
}) = GenerateTitleTask;
const factory OutboundTask.imageToDataUrl({
required String id,
String? conversationId,
required String filePath,
required String fileName,
@Default(TaskStatus.queued) TaskStatus status,
@Default(0) int attempt,
String? idempotencyKey,
DateTime? enqueuedAt,
DateTime? startedAt,
DateTime? completedAt,
String? error,
}) = ImageToDataUrlTask;
factory OutboundTask.fromJson(Map<String, dynamic> json) =>
_$OutboundTaskFromJson(json);

View File

@@ -285,4 +285,25 @@ class TaskQueueNotifier extends StateNotifier<List<OutboundTask>> {
_process();
return id;
}
Future<String> enqueueImageToDataUrl({
required String? conversationId,
required String filePath,
required String fileName,
String? idempotencyKey,
}) async {
final id = _uuid.v4();
final task = OutboundTask.imageToDataUrl(
id: id,
conversationId: conversationId,
filePath: filePath,
fileName: fileName,
idempotencyKey: idempotencyKey,
enqueuedAt: DateTime.now(),
);
state = [...state, task];
await _save();
_process();
return id;
}
}

View File

@@ -1,5 +1,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'dart:io';
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/providers/app_providers.dart';
@@ -7,6 +9,8 @@ import '../../../core/services/attachment_upload_queue.dart';
import '../../../core/utils/debug_logger.dart';
import '../../../core/models/chat_message.dart';
import '../../../features/chat/providers/chat_providers.dart' as chat;
import '../../../features/chat/services/file_attachment_service.dart';
import 'package:path/path.dart' as path;
import 'package:uuid/uuid.dart';
import 'outbound_task.dart';
@@ -20,6 +24,7 @@ class TaskWorker {
uploadMedia: _performUploadMedia,
executeToolCall: _performExecuteToolCall,
generateImage: _performGenerateImage,
imageToDataUrl: _performImageToDataUrl,
saveConversation: _performSaveConversation,
generateTitle: _performGenerateTitle,
);
@@ -96,6 +101,34 @@ class TaskWorker {
entry = null;
}
if (entry == null) return;
// Reflect progress into UI attachment state if that file is present
try {
final current = _ref.read(attachedFilesProvider);
final idx = current.indexWhere((f) => f.file.path == task.filePath);
if (idx != -1) {
final existing = current[idx];
final status = switch (entry.status) {
QueuedAttachmentStatus.pending => FileUploadStatus.uploading,
QueuedAttachmentStatus.uploading => FileUploadStatus.uploading,
QueuedAttachmentStatus.completed => FileUploadStatus.completed,
QueuedAttachmentStatus.failed => FileUploadStatus.failed,
QueuedAttachmentStatus.cancelled => FileUploadStatus.failed,
};
final newState = FileUploadState(
file: File(task.filePath),
fileName: task.fileName,
fileSize: task.fileSize ?? existing.fileSize,
progress: status == FileUploadStatus.completed ? 1.0 : existing.progress,
status: status,
fileId: entry.fileId ?? existing.fileId,
error: entry.lastError,
);
_ref
.read(attachedFilesProvider.notifier)
.updateFileState(task.filePath, newState);
}
} catch (_) {}
switch (entry.status) {
case QueuedAttachmentStatus.completed:
case QueuedAttachmentStatus.failed:
@@ -281,6 +314,84 @@ class TaskWorker {
}
}
Future<void> _performImageToDataUrl(ImageToDataUrlTask task) async {
try {
// Update UI to uploading state first
try {
final current = _ref.read(attachedFilesProvider);
final idx = current.indexWhere((f) => f.file.path == task.filePath);
if (idx != -1) {
final existing = current[idx];
final uploading = FileUploadState(
file: existing.file,
fileName: task.fileName,
fileSize: existing.fileSize,
progress: 0.5,
status: FileUploadStatus.uploading,
fileId: existing.fileId,
);
_ref.read(attachedFilesProvider.notifier).updateFileState(
task.filePath,
uploading,
);
}
} catch (_) {}
// Read file and convert to data URL
final file = File(task.filePath);
final bytes = await file.readAsBytes();
final b64 = base64Encode(bytes);
final ext = path.extension(task.fileName).toLowerCase();
String mime = 'image/png';
if (ext == '.jpg' || ext == '.jpeg') mime = 'image/jpeg';
else if (ext == '.gif') mime = 'image/gif';
else if (ext == '.webp') mime = 'image/webp';
final dataUrl = 'data:$mime;base64,$b64';
// Mark as completed with data URL as fileId
try {
final current = _ref.read(attachedFilesProvider);
final idx = current.indexWhere((f) => f.file.path == task.filePath);
if (idx != -1) {
final existing = current[idx];
final done = FileUploadState(
file: existing.file,
fileName: task.fileName,
fileSize: existing.fileSize,
progress: 1.0,
status: FileUploadStatus.completed,
fileId: dataUrl,
);
_ref.read(attachedFilesProvider.notifier).updateFileState(
task.filePath,
done,
);
}
} catch (_) {}
} catch (e) {
try {
final current = _ref.read(attachedFilesProvider);
final idx = current.indexWhere((f) => f.file.path == task.filePath);
if (idx != -1) {
final existing = current[idx];
final failed = FileUploadState(
file: existing.file,
fileName: task.fileName,
fileSize: existing.fileSize,
progress: 0.0,
status: FileUploadStatus.failed,
fileId: existing.fileId,
error: e.toString(),
);
_ref.read(attachedFilesProvider.notifier).updateFileState(
task.filePath,
failed,
);
}
} catch (_) {}
}
}
Future<void> _performSaveConversation(SaveConversationTask task) async {
final api = _ref.read(apiServiceProvider);
final messages = _ref.read(chat.chatMessagesProvider);