Add initial implementation of comfyui_api_sdk with API models and examples
This commit is contained in:
206
lib/src/comfyui_api.dart
Normal file
206
lib/src/comfyui_api.dart
Normal file
@@ -0,0 +1,206 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
/// A Dart SDK for interacting with the ComfyUI API
|
||||
class ComfyUiApi {
|
||||
final String host;
|
||||
final String clientId;
|
||||
final http.Client _httpClient;
|
||||
WebSocketChannel? _wsChannel;
|
||||
final StreamController<Map<String, dynamic>> _progressController =
|
||||
StreamController.broadcast();
|
||||
|
||||
/// Stream of progress updates from ComfyUI
|
||||
Stream<Map<String, dynamic>> get progressUpdates =>
|
||||
_progressController.stream;
|
||||
|
||||
/// Creates a new ComfyUI API client
|
||||
///
|
||||
/// [host] The host of the ComfyUI server (e.g. 'http://localhost:8188')
|
||||
/// [clientId] Optional client ID, will be automatically generated if not provided
|
||||
ComfyUiApi({
|
||||
required this.host,
|
||||
String? clientId,
|
||||
http.Client? httpClient,
|
||||
}) : clientId = clientId ?? const Uuid().v4(),
|
||||
_httpClient = httpClient ?? http.Client();
|
||||
|
||||
/// Connects to the WebSocket for progress updates
|
||||
Future<void> connectWebSocket() async {
|
||||
final wsUrl =
|
||||
'ws://${host.replaceFirst(RegExp(r'^https?://'), '')}/ws?clientId=$clientId';
|
||||
_wsChannel = WebSocketChannel.connect(Uri.parse(wsUrl));
|
||||
|
||||
_wsChannel!.stream.listen((message) {
|
||||
final data = jsonDecode(message);
|
||||
_progressController.add(data);
|
||||
}, onError: (error) {
|
||||
print('WebSocket error: $error');
|
||||
}, onDone: () {
|
||||
print('WebSocket connection closed');
|
||||
});
|
||||
}
|
||||
|
||||
/// Closes the WebSocket connection and cleans up resources
|
||||
void dispose() {
|
||||
_wsChannel?.sink.close();
|
||||
_progressController.close();
|
||||
_httpClient.close();
|
||||
}
|
||||
|
||||
/// Gets the current queue status
|
||||
Future<Map<String, dynamic>> getQueue() async {
|
||||
final response = await _httpClient.get(Uri.parse('$host/queue'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets the history of the queue
|
||||
Future<Map<String, dynamic>> getHistory({int maxItems = 64}) async {
|
||||
final response = await _httpClient
|
||||
.get(Uri.parse('$host/api/history?max_items=$maxItems'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets image data by filename
|
||||
Future<List<int>> getImage(String filename) async {
|
||||
final response =
|
||||
await _httpClient.get(Uri.parse('$host/api/view?filename=$filename'));
|
||||
_validateResponse(response);
|
||||
return response.bodyBytes;
|
||||
}
|
||||
|
||||
/// Gets a list of all available models
|
||||
Future<Map<String, dynamic>> getModels() async {
|
||||
final response =
|
||||
await _httpClient.get(Uri.parse('$host/api/experiment/models'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets a list of checkpoints
|
||||
Future<List<dynamic>> getCheckpoints() async {
|
||||
final response = await _httpClient
|
||||
.get(Uri.parse('$host/api/experiment/models/checkpoints'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets details for a specific checkpoint
|
||||
Future<Map<String, dynamic>> getCheckpointDetails(
|
||||
String pathAndFileName) async {
|
||||
final response = await _httpClient.get(Uri.parse(
|
||||
'$host/api/view_metadata/checkpoints?filename=$pathAndFileName'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets a list of LoRAs
|
||||
Future<List<dynamic>> getLoras() async {
|
||||
final response =
|
||||
await _httpClient.get(Uri.parse('$host/api/experiment/models/loras'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets details for a specific LoRA
|
||||
Future<Map<String, dynamic>> getLoraDetails(String pathAndFileName) async {
|
||||
final response = await _httpClient.get(
|
||||
Uri.parse('$host/api/view_metadata/loras?filename=$pathAndFileName'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets a list of VAEs
|
||||
Future<List<dynamic>> getVaes() async {
|
||||
final response =
|
||||
await _httpClient.get(Uri.parse('$host/api/experiment/models/vae'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets details for a specific VAE
|
||||
Future<Map<String, dynamic>> getVaeDetails(String pathAndFileName) async {
|
||||
final response = await _httpClient.get(
|
||||
Uri.parse('$host/api/view_metadata/vae?filename=$pathAndFileName'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets a list of upscale models
|
||||
Future<List<dynamic>> getUpscaleModels() async {
|
||||
final response = await _httpClient
|
||||
.get(Uri.parse('$host/api/experiment/models/upscale_models'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets details for a specific upscale model
|
||||
Future<Map<String, dynamic>> getUpscaleModelDetails(
|
||||
String pathAndFileName) async {
|
||||
final response = await _httpClient.get(Uri.parse(
|
||||
'$host/api/view_metadata/upscale_models?filename=$pathAndFileName'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets a list of embeddings
|
||||
Future<List<dynamic>> getEmbeddings() async {
|
||||
final response = await _httpClient
|
||||
.get(Uri.parse('$host/api/experiment/models/embeddings'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets details for a specific embedding
|
||||
Future<Map<String, dynamic>> getEmbeddingDetails(
|
||||
String pathAndFileName) async {
|
||||
final response = await _httpClient.get(Uri.parse(
|
||||
'$host/api/view_metadata/embeddings?filename=$pathAndFileName'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Gets information about all available objects (nodes)
|
||||
Future<Map<String, dynamic>> getObjectInfo() async {
|
||||
final response = await _httpClient.get(Uri.parse('$host/api/object_info'));
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Submits a prompt (workflow) to generate an image
|
||||
Future<Map<String, dynamic>> submitPrompt(Map<String, dynamic> prompt) async {
|
||||
final response = await _httpClient.post(
|
||||
Uri.parse('$host/api/prompt'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(prompt),
|
||||
);
|
||||
_validateResponse(response);
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
|
||||
/// Validates HTTP response and throws an exception if needed
|
||||
void _validateResponse(http.Response response) {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw ComfyUiApiException(
|
||||
statusCode: response.statusCode,
|
||||
message: 'API request failed: ${response.body}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception thrown when the ComfyUI API returns an error
|
||||
class ComfyUiApiException implements Exception {
|
||||
final int statusCode;
|
||||
final String message;
|
||||
|
||||
ComfyUiApiException({required this.statusCode, required this.message});
|
||||
|
||||
@override
|
||||
String toString() => 'ComfyUiApiException: $statusCode - $message';
|
||||
}
|
87
lib/src/models/models.dart
Normal file
87
lib/src/models/models.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
/// Models that represent ComfyUI API responses
|
||||
|
||||
/// Represents queue information from ComfyUI
|
||||
class QueueInfo {
|
||||
final int queueRunning;
|
||||
final List<Map<String, dynamic>> queue;
|
||||
final Map<String, dynamic> queuePending;
|
||||
|
||||
QueueInfo({
|
||||
required this.queueRunning,
|
||||
required this.queue,
|
||||
required this.queuePending,
|
||||
});
|
||||
|
||||
factory QueueInfo.fromJson(Map<String, dynamic> json) {
|
||||
return QueueInfo(
|
||||
queueRunning: json['queue_running'] as int,
|
||||
queue: List<Map<String, dynamic>>.from(json['queue'] ?? []),
|
||||
queuePending: Map<String, dynamic>.from(json['queue_pending'] ?? {}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a prompt execution status
|
||||
class PromptExecutionStatus {
|
||||
final String? promptId;
|
||||
final int? number;
|
||||
final String? status;
|
||||
final dynamic error;
|
||||
|
||||
PromptExecutionStatus({
|
||||
this.promptId,
|
||||
this.number,
|
||||
this.status,
|
||||
this.error,
|
||||
});
|
||||
|
||||
factory PromptExecutionStatus.fromJson(Map<String, dynamic> json) {
|
||||
return PromptExecutionStatus(
|
||||
promptId: json['prompt_id'] as String?,
|
||||
number: json['number'] as int?,
|
||||
status: json['status'] as String?,
|
||||
error: json['error'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents history data
|
||||
class HistoryItem {
|
||||
final String promptId;
|
||||
final Map<String, dynamic> prompt;
|
||||
final Map<String, dynamic>? outputs;
|
||||
|
||||
HistoryItem({
|
||||
required this.promptId,
|
||||
required this.prompt,
|
||||
this.outputs,
|
||||
});
|
||||
|
||||
factory HistoryItem.fromJson(Map<String, dynamic> json) {
|
||||
return HistoryItem(
|
||||
promptId: json['prompt_id'] as String,
|
||||
prompt: Map<String, dynamic>.from(json['prompt'] ?? {}),
|
||||
outputs: json['outputs'] != null
|
||||
? Map<String, dynamic>.from(json['outputs'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a progress update received via WebSocket
|
||||
class ProgressUpdate {
|
||||
final String type;
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
ProgressUpdate({
|
||||
required this.type,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory ProgressUpdate.fromJson(Map<String, dynamic> json) {
|
||||
return ProgressUpdate(
|
||||
type: json['type'] as String,
|
||||
data: Map<String, dynamic>.from(json['data'] ?? {}),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user