From 2bbbd38b93154f713487dae9b7fc6d1bb350544c Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Fri, 21 Mar 2025 11:51:57 +0100 Subject: [PATCH] Implement example file to showcase how the library works --- example/example.dart | 231 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 example/example.dart diff --git a/example/example.dart b/example/example.dart new file mode 100644 index 0000000..eca1c1d --- /dev/null +++ b/example/example.dart @@ -0,0 +1,231 @@ +import 'dart:io'; + +import 'package:comfyui_api_sdk/comfyui_api_sdk.dart'; +import 'package:uuid/uuid.dart'; +import 'package:comfyui_api_sdk/src/prompt_builder.dart'; + +bool jobFinished = false; + +void main() async { + // Create the API client + final clientId = Uuid().v4().replaceAll('-', ''); + final api = ComfyUiApi(host: 'http://mennos-server:7860', clientId: clientId); + + // Connect to the WebSocket for progress updates + await api.connectWebSocket(); + + // Listen for progress updates + api.onProgressChanged((progress) { + print( + '⏱️ Progress: ${progress.value} / ${progress.max} for ${progress.promptId}'); + }); + + // Register for prompt start events + api.onPromptStart((promptId) { + print('🚀 Prompt started: $promptId'); + }); + + // Register for prompt finished events + api.onPromptFinished((promptId) async { + print('✅ Prompt finished: $promptId'); + + // Fetch the history when a prompt finishes + try { + final history = await api.getHistory(maxItems: 5); + print('Recent history:'); + history.items.forEach((promptId, item) { + print(' - $promptId: ${item.status.statusStr}'); + }); + + final prompt = history.items[promptId]; + if (prompt != null) { + print('🖼 Prompt $promptId: ${prompt.status.statusStr}'); + + if (prompt.outputs.nodes.isNotEmpty) { + for (var node in prompt.outputs.nodes.values) { + for (var image in node.images) { + print( + ' - Image: ${image.filename} URL: ${api.host}/api/view?filename=${image.filename}'); + + // Write image to disk using image.fetchImageBytes(api.host) + final bytes = await image.fetchImageBytes(api.host); + + // Ensure pwd/images exists + Directory('images').createSync(); + + // We can save this to the disk under $pwd/images/${image.filename} + final file = File('images/${image.filename}'); + await file.writeAsBytes(bytes); + } + } + } + } + } catch (e) { + print('Error fetching history: $e'); + } + + jobFinished = true; + }); + + // Let's get a list of available checkpoints + try { + final checkpoints = await api.getCheckpoints(); + print('📦 Checkpoints: ${checkpoints.length}'); + for (var checkpoint in checkpoints) { + print(' - ${checkpoint.name}'); + } + } catch (e) { + print('❌ Error fetching checkpoints: $e'); + } + + // Let's get a list of vaes + try { + final vaes = await api.getVaes(); + print('🧠 VAES: ${vaes.length}'); + for (var vae in vaes) { + print(' - ${vae.name}'); + } + } catch (e) { + print('❌ Error fetching vaes: $e'); + } + + // Let's get a list of Loras + try { + final loras = await api.getLoras(); + print('📡 Loras: ${loras.length}'); + for (var lora in loras) { + print(' - ${lora.name}'); + } + } catch (e) { + print('❌ Error fetching loras: $e'); + } + + // Let's get a list of possible samplers to use + try { + final samplers = await api.getKSamplers(); + print('🎲 Samplers: ${samplers.length}'); + for (var sampler in samplers) { + print(' - ${sampler}'); + } + } catch (e) { + print('❌ Error fetching samplers: $e'); + } + + // Let's get a list of possible schedulers to use + try { + final schedulers = await api.getSchedulers(); + print('📅 Schedulers: ${schedulers.length}'); + for (var scheduler in schedulers) { + print(' - ${scheduler}'); + } + } catch (e) { + print('❌ Error fetching schedulers: $e'); + } + + var randomSeed = DateTime.now().millisecondsSinceEpoch; + + // Fetch object info for validation (optional) + Map objectInfo = {}; + try { + objectInfo = await api.getObjectInfo(); + } catch (e) { + print('❌ Error fetching object info: $e'); + } + + // Use PromptBuilder to create a workflow + final promptBuilder = PromptBuilder(clientId: api.clientId) + ..addNode( + "CheckpointLoaderSimple", + {"ckpt_name": "Anime/prefectPonyXL_v50.safetensors"}, + title: "Load Checkpoint", + outputTags: {"MODEL": "model", "CLIP": "clip", "VAE": "vae"}, + ) + ..addNode( + "EmptyLatentImage", + {"width": 512, "height": 512, "batch_size": 1}, + title: "Empty Latent Image", + outputTags: {"LATENT": "latent_image"}, + ) + ..addNode( + "CLIPTextEncode", + { + "text": "beautiful scenery nature glass bottle landscape", + "clip": ["clip", 1] // Explicitly use the "clip" output + }, + title: "CLIP Text Encode (Positive Prompt)", + outputTags: {"CONDITIONING": "positive_conditioning"}, + ) + ..addNode( + "CLIPTextEncode", + { + "text": "negative, dark, scary, horror, spooky", + "clip": ["clip", 1] // Explicitly use the "clip" output + }, + title: "CLIP Text Encode (Negative Prompt)", + outputTags: {"CONDITIONING": "negative_conditioning"}, + ) + ..addNode( + "KSampler", + { + "seed": randomSeed, + "steps": 25, + "cfg": 4, + "sampler_name": "euler", + "scheduler": "normal", + "denoise": 1, + "model": ["model", 0], // Explicitly use the "model" output + "positive": [ + "positive_conditioning", + 0 + ], // Explicitly use the positive conditioning + "negative": [ + "negative_conditioning", + 0 + ], // Explicitly use the negative conditioning + "latent_image": ["latent_image", 0] // Explicitly use the latent image + }, + title: "KSampler", + outputTags: {"LATENT": "denoised_latent"}, + ) + ..addNode( + "VAEDecode", + { + "samples": ["denoised_latent", 0], // Explicitly use the denoised latent + "vae": ["vae", 2] // Explicitly use the "vae" output + }, + title: "VAE Decode", + outputTags: {"IMAGE": "final_image"}, + ) + ..addNode( + "SaveImage", + { + "filename_prefix": "ComfyUI", + "images": ["final_image", 0] // Explicitly use the final image + }, + title: "Save Image", + ); + + // Validate the workflow (optional) + try { + promptBuilder.validate(objectInfo); + } catch (e) { + print('❌ Validation error: $e'); + return; + } + + // Submit the workflow + try { + final result = await api.submitPrompt(promptBuilder.build()); + print('💼 Prompt submitted: ${result.promptId}'); + } catch (e) { + print('❌ Error submitting prompt: $e'); + } + + // Wait until job is finished + while (!jobFinished) { + await Future.delayed(Duration(seconds: 1)); + } + + // Clean up + api.dispose(); +}