adds proper support for fetching images

This commit is contained in:
Menno van Leeuwen 2025-03-30 18:40:31 +02:00
parent 47562afde0
commit 28a0014fa0
Signed by: vleeuwenmenno
SSH Key Fingerprint: SHA256:OJFmjANpakwD3F2Rsws4GLtbdz1TJ5tkQF0RZmF0TRE
4 changed files with 44 additions and 7 deletions

View File

@ -42,10 +42,19 @@ class ComfyApiHttpEndpoints {
return HistoryResponse.fromJson(jsonDecode(response.body)); return HistoryResponse.fromJson(jsonDecode(response.body));
} }
/// Gets image data by filename /// Gets image data by filename, optionally specifying subfolder and type
Future<List<int>> getImage(String filename) async { Future<List<int>> getImage(String filename,
final response = {String? subfolder, String? type}) async {
await _httpClient.get(Uri.parse('$host/api/view?filename=$filename')); // Build query parameters dynamically
final queryParameters = {
'filename': filename,
if (subfolder != null && subfolder.isNotEmpty) 'subfolder': subfolder,
if (type != null && type.isNotEmpty) 'type': type,
};
final uri =
Uri.parse('$host/api/view').replace(queryParameters: queryParameters);
logger.d('Fetching image from URI: $uri'); // Log the final URI
final response = await _httpClient.get(uri);
_validateResponse(response); _validateResponse(response);
return response.bodyBytes; return response.bodyBytes;
} }

View File

@ -96,9 +96,10 @@ class ComfyUiApi {
Future<HistoryResponse> getHistory({int maxItems = 64}) => Future<HistoryResponse> getHistory({int maxItems = 64}) =>
_httpEndpoints.getHistory(maxItems: maxItems); _httpEndpoints.getHistory(maxItems: maxItems);
/// Gets image data by filename /// Gets image data by filename, optionally specifying subfolder and type
Future<List<int>> getImage(String filename) => Future<List<int>> getImage(String filename,
_httpEndpoints.getImage(filename); {String? subfolder, String? type}) =>
_httpEndpoints.getImage(filename, subfolder: subfolder, type: type);
/// Gets a list of all available models /// Gets a list of all available models
Future<Map<String, dynamic>> getModels() => _httpEndpoints.getModels(); Future<Map<String, dynamic>> getModels() => _httpEndpoints.getModels();

View File

@ -2,20 +2,29 @@ class ExecutionEvent {
final String promptId; final String promptId;
final int timestamp; final int timestamp;
final String? node; final String? node;
final List<Map<String, dynamic>>? outputImages;
final Map<String, dynamic>? extra; final Map<String, dynamic>? extra;
const ExecutionEvent({ const ExecutionEvent({
required this.promptId, required this.promptId,
required this.timestamp, required this.timestamp,
this.node, this.node,
this.outputImages,
this.extra, this.extra,
}); });
factory ExecutionEvent.fromJson(Map<String, dynamic> json) { factory ExecutionEvent.fromJson(Map<String, dynamic> json) {
// Note: This factory is not directly used by the handler,
// but kept for potential future use or consistency.
// The handler now constructs the event directly.
return ExecutionEvent( return ExecutionEvent(
promptId: json['prompt_id'] as String, promptId: json['prompt_id'] as String,
timestamp: json['timestamp'] as int, timestamp: json['timestamp'] as int,
node: json['node'] as String?, node: json['node'] as String?,
// Parsing logic moved to the handler for direct use.
// outputImages: (json['output']?['images'] as List<dynamic>?)
// ?.map((e) => e as Map<String, dynamic>)
// .toList(),
extra: json['extra'] as Map<String, dynamic>?, extra: json['extra'] as Map<String, dynamic>?,
); );
} }
@ -25,6 +34,7 @@ class ExecutionEvent {
'prompt_id': promptId, 'prompt_id': promptId,
'timestamp': timestamp, 'timestamp': timestamp,
'node': node, 'node': node,
'outputImages': outputImages, // Added for completeness
'extra': extra, 'extra': extra,
}; };
} }

View File

@ -38,11 +38,28 @@ class WebSocketEventHandler {
) { ) {
if (event.data.containsKey('prompt_id')) { if (event.data.containsKey('prompt_id')) {
try { try {
List<Map<String, dynamic>>? outputImages;
if (event.data.containsKey('output') &&
event.data['output'] is Map &&
(event.data['output'] as Map).containsKey('images') &&
event.data['output']['images'] is List) {
// Ensure correct casting
try {
outputImages = (event.data['output']['images'] as List<dynamic>)
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
} catch (e) {
print('Error parsing output images: $e');
outputImages = null; // Set to null if parsing fails
}
}
final executionEvent = ExecutionEvent( final executionEvent = ExecutionEvent(
promptId: event.data['prompt_id'] as String, promptId: event.data['prompt_id'] as String,
timestamp: event.data['timestamp'] as int? ?? timestamp: event.data['timestamp'] as int? ??
DateTime.now().millisecondsSinceEpoch, DateTime.now().millisecondsSinceEpoch,
node: event.data['node']?.toString(), node: event.data['node']?.toString(),
outputImages: outputImages, // Pass the extracted images
extra: event.data['extra'] as Map<String, dynamic>?, extra: event.data['extra'] as Map<String, dynamic>?,
); );
executionEventController.add(executionEvent); executionEventController.add(executionEvent);