Add logger dependency and implement PromptBuilder for workflow management

This commit is contained in:
2025-03-21 11:51:35 +01:00
parent f45572532f
commit 8dad2b57b3
6 changed files with 699 additions and 2 deletions

View File

@@ -2,8 +2,8 @@ import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';
import 'package:logger/logger.dart';
import 'models/websocket_event.dart';
import 'models/progress_event.dart';
import 'models/execution_event.dart';
@@ -22,14 +22,17 @@ class ComfyUiApi {
final String clientId;
final http.Client _httpClient;
final WebSocketManager _webSocketManager;
final Logger logger;
/// Creates a new ComfyUI API client
ComfyUiApi({
required this.host,
required this.clientId,
Logger? logger,
http.Client? httpClient,
}) : _httpClient = httpClient ?? http.Client(),
_webSocketManager = WebSocketManager(host: host, clientId: clientId);
_webSocketManager = WebSocketManager(host: host, clientId: clientId),
logger = logger ?? Logger();
/// Expose WebSocketManager streams and methods
Stream<WebSocketEvent> get events => _webSocketManager.events;

View File

@@ -0,0 +1,89 @@
class PromptBuilder {
final String clientId;
final Map<String, dynamic> _nodes = {};
final Map<String, Map<String, String>> _outputToNode =
{}; // Maps output tags to node IDs
int _nodeIdCounter = 1;
PromptBuilder({required this.clientId});
/// Adds a node to the workflow
void addNode(String classType, Map<String, dynamic> inputs,
{String? title, Map<String, String>? outputTags}) {
final nodeId = _nodeIdCounter.toString();
// Resolve dependencies for inputs
inputs.forEach((key, value) {
if (value is List && value.length == 2 && value[1] is int) {
final outputTag = value[0];
if (_outputToNode.containsKey(outputTag)) {
inputs[key] = [_outputToNode[outputTag]!["nodeId"], value[1]];
} else {
throw Exception(
"Unresolved dependency: No node provides output tag '$outputTag'");
}
}
});
_nodes[nodeId] = {
"inputs": inputs,
"class_type": classType,
"_meta": {"title": title ?? classType}
};
// Register outputs of this node with optional tags
final defaultOutputs = _getDefaultOutputs(classType);
for (var i = 0; i < defaultOutputs.length; i++) {
final outputTag = outputTags?[defaultOutputs[i]] ?? defaultOutputs[i];
_outputToNode[outputTag] = {
"nodeId": nodeId,
"outputIndex": i.toString()
};
}
_nodeIdCounter++;
}
/// Generates the final workflow map
Map<String, dynamic> build() {
return {
"client_id": clientId,
"prompt": _nodes,
};
}
/// Validates the workflow against object info (optional)
void validate(Map<String, dynamic> objectInfo) {
for (var nodeId in _nodes.keys) {
final node = _nodes[nodeId];
final classType = node["class_type"];
if (!objectInfo.containsKey(classType)) {
throw Exception("Invalid node class type: $classType");
}
final requiredInputs = objectInfo[classType]["input"]["required"];
for (var inputKey in requiredInputs.keys) {
if (!node["inputs"].containsKey(inputKey)) {
throw Exception(
"Missing required input '$inputKey' for node $nodeId");
}
}
}
}
/// Helper method to get default outputs for a class type
List<String> _getDefaultOutputs(String classType) {
if (classType == "CheckpointLoaderSimple") {
return ["MODEL", "CLIP", "VAE"];
} else if (classType == "CLIPTextEncode") {
return ["CONDITIONING"];
} else if (classType == "EmptyLatentImage" ||
classType == "KSampler" ||
classType == "LatentUpscale" ||
classType == "LatentUpscaleBy") {
return ["LATENT"];
} else if (classType == "VAEDecode") {
return ["IMAGE"];
}
return [];
}
}