Add initial implementation of comfyui_api_sdk with API models and examples
This commit is contained in:
commit
1f9409ce0e
425
.dart_tool/package_config.json
Normal file
425
.dart_tool/package_config.json
Normal file
@ -0,0 +1,425 @@
|
||||
{
|
||||
"configVersion": 2,
|
||||
"packages": [
|
||||
{
|
||||
"name": "_fe_analyzer_shared",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-80.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "analyzer",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/analyzer-7.3.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/args-2.7.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "async",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/async-2.13.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "boolean_selector",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "build",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/build-2.4.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "build_config",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/build_config-1.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "build_daemon",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/build_daemon-4.0.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "build_resolvers",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/build_resolvers-2.4.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "build_runner",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/build_runner-2.4.14",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "build_runner_core",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/build_runner_core-8.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "built_collection",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/built_collection-5.1.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "built_value",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/built_value-8.9.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "checked_yaml",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/checked_yaml-2.0.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.19"
|
||||
},
|
||||
{
|
||||
"name": "code_builder",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/code_builder-4.10.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "collection",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/collection-1.19.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "convert",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/convert-3.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "coverage",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/coverage-1.11.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "crypto",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/crypto-3.0.6",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "dart_style",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/dart_style-3.0.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "file",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/file-7.0.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "fixnum",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/fixnum-1.1.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "frontend_server_client",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/frontend_server_client-4.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "glob",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/glob-2.1.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "graphs",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/graphs-2.3.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "http",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/http-0.13.6",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.19"
|
||||
},
|
||||
{
|
||||
"name": "http_multi_server",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/http_multi_server-3.2.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.2"
|
||||
},
|
||||
{
|
||||
"name": "http_parser",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/http_parser-4.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "io",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/io-1.0.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "js",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/js-0.7.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.7"
|
||||
},
|
||||
{
|
||||
"name": "json_annotation",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/json_annotation-4.9.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "lints",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/lints-2.1.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "logging",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/logging-1.3.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "matcher",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/matcher-0.12.17",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "meta",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/meta-1.16.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "mime",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/mime-2.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.2"
|
||||
},
|
||||
{
|
||||
"name": "mockito",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/mockito-5.4.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "node_preamble",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/node_preamble-2.0.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "package_config",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/package_config-2.2.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/path-1.9.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "pool",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/pool-1.5.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "pub_semver",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/pub_semver-2.2.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "pubspec_parse",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "shelf",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/shelf-1.4.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "shelf_packages_handler",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.17"
|
||||
},
|
||||
{
|
||||
"name": "shelf_static",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/shelf_static-1.1.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "shelf_web_socket",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/shelf_web_socket-2.0.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "source_gen",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/source_gen-2.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "source_map_stack_trace",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "source_maps",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/source_maps-0.10.13",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "source_span",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/source_span-1.10.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "stack_trace",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "stream_channel",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "stream_transform",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/stream_transform-2.1.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "string_scanner",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "term_glyph",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "test",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/test-1.25.15",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "test_api",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/test_api-0.7.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "test_core",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/test_core-0.6.8",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "timing",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/timing-1.0.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "typed_data",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "uuid",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/uuid-3.0.7",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "vm_service",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/vm_service-15.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "watcher",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/watcher-1.1.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/web-0.5.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "web_socket_channel",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "webkit_inspection_protocol",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "yaml",
|
||||
"rootUri": "file:///home/menno/.pub-cache/hosted/pub.dev/yaml-3.1.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "comfyui_api_sdk",
|
||||
"rootUri": "../",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
}
|
||||
],
|
||||
"generated": "2025-03-20T10:48:40.871849Z",
|
||||
"generator": "pub",
|
||||
"generatorVersion": "3.7.2",
|
||||
"flutterRoot": "file:///home/menno/.flutter/flutter",
|
||||
"flutterVersion": "3.29.2",
|
||||
"pubCache": "file:///home/menno/.pub-cache"
|
||||
}
|
BIN
.dart_tool/pub/bin/test/test.dart-3.7.2.snapshot
Normal file
BIN
.dart_tool/pub/bin/test/test.dart-3.7.2.snapshot
Normal file
Binary file not shown.
BIN
.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA=
Normal file
BIN
.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA=
Normal file
Binary file not shown.
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "comfyui-api-sdk",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "example/example.dart"
|
||||
}
|
||||
]
|
||||
}
|
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
||||
// Found requests:
|
||||
// To get the current queue
|
||||
// GET ${host}/queue
|
||||
|
||||
// To view a image
|
||||
// GET ${host}/api/view?filename=ComfyUI_00006_.png
|
||||
|
||||
// To get the history of the queue
|
||||
// GET ${host}/api/history?max_items=64
|
||||
|
||||
// To post a new image generation request to the queue
|
||||
// POST ${host}/api/prompt
|
||||
// Content-Type: application/json
|
||||
// { ... }
|
||||
|
||||
// To request a list of all the available models
|
||||
// GET ${host}/api/experiment/models
|
||||
|
||||
// To request a list of checkpoints (Or details for a specific checkpoint)
|
||||
// GET ${host}/api/experiment/models/checkpoints
|
||||
// GET ${host}/api/view_metadata/checkpoints?filename=${pathAndFileName}
|
||||
|
||||
// To request a list of loras (Or details for a specific lora)
|
||||
// GET ${host}/api/experiment/models/loras
|
||||
// GET ${host}/api/view_metadata/loras?filename=${pathAndFileName}
|
||||
|
||||
// To request a list of VAEs (Or details for a specific VAE)
|
||||
// GET ${host}/api/experiment/models/vae
|
||||
// GET ${host}/api/view_metadata/vae?filename=${pathAndFileName}
|
||||
|
||||
// To request a list of upscale models (Or details for a specific upscale model)
|
||||
// GET ${host}/api/experiment/models/upscale_models
|
||||
// GET ${host}/api/view_metadata/upscale_models?filename=${pathAndFileName}
|
||||
|
||||
// To request a list of embeddings (Or details for a specific embedding)
|
||||
// GET ${host}/api/experiment/models/embeddings
|
||||
// GET ${host}/api/view_metadata/embeddings?filename=${pathAndFileName}
|
||||
|
||||
// To get object info (Checkpoints, models, loras etc)
|
||||
// GET ${host}/api/object_info
|
||||
|
||||
// WebSocket for progress updates
|
||||
// ws://${host}/ws?clientId=${clientId}
|
||||
|
||||
// Final question
|
||||
// How do we figure out the clientId
|
88
api-response-examples/api/experiment/models.json
Normal file
88
api-response-examples/api/experiment/models.json
Normal file
@ -0,0 +1,88 @@
|
||||
[
|
||||
{
|
||||
"name": "checkpoints",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/checkpoints",
|
||||
"/data/models/Stable-diffusion",
|
||||
"/stable-diffusion/output/checkpoints"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "loras",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/loras",
|
||||
"/data/models/Lora",
|
||||
"/stable-diffusion/output/loras"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/vae",
|
||||
"/data/models/VAE",
|
||||
"/stable-diffusion/output/vae"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "text_encoders",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/text_encoders",
|
||||
"/stable-diffusion/models/clip",
|
||||
"/data/models/CLIPEncoder",
|
||||
"/stable-diffusion/output/clip"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "diffusion_models",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/unet",
|
||||
"/stable-diffusion/models/diffusion_models",
|
||||
"/stable-diffusion/output/diffusion_models"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "clip_vision",
|
||||
"folders": ["/stable-diffusion/models/clip_vision"]
|
||||
},
|
||||
{
|
||||
"name": "style_models",
|
||||
"folders": ["/stable-diffusion/models/style_models"]
|
||||
},
|
||||
{
|
||||
"name": "embeddings",
|
||||
"folders": ["/stable-diffusion/models/embeddings", "/data/embeddings"]
|
||||
},
|
||||
{ "name": "diffusers", "folders": ["/stable-diffusion/models/diffusers"] },
|
||||
{ "name": "vae_approx", "folders": ["/stable-diffusion/models/vae_approx"] },
|
||||
{
|
||||
"name": "controlnet",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/controlnet",
|
||||
"/stable-diffusion/models/t2i_adapter",
|
||||
"/data/models/ControlNet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "gligen",
|
||||
"folders": ["/stable-diffusion/models/gligen", "/data/models/GLIGEN"]
|
||||
},
|
||||
{
|
||||
"name": "upscale_models",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/upscale_models",
|
||||
"/data/models/RealESRGAN",
|
||||
"/data/models/ESRGAN",
|
||||
"/data/models/SwinIR",
|
||||
"/data/models/GFPGAN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hypernetworks",
|
||||
"folders": [
|
||||
"/stable-diffusion/models/hypernetworks",
|
||||
"/data/models/hypernetworks"
|
||||
]
|
||||
},
|
||||
{ "name": "photomaker", "folders": ["/stable-diffusion/models/photomaker"] },
|
||||
{ "name": "classifiers", "folders": ["/stable-diffusion/models/classifiers"] }
|
||||
]
|
31
api-response-examples/api/experiment/models/checkpoints.json
Normal file
31
api-response-examples/api/experiment/models/checkpoints.json
Normal file
@ -0,0 +1,31 @@
|
||||
[
|
||||
{ "name": "prefectPonyXL_v3.safetensors", "pathIndex": 1 },
|
||||
{ "name": "sd-v1-5-inpainting.ckpt", "pathIndex": 1 },
|
||||
{ "name": "v1-5-pruned-emaonly.ckpt", "pathIndex": 1 },
|
||||
{ "name": "Semi-realism/bemypony_Semirealanime.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Semi-realism/duchaitenPonyXLNo_v60.safetensors", "pathIndex": 1 },
|
||||
{ "name": "FLUX/flux1-dev-fp8.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Realism/cyberrealisticPony_v70a.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Realism/cyberrealisticPony_v8.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Realism/realvisxlV50_v50Bakedvae.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Anime/autismmixSDXL_autismmixConfetti.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Anime/autismmixSDXL_autismmixPony.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Anime/ponyDiffusionV6XL_v6StartWithThisOne.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Anime/prefectPonyXL_v50.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Anime/waiANINSFWPONYXL_v11.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Anime/waiANINSFWPONYXL_v130.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Anime/waiNSFWIllustrious_v70.safetensors", "pathIndex": 1 },
|
||||
{ "name": "RDXL/rdxlAnime_sdxlPony8.safetensors", "pathIndex": 1 },
|
||||
{ "name": "RDXL/rdxlPixelArt_pony2.safetensors", "pathIndex": 1 },
|
||||
{ "name": "RDXL/realDream_sdxlPony12.safetensors", "pathIndex": 1 },
|
||||
{ "name": "SD3.5/sd3.5_large_fp16.safetensors", "pathIndex": 1 },
|
||||
{ "name": "SD3.5/sd3.5_large_fp8_scaled.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Babes/babesBYSTABLEYOGI_xlV2.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Babes/babesByStableYogi_ponyV3VAE.safetensors", "pathIndex": 1 }
|
||||
]
|
101
api-response-examples/api/experiment/models/loras.json
Normal file
101
api-response-examples/api/experiment/models/loras.json
Normal file
@ -0,0 +1,101 @@
|
||||
[
|
||||
{ "name": "Expressive_H-000001.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Hand v2.safetensors", "pathIndex": 1 },
|
||||
{ "name": "LogoRedmondV2-Logo-LogoRedmAF.safetensors", "pathIndex": 1 },
|
||||
{ "name": "WowifierXL-V2.safetensors", "pathIndex": 1 },
|
||||
{ "name": "detailed_notrigger.safetensors", "pathIndex": 1 },
|
||||
{ "name": "detailxl.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/80s_Pop_PDXL.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/Alola_Style_PDXL.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/BoldToon.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Citron Pony Styles/CandyCuteStylePDXL.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Citron Pony Styles/CatalystStylePDXL.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Citron Pony Styles/Citron3D_PDXL.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Citron Pony Styles/CitronAnimeTreasure-07.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Citron Pony Styles/EnergyCAT.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/FlatAnimeP1.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/LunarCAT_Style.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/RealisticAnime.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/Smooth.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Citron Pony Styles/Vivid.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Vixon's Pony Styles/Sh4rd4n1cXLP.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Vixon's Pony Styles/ch33s3XLP.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Vixon's Pony Styles/itsyelizXLP.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Vixon's Pony Styles/lalangheejXLP.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Vixon's Pony Styles/nikkileeismeXLP.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Vixon's Pony Styles/tomidoronXLP.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Characters/princess_xl_v2.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Characters/princess_zelda.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Characters/Peni Parker/32dim-MR_PeniParker-PONY.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Peni Parker/PeniParkerRivals-10.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Peni Parker/Peni_Parker-000007.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Peni Parker/Peni_parker_marvel_rivels.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Cortana/Cortana(revAnimated).safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Characters/Cortana/Cortana.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Characters/Cortana/Cortana_XL.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Characters/Cortana/cortana_xl_v3.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Characters/Widowmaker/SDXL_ow1 Windowmaker.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Widowmaker/WidowmakerPonyLoRA.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Widowmaker/Widowmaker_cgi.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Characters/Lara Croft/ClassicLara.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Characters/Lara Croft/LaraCroft_character-20.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Lara Croft/lara_croft_xl_v2.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Samus Aran/Samus AranPonyLora.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Characters/Samus Aran/samus aran.safetensors", "pathIndex": 1 },
|
||||
{ "name": "Characters/Samus Aran/samus-09.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Characters/D.va/DVaOWXL - by KillerUwU13_AI.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{ "name": "Characters/D.va/DVaPony.safetensors", "pathIndex": 1 },
|
||||
{
|
||||
"name": "Characters/Scarlett Johansson/Scarlett-v20.safetensors",
|
||||
"pathIndex": 1
|
||||
},
|
||||
{
|
||||
"name": "Characters/Scarlett Johansson/Scarlett4.safetensors",
|
||||
"pathIndex": 1
|
||||
}
|
||||
]
|
@ -0,0 +1,10 @@
|
||||
[
|
||||
{ "name": "RealESRGAN_x4plus.pth", "pathIndex": 1 },
|
||||
{ "name": "RealESRGAN_x4plus_anime_6B.pth", "pathIndex": 1 },
|
||||
{ "name": "4x-AnimeSharp.pth", "pathIndex": 2 },
|
||||
{ "name": "4x-UltraSharp.pth", "pathIndex": 2 },
|
||||
{ "name": "4xNMKDSuperscale_4xNMKDSuperscale.pt", "pathIndex": 2 },
|
||||
{ "name": "ESRGAN_4x.pth", "pathIndex": 2 },
|
||||
{ "name": "SwinIR_4x.pth", "pathIndex": 3 },
|
||||
{ "name": "GFPGANv1.4.pth", "pathIndex": 4 }
|
||||
]
|
5
api-response-examples/api/experiment/models/vae.json
Normal file
5
api-response-examples/api/experiment/models/vae.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
{ "name": "ae.safetensors", "pathIndex": 1 },
|
||||
{ "name": "sdxl_vae.safetensors", "pathIndex": 1 },
|
||||
{ "name": "vae-ft-mse-840000-ema-pruned.ckpt", "pathIndex": 1 }
|
||||
]
|
10222
api-response-examples/api/object-info.json
Normal file
10222
api-response-examples/api/object-info.json
Normal file
File diff suppressed because it is too large
Load Diff
12
api-response-examples/api/view_metadata/checkpoints.json
Normal file
12
api-response-examples/api/view_metadata/checkpoints.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"modelspec.hash_sha256": "0x2f3c5caac0469f474439cf84eb09f900bd8e5900f4ad9404c4e05cec12314df6",
|
||||
"modelspec.date": "2024-08-01",
|
||||
"modelspec.sai_model_spec": "1.0.1",
|
||||
"modelspec.author": "Black Forest Labs",
|
||||
"modelspec.architecture": "Flux.1-dev",
|
||||
"modelspec.license": "FLUX.1 [dev] Non-Commercial License",
|
||||
"modelspec.implementation": "https://github.com/black-forest-labs/flux",
|
||||
"modelspec.thumbnail": "data:image/jpeg;base64,TRUNCATED",
|
||||
"modelspec.title": "Flux.1-dev",
|
||||
"modelspec.description": "A guidance distilled rectified flow model."
|
||||
}
|
77
api-response-examples/api/view_metadata/loras.json
Normal file
77
api-response-examples/api/view_metadata/loras.json
Normal file
File diff suppressed because one or more lines are too long
11
api-response-examples/api/view_metadata/vae.json
Normal file
11
api-response-examples/api/view_metadata/vae.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"modelspec.architecture": "Flux.1-AE",
|
||||
"modelspec.title": "Flux.1 Autoencoder",
|
||||
"modelspec.author": "Black Forest Labs",
|
||||
"modelspec.description": "The autoencoder for the Flux.1 model family",
|
||||
"modelspec.implementation": "https://github.com/black-forest-labs/flux",
|
||||
"modelspec.date": "2024-08-01",
|
||||
"modelspec.license": "Apache License 2.0",
|
||||
"modelspec.hash_sha256": "0xddec9c299f56c1178e6281a12167f2ebec9aa4de8fce81e234a687bb231d5b6d",
|
||||
"modelspec.sai_model_spec": "1.0.1"
|
||||
}
|
2724
api-response-examples/history.json
Normal file
2724
api-response-examples/history.json
Normal file
File diff suppressed because it is too large
Load Diff
768
api-response-examples/queue.json
Normal file
768
api-response-examples/queue.json
Normal file
@ -0,0 +1,768 @@
|
||||
{
|
||||
"queue_running": [
|
||||
[
|
||||
6,
|
||||
"97f9479d-4a5b-40d3-a71f-a75d3aadacdb",
|
||||
{
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 1042361530597518,
|
||||
"steps": 20,
|
||||
"cfg": 8.0,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1.0,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"class_type": "KSampler",
|
||||
"_meta": { "title": "KSampler" }
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "Anime/autismmixSDXL_autismmixConfetti.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"_meta": { "title": "Load Checkpoint" }
|
||||
},
|
||||
"5": {
|
||||
"inputs": { "width": 512, "height": 512, "batch_size": 1 },
|
||||
"class_type": "EmptyLatentImage",
|
||||
"_meta": { "title": "Empty Latent Image" }
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": { "title": "CLIP Text Encode (Prompt)" }
|
||||
},
|
||||
"7": {
|
||||
"inputs": { "text": "text, watermark", "clip": ["4", 1] },
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": { "title": "CLIP Text Encode (Prompt)" }
|
||||
},
|
||||
"8": {
|
||||
"inputs": { "samples": ["3", 0], "vae": ["4", 2] },
|
||||
"class_type": "VAEDecode",
|
||||
"_meta": { "title": "VAE Decode" }
|
||||
},
|
||||
"9": {
|
||||
"inputs": { "filename_prefix": "ComfyUI", "images": ["8", 0] },
|
||||
"class_type": "SaveImage",
|
||||
"_meta": { "title": "Save Image" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"extra_pnginfo": {
|
||||
"workflow": {
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [413, 389],
|
||||
"size": [425.27801513671875, 180.6060791015625],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "clip", "type": "CLIP", "link": 5 }],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [6],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CLIPTextEncode" },
|
||||
"widgets_values": ["text, watermark"]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [415, 186],
|
||||
"size": [422.84503173828125, 164.31304931640625],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "clip", "type": "CLIP", "link": 3 }],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [4],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CLIPTextEncode" },
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [473, 609],
|
||||
"size": [315, 106],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [2],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "EmptyLatentImage" },
|
||||
"widgets_values": [512, 512, 1]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [863, 186],
|
||||
"size": [315, 262],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "model", "type": "MODEL", "link": 1 },
|
||||
{ "name": "positive", "type": "CONDITIONING", "link": 4 },
|
||||
{ "name": "negative", "type": "CONDITIONING", "link": 6 },
|
||||
{ "name": "latent_image", "type": "LATENT", "link": 2 }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [7],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "KSampler" },
|
||||
"widgets_values": [
|
||||
1042361530597518,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [1209, 188],
|
||||
"size": [210, 46],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "samples", "type": "LATENT", "link": 7 },
|
||||
{ "name": "vae", "type": "VAE", "link": 8 }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [9],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "VAEDecode" },
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveImage",
|
||||
"pos": [1451, 189],
|
||||
"size": [210, 270],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "images", "type": "IMAGE", "link": 9 }],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": ["ComfyUI"]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [26, 474],
|
||||
"size": [315, 98],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [1],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [3, 5],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [8],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
|
||||
"widgets_values": [
|
||||
"Anime/autismmixSDXL_autismmixConfetti.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[1, 4, 0, 3, 0, "MODEL"],
|
||||
[2, 5, 0, 3, 3, "LATENT"],
|
||||
[3, 4, 1, 6, 0, "CLIP"],
|
||||
[4, 6, 0, 3, 1, "CONDITIONING"],
|
||||
[5, 4, 1, 7, 0, "CLIP"],
|
||||
[6, 7, 0, 3, 2, "CONDITIONING"],
|
||||
[7, 3, 0, 8, 0, "LATENT"],
|
||||
[8, 4, 2, 8, 1, "VAE"],
|
||||
[9, 8, 0, 9, 0, "IMAGE"]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9090909090909091,
|
||||
"offset": [114.45999999999988, -114.63999999999956]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
},
|
||||
"client_id": "eda4bf07c812424dbb9e964c5e000ade"
|
||||
},
|
||||
["9"]
|
||||
]
|
||||
],
|
||||
"queue_pending": [
|
||||
[
|
||||
7,
|
||||
"09e094b9-51c2-4355-a994-9c7272310d76",
|
||||
{
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 344828775410837,
|
||||
"steps": 20,
|
||||
"cfg": 8.0,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1.0,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"class_type": "KSampler",
|
||||
"_meta": { "title": "KSampler" }
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "Anime/autismmixSDXL_autismmixConfetti.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"_meta": { "title": "Load Checkpoint" }
|
||||
},
|
||||
"5": {
|
||||
"inputs": { "width": 512, "height": 512, "batch_size": 1 },
|
||||
"class_type": "EmptyLatentImage",
|
||||
"_meta": { "title": "Empty Latent Image" }
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": { "title": "CLIP Text Encode (Prompt)" }
|
||||
},
|
||||
"7": {
|
||||
"inputs": { "text": "text, watermark", "clip": ["4", 1] },
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": { "title": "CLIP Text Encode (Prompt)" }
|
||||
},
|
||||
"8": {
|
||||
"inputs": { "samples": ["3", 0], "vae": ["4", 2] },
|
||||
"class_type": "VAEDecode",
|
||||
"_meta": { "title": "VAE Decode" }
|
||||
},
|
||||
"9": {
|
||||
"inputs": { "filename_prefix": "ComfyUI", "images": ["8", 0] },
|
||||
"class_type": "SaveImage",
|
||||
"_meta": { "title": "Save Image" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"extra_pnginfo": {
|
||||
"workflow": {
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [413, 389],
|
||||
"size": [425.27801513671875, 180.6060791015625],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "clip", "type": "CLIP", "link": 5 }],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [6],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CLIPTextEncode" },
|
||||
"widgets_values": ["text, watermark"]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [415, 186],
|
||||
"size": [422.84503173828125, 164.31304931640625],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "clip", "type": "CLIP", "link": 3 }],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [4],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CLIPTextEncode" },
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [473, 609],
|
||||
"size": [315, 106],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [2],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "EmptyLatentImage" },
|
||||
"widgets_values": [512, 512, 1]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [863, 186],
|
||||
"size": [315, 262],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "model", "type": "MODEL", "link": 1 },
|
||||
{ "name": "positive", "type": "CONDITIONING", "link": 4 },
|
||||
{ "name": "negative", "type": "CONDITIONING", "link": 6 },
|
||||
{ "name": "latent_image", "type": "LATENT", "link": 2 }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [7],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "KSampler" },
|
||||
"widgets_values": [
|
||||
344828775410837,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [1209, 188],
|
||||
"size": [210, 46],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "samples", "type": "LATENT", "link": 7 },
|
||||
{ "name": "vae", "type": "VAE", "link": 8 }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [9],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "VAEDecode" },
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveImage",
|
||||
"pos": [1451, 189],
|
||||
"size": [210, 270],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "images", "type": "IMAGE", "link": 9 }],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": ["ComfyUI"]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [26, 474],
|
||||
"size": [315, 98],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [1],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [3, 5],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [8],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
|
||||
"widgets_values": [
|
||||
"Anime/autismmixSDXL_autismmixConfetti.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[1, 4, 0, 3, 0, "MODEL"],
|
||||
[2, 5, 0, 3, 3, "LATENT"],
|
||||
[3, 4, 1, 6, 0, "CLIP"],
|
||||
[4, 6, 0, 3, 1, "CONDITIONING"],
|
||||
[5, 4, 1, 7, 0, "CLIP"],
|
||||
[6, 7, 0, 3, 2, "CONDITIONING"],
|
||||
[7, 3, 0, 8, 0, "LATENT"],
|
||||
[8, 4, 2, 8, 1, "VAE"],
|
||||
[9, 8, 0, 9, 0, "IMAGE"]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9090909090909091,
|
||||
"offset": [114.45999999999988, -114.63999999999956]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
},
|
||||
"client_id": "eda4bf07c812424dbb9e964c5e000ade"
|
||||
},
|
||||
["9"]
|
||||
],
|
||||
[
|
||||
8,
|
||||
"6dbfc8a9-f672-47f6-9bcd-f43e571e280a",
|
||||
{
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 255741014898185,
|
||||
"steps": 20,
|
||||
"cfg": 8.0,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1.0,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"class_type": "KSampler",
|
||||
"_meta": { "title": "KSampler" }
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "Anime/autismmixSDXL_autismmixConfetti.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"_meta": { "title": "Load Checkpoint" }
|
||||
},
|
||||
"5": {
|
||||
"inputs": { "width": 512, "height": 512, "batch_size": 1 },
|
||||
"class_type": "EmptyLatentImage",
|
||||
"_meta": { "title": "Empty Latent Image" }
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": { "title": "CLIP Text Encode (Prompt)" }
|
||||
},
|
||||
"7": {
|
||||
"inputs": { "text": "text, watermark", "clip": ["4", 1] },
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": { "title": "CLIP Text Encode (Prompt)" }
|
||||
},
|
||||
"8": {
|
||||
"inputs": { "samples": ["3", 0], "vae": ["4", 2] },
|
||||
"class_type": "VAEDecode",
|
||||
"_meta": { "title": "VAE Decode" }
|
||||
},
|
||||
"9": {
|
||||
"inputs": { "filename_prefix": "ComfyUI", "images": ["8", 0] },
|
||||
"class_type": "SaveImage",
|
||||
"_meta": { "title": "Save Image" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"extra_pnginfo": {
|
||||
"workflow": {
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [413, 389],
|
||||
"size": [425.27801513671875, 180.6060791015625],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "clip", "type": "CLIP", "link": 5 }],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [6],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CLIPTextEncode" },
|
||||
"widgets_values": ["text, watermark"]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [415, 186],
|
||||
"size": [422.84503173828125, 164.31304931640625],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "clip", "type": "CLIP", "link": 3 }],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [4],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CLIPTextEncode" },
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [473, 609],
|
||||
"size": [315, 106],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [2],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "EmptyLatentImage" },
|
||||
"widgets_values": [512, 512, 1]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [863, 186],
|
||||
"size": [315, 262],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "model", "type": "MODEL", "link": 1 },
|
||||
{ "name": "positive", "type": "CONDITIONING", "link": 4 },
|
||||
{ "name": "negative", "type": "CONDITIONING", "link": 6 },
|
||||
{ "name": "latent_image", "type": "LATENT", "link": 2 }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [7],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "KSampler" },
|
||||
"widgets_values": [
|
||||
255741014898185,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [1209, 188],
|
||||
"size": [210, 46],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "samples", "type": "LATENT", "link": 7 },
|
||||
{ "name": "vae", "type": "VAE", "link": 8 }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [9],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "VAEDecode" },
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveImage",
|
||||
"pos": [1451, 189],
|
||||
"size": [210, 270],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "images", "type": "IMAGE", "link": 9 }],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": ["ComfyUI"]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [26, 474],
|
||||
"size": [315, 98],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [1],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [3, 5],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [8],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
|
||||
"widgets_values": [
|
||||
"Anime/autismmixSDXL_autismmixConfetti.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[1, 4, 0, 3, 0, "MODEL"],
|
||||
[2, 5, 0, 3, 3, "LATENT"],
|
||||
[3, 4, 1, 6, 0, "CLIP"],
|
||||
[4, 6, 0, 3, 1, "CONDITIONING"],
|
||||
[5, 4, 1, 7, 0, "CLIP"],
|
||||
[6, 7, 0, 3, 2, "CONDITIONING"],
|
||||
[7, 3, 0, 8, 0, "LATENT"],
|
||||
[8, 4, 2, 8, 1, "VAE"],
|
||||
[9, 8, 0, 9, 0, "IMAGE"]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9090909090909091,
|
||||
"offset": [114.45999999999988, -114.63999999999956]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
},
|
||||
"client_id": "eda4bf07c812424dbb9e964c5e000ade"
|
||||
},
|
||||
["9"]
|
||||
]
|
||||
]
|
||||
}
|
205
comfyui-api-sdk.dart
Normal file
205
comfyui-api-sdk.dart
Normal file
@ -0,0 +1,205 @@
|
||||
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';
|
||||
|
||||
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<Map<String, 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<Map<String, 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<Map<String, 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<Map<String, 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<Map<String, 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';
|
||||
}
|
94
example/example.dart
Normal file
94
example/example.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'dart:io';
|
||||
import 'package:comfyui_api_sdk/comfyui_api_sdk.dart';
|
||||
|
||||
void main() async {
|
||||
// Create the API client
|
||||
final api = ComfyUiApi(host: 'http://mennos-server:7860');
|
||||
|
||||
// Connect to the WebSocket for progress updates
|
||||
await api.connectWebSocket();
|
||||
|
||||
// Listen for progress updates
|
||||
api.progressUpdates.listen((update) {
|
||||
print('Progress update: $update');
|
||||
});
|
||||
|
||||
// Get available checkpoints
|
||||
final checkpoints = await api.getCheckpoints();
|
||||
print('Available checkpoints: ${checkpoints.keys.join(', ')}');
|
||||
|
||||
// Get queue status
|
||||
final queue = await api.getQueue();
|
||||
print('Queue status: $queue');
|
||||
|
||||
// Submit a basic text-to-image prompt
|
||||
final promptWorkflow = {
|
||||
"prompt": {
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 123456789,
|
||||
"steps": 20,
|
||||
"cfg": 7,
|
||||
"sampler_name": "euler_ancestral",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"class_type": "KSampler"
|
||||
},
|
||||
"4": {
|
||||
"inputs": {"ckpt_name": "dreamshaper_8.safetensors"},
|
||||
"class_type": "CheckpointLoaderSimple"
|
||||
},
|
||||
"5": {
|
||||
"inputs": {"width": 512, "height": 512, "batch_size": 1},
|
||||
"class_type": "EmptyLatentImage"
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "a beautiful landscape with mountains and a lake",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"text": "ugly, blurry, low quality",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": ["3", 0],
|
||||
"vae": ["4", 2]
|
||||
},
|
||||
"class_type": "VAEDecode"
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": ["8", 0]
|
||||
},
|
||||
"class_type": "SaveImage"
|
||||
}
|
||||
},
|
||||
"client_id": api.clientId
|
||||
};
|
||||
|
||||
try {
|
||||
final result = await api.submitPrompt(promptWorkflow);
|
||||
print('Prompt submitted: $result');
|
||||
} catch (e) {
|
||||
print('Error submitting prompt: $e');
|
||||
}
|
||||
|
||||
// Wait for some time to receive WebSocket messages
|
||||
await Future.delayed(Duration(seconds: 60));
|
||||
|
||||
// Clean up
|
||||
api.dispose();
|
||||
}
|
4
lib/comfyui_api_sdk.dart
Normal file
4
lib/comfyui_api_sdk.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library comfyui_api_sdk;
|
||||
|
||||
export 'src/comfyui_api.dart';
|
||||
export 'src/models/models.dart';
|
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'] ?? {}),
|
||||
);
|
||||
}
|
||||
}
|
549
pubspec.lock
Normal file
549
pubspec.lock
Normal file
@ -0,0 +1,549 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "80.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.14"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mockito:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mockito
|
||||
sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.5"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.25.15"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.8"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
web_socket_channel:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
17
pubspec.yaml
Normal file
17
pubspec.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
name: comfyui_api_sdk
|
||||
description: A Dart SDK for interacting with ComfyUI API
|
||||
version: 0.1.0
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
http: ^0.13.5
|
||||
web_socket_channel: ^2.3.0
|
||||
uuid: ^3.0.7
|
||||
mockito: ^5.4.5
|
||||
build_runner: ^2.4.14
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.21.0
|
197
test/comfyui_api_test.dart
Normal file
197
test/comfyui_api_test.dart
Normal file
@ -0,0 +1,197 @@
|
||||
import 'dart:convert';
|
||||
import 'package:comfyui_api_sdk/comfyui_api_sdk.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
import 'comfyui_api_test.mocks.dart';
|
||||
import 'test_data.dart';
|
||||
|
||||
@GenerateMocks([http.Client, WebSocketChannel, WebSocketSink])
|
||||
void main() {
|
||||
late MockClient mockClient;
|
||||
late ComfyUiApi api;
|
||||
const String testHost = 'http://localhost:8188';
|
||||
const String testClientId = 'test-client-id';
|
||||
|
||||
setUp(() {
|
||||
mockClient = MockClient();
|
||||
api = ComfyUiApi(
|
||||
host: testHost,
|
||||
clientId: testClientId,
|
||||
httpClient: mockClient,
|
||||
);
|
||||
});
|
||||
|
||||
group('ComfyUiApi', () {
|
||||
test('initialize with provided values', () {
|
||||
expect(api.host, equals(testHost));
|
||||
expect(api.clientId, equals(testClientId));
|
||||
});
|
||||
|
||||
test('initialize with generated clientId when not provided', () {
|
||||
final autoApi = ComfyUiApi(host: testHost, httpClient: mockClient);
|
||||
expect(autoApi.clientId, isNotEmpty);
|
||||
expect(autoApi.clientId, isNot(equals(testClientId)));
|
||||
});
|
||||
|
||||
test('getQueue returns parsed response', () async {
|
||||
when(mockClient.get(Uri.parse('$testHost/queue'))).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(TestData.queueResponse), 200));
|
||||
|
||||
final result = await api.getQueue();
|
||||
|
||||
expect(result, equals(TestData.queueResponse));
|
||||
verify(mockClient.get(Uri.parse('$testHost/queue'))).called(1);
|
||||
});
|
||||
|
||||
test('getHistory returns parsed response', () async {
|
||||
when(mockClient.get(Uri.parse('$testHost/api/history?max_items=64')))
|
||||
.thenAnswer((_) async =>
|
||||
http.Response(jsonEncode(TestData.historyResponse), 200));
|
||||
|
||||
final result = await api.getHistory();
|
||||
|
||||
expect(result, equals(TestData.historyResponse));
|
||||
verify(mockClient.get(Uri.parse('$testHost/api/history?max_items=64')))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getImage returns image bytes', () async {
|
||||
final bytes = [1, 2, 3, 4];
|
||||
when(mockClient.get(Uri.parse('$testHost/api/view?filename=test.png')))
|
||||
.thenAnswer((_) async => http.Response.bytes(bytes, 200));
|
||||
|
||||
final result = await api.getImage('test.png');
|
||||
|
||||
expect(result, equals(bytes));
|
||||
verify(mockClient.get(Uri.parse('$testHost/api/view?filename=test.png')))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getCheckpoints returns parsed response', () async {
|
||||
when(mockClient
|
||||
.get(Uri.parse('$testHost/api/experiment/models/checkpoints')))
|
||||
.thenAnswer((_) async =>
|
||||
http.Response(jsonEncode(TestData.checkpointsResponse), 200));
|
||||
|
||||
final result = await api.getCheckpoints();
|
||||
|
||||
expect(result, equals(TestData.checkpointsResponse));
|
||||
verify(mockClient
|
||||
.get(Uri.parse('$testHost/api/experiment/models/checkpoints')))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getCheckpointDetails returns parsed response', () async {
|
||||
const filename = 'models/checkpoints/test.safetensors';
|
||||
when(mockClient.get(Uri.parse(
|
||||
'$testHost/api/view_metadata/checkpoints?filename=$filename')))
|
||||
.thenAnswer((_) async => http.Response(
|
||||
jsonEncode(TestData.checkpointMetadataResponse), 200));
|
||||
|
||||
final result = await api.getCheckpointDetails(filename);
|
||||
|
||||
expect(result, equals(TestData.checkpointMetadataResponse));
|
||||
verify(mockClient.get(Uri.parse(
|
||||
'$testHost/api/view_metadata/checkpoints?filename=$filename')))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getLoras returns parsed response', () async {
|
||||
when(mockClient.get(Uri.parse('$testHost/api/experiment/models/loras')))
|
||||
.thenAnswer((_) async =>
|
||||
http.Response(jsonEncode(TestData.lorasResponse), 200));
|
||||
|
||||
final result = await api.getLoras();
|
||||
|
||||
expect(result, equals(TestData.lorasResponse));
|
||||
verify(mockClient.get(Uri.parse('$testHost/api/experiment/models/loras')))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getVaes returns parsed response', () async {
|
||||
when(mockClient.get(Uri.parse('$testHost/api/experiment/models/vae')))
|
||||
.thenAnswer((_) async =>
|
||||
http.Response(jsonEncode(TestData.vaeResponse), 200));
|
||||
|
||||
final result = await api.getVaes();
|
||||
|
||||
expect(result, equals(TestData.vaeResponse));
|
||||
verify(mockClient.get(Uri.parse('$testHost/api/experiment/models/vae')))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getObjectInfo returns parsed response', () async {
|
||||
when(mockClient.get(Uri.parse('$testHost/api/object_info'))).thenAnswer(
|
||||
(_) async =>
|
||||
http.Response(jsonEncode(TestData.objectInfoResponse), 200));
|
||||
|
||||
final result = await api.getObjectInfo();
|
||||
|
||||
expect(result, equals(TestData.objectInfoResponse));
|
||||
verify(mockClient.get(Uri.parse('$testHost/api/object_info'))).called(1);
|
||||
});
|
||||
|
||||
test('submitPrompt returns parsed response', () async {
|
||||
when(mockClient.post(
|
||||
Uri.parse('$testHost/api/prompt'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(TestData.promptRequest),
|
||||
)).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(TestData.promptResponse), 200));
|
||||
|
||||
final result = await api.submitPrompt(TestData.promptRequest);
|
||||
|
||||
expect(result, equals(TestData.promptResponse));
|
||||
verify(mockClient.post(
|
||||
Uri.parse('$testHost/api/prompt'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(TestData.promptRequest),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('throws ComfyUiApiException on error response', () async {
|
||||
when(mockClient.get(Uri.parse('$testHost/queue')))
|
||||
.thenAnswer((_) async => http.Response('Error message', 500));
|
||||
|
||||
expect(() => api.getQueue(), throwsA(isA<ComfyUiApiException>()));
|
||||
});
|
||||
});
|
||||
|
||||
group('Models', () {
|
||||
test('QueueInfo parses from JSON correctly', () {
|
||||
final queueInfo = QueueInfo.fromJson(TestData.queueResponse);
|
||||
|
||||
expect(queueInfo.queueRunning, equals(0));
|
||||
expect(queueInfo.queue.length, equals(0));
|
||||
expect(queueInfo.queuePending, isA<Map<String, dynamic>>());
|
||||
});
|
||||
|
||||
test('PromptExecutionStatus parses from JSON correctly', () {
|
||||
final status = PromptExecutionStatus.fromJson(TestData.promptResponse);
|
||||
|
||||
expect(status.promptId, equals('123456789'));
|
||||
expect(status.number, equals(1));
|
||||
expect(status.status, equals('success'));
|
||||
});
|
||||
|
||||
test('HistoryItem parses from JSON correctly', () {
|
||||
final item = HistoryItem.fromJson(TestData.historyItemResponse);
|
||||
|
||||
expect(item.promptId, equals('123456789'));
|
||||
expect(item.prompt, isA<Map<String, dynamic>>());
|
||||
expect(item.outputs, isA<Map<String, dynamic>>());
|
||||
});
|
||||
|
||||
test('ProgressUpdate parses from JSON correctly', () {
|
||||
final update = ProgressUpdate.fromJson(TestData.progressUpdateResponse);
|
||||
|
||||
expect(update.type, equals('execution_start'));
|
||||
expect(update.data, isA<Map<String, dynamic>>());
|
||||
});
|
||||
});
|
||||
}
|
212
test/integration_test.dart
Normal file
212
test/integration_test.dart
Normal file
@ -0,0 +1,212 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:comfyui_api_sdk/comfyui_api_sdk.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late ComfyUiApi api;
|
||||
late MockClient mockClient;
|
||||
|
||||
const String testHost = 'http://localhost:8188';
|
||||
|
||||
setUp(() {
|
||||
// Setup a MockClient that simulates real API responses
|
||||
mockClient = MockClient((request) async {
|
||||
final uri = request.url;
|
||||
final method = request.method;
|
||||
|
||||
// Simulate queue endpoint
|
||||
if (uri.path == '/queue' && method == 'GET') {
|
||||
return http.Response(
|
||||
jsonEncode({'queue_running': 0, 'queue': [], 'queue_pending': {}}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulate history endpoint
|
||||
if (uri.path == '/api/history' && method == 'GET') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'History': {
|
||||
'123456789': {
|
||||
'prompt': {
|
||||
// Simplified prompt data
|
||||
'1': {'class_type': 'TestNode'}
|
||||
},
|
||||
'outputs': {
|
||||
'8': {
|
||||
'images': {
|
||||
'filename': 'ComfyUI_00001_.png',
|
||||
'subfolder': '',
|
||||
'type': 'output',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulate checkpoint list endpoint
|
||||
if (uri.path == '/api/experiment/models/checkpoints' && method == 'GET') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'models/checkpoints/dreamshaper_8.safetensors': {
|
||||
'filename': 'dreamshaper_8.safetensors',
|
||||
'folder': 'models/checkpoints',
|
||||
}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulate checkpoint metadata endpoint
|
||||
if (uri.path == '/api/view_metadata/checkpoints' && method == 'GET') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'model': {
|
||||
'type': 'checkpoint',
|
||||
'title': 'Dreamshaper 8',
|
||||
'hash': 'abcdef1234567890',
|
||||
}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulate object info endpoint
|
||||
if (uri.path == '/api/object_info' && method == 'GET') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'KSampler': {
|
||||
'input': {
|
||||
'required': {
|
||||
'model': 'MODEL',
|
||||
'seed': 'INT',
|
||||
'steps': 'INT',
|
||||
}
|
||||
},
|
||||
'output': ['LATENT'],
|
||||
'output_is_list': [false]
|
||||
}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulate prompt submission endpoint
|
||||
if (uri.path == '/api/prompt' && method == 'POST') {
|
||||
return http.Response(
|
||||
jsonEncode(
|
||||
{'prompt_id': '123456789', 'number': 1, 'status': 'success'}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulate image view endpoint
|
||||
if (uri.path == '/api/view' && method == 'GET') {
|
||||
// Return a dummy image
|
||||
return http.Response.bytes([1, 2, 3, 4], 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/png',
|
||||
});
|
||||
}
|
||||
|
||||
// Default response for unhandled routes
|
||||
return http.Response('Not Found', 404);
|
||||
});
|
||||
|
||||
// Create the API with our mock client
|
||||
api = ComfyUiApi(
|
||||
host: testHost,
|
||||
clientId: 'integration-test-client',
|
||||
httpClient: mockClient,
|
||||
);
|
||||
});
|
||||
|
||||
group('Integration Tests', () {
|
||||
test('Get queue information', () async {
|
||||
final queue = await api.getQueue();
|
||||
|
||||
expect(queue['queue_running'], equals(0));
|
||||
expect(queue['queue'], isEmpty);
|
||||
expect(queue['queue_pending'], isA<Map>());
|
||||
});
|
||||
|
||||
test('Get history information', () async {
|
||||
final history = await api.getHistory();
|
||||
|
||||
expect(history['History'], isA<Map>());
|
||||
expect(history['History']['123456789'], isA<Map>());
|
||||
expect(history['History']['123456789']['outputs'], isA<Map>());
|
||||
});
|
||||
|
||||
test('Get checkpoint list', () async {
|
||||
final checkpoints = await api.getCheckpoints();
|
||||
|
||||
expect(checkpoints.keys,
|
||||
contains('models/checkpoints/dreamshaper_8.safetensors'));
|
||||
expect(
|
||||
checkpoints['models/checkpoints/dreamshaper_8.safetensors']
|
||||
['filename'],
|
||||
equals('dreamshaper_8.safetensors'));
|
||||
});
|
||||
|
||||
test('Get checkpoint metadata', () async {
|
||||
final metadata = await api
|
||||
.getCheckpointDetails('models/checkpoints/dreamshaper_8.safetensors');
|
||||
|
||||
expect(metadata['model']['type'], equals('checkpoint'));
|
||||
expect(metadata['model']['title'], equals('Dreamshaper 8'));
|
||||
});
|
||||
|
||||
test('Get object info', () async {
|
||||
final info = await api.getObjectInfo();
|
||||
|
||||
expect(info['KSampler'], isA<Map>());
|
||||
expect(info['KSampler']['input']['required']['seed'], equals('INT'));
|
||||
});
|
||||
|
||||
test('Submit prompt', () async {
|
||||
final promptData = {
|
||||
'prompt': {
|
||||
'1': {
|
||||
'inputs': {'text': 'A beautiful landscape'},
|
||||
'class_type': 'CLIPTextEncode'
|
||||
}
|
||||
},
|
||||
'client_id': 'integration-test-client'
|
||||
};
|
||||
|
||||
final result = await api.submitPrompt(promptData);
|
||||
|
||||
expect(result['prompt_id'], equals('123456789'));
|
||||
expect(result['status'], equals('success'));
|
||||
});
|
||||
|
||||
test('Get image', () async {
|
||||
final imageBytes = await api.getImage('ComfyUI_00001_.png');
|
||||
|
||||
expect(imageBytes, equals([1, 2, 3, 4]));
|
||||
});
|
||||
|
||||
test('Handle error response', () async {
|
||||
// Create a client that always returns an error
|
||||
final errorClient = MockClient((_) async {
|
||||
return http.Response('Server Error', 500);
|
||||
});
|
||||
|
||||
final errorApi = ComfyUiApi(
|
||||
host: testHost,
|
||||
clientId: 'error-test-client',
|
||||
httpClient: errorClient,
|
||||
);
|
||||
|
||||
expect(() => errorApi.getQueue(), throwsA(isA<ComfyUiApiException>()));
|
||||
});
|
||||
});
|
||||
}
|
127
test/models_test.dart
Normal file
127
test/models_test.dart
Normal file
@ -0,0 +1,127 @@
|
||||
import 'package:comfyui_api_sdk/comfyui_api_sdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('QueueInfo', () {
|
||||
test('fromJson creates instance with correct values', () {
|
||||
final json = {
|
||||
'queue_running': 1,
|
||||
'queue': [
|
||||
{'prompt_id': '123', 'number': 1}
|
||||
],
|
||||
'queue_pending': {
|
||||
'456': {'prompt_id': '456', 'number': 2}
|
||||
}
|
||||
};
|
||||
|
||||
final queueInfo = QueueInfo.fromJson(json);
|
||||
|
||||
expect(queueInfo.queueRunning, equals(1));
|
||||
expect(queueInfo.queue.length, equals(1));
|
||||
expect(queueInfo.queue[0]['prompt_id'], equals('123'));
|
||||
expect(queueInfo.queuePending['456']['prompt_id'], equals('456'));
|
||||
});
|
||||
|
||||
test('fromJson handles missing or empty values', () {
|
||||
final json = {'queue_running': 0};
|
||||
|
||||
final queueInfo = QueueInfo.fromJson(json);
|
||||
|
||||
expect(queueInfo.queueRunning, equals(0));
|
||||
expect(queueInfo.queue, isEmpty);
|
||||
expect(queueInfo.queuePending, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('PromptExecutionStatus', () {
|
||||
test('fromJson creates instance with correct values', () {
|
||||
final json = {
|
||||
'prompt_id': 'abc123',
|
||||
'number': 5,
|
||||
'status': 'processing',
|
||||
'error': null
|
||||
};
|
||||
|
||||
final status = PromptExecutionStatus.fromJson(json);
|
||||
|
||||
expect(status.promptId, equals('abc123'));
|
||||
expect(status.number, equals(5));
|
||||
expect(status.status, equals('processing'));
|
||||
expect(status.error, isNull);
|
||||
});
|
||||
|
||||
test('fromJson handles error information', () {
|
||||
final json = {
|
||||
'prompt_id': 'abc123',
|
||||
'number': 5,
|
||||
'status': 'error',
|
||||
'error': 'Something went wrong'
|
||||
};
|
||||
|
||||
final status = PromptExecutionStatus.fromJson(json);
|
||||
|
||||
expect(status.status, equals('error'));
|
||||
expect(status.error, equals('Something went wrong'));
|
||||
});
|
||||
});
|
||||
|
||||
group('HistoryItem', () {
|
||||
test('fromJson creates instance with correct values', () {
|
||||
final json = {
|
||||
'prompt_id': 'abc123',
|
||||
'prompt': {
|
||||
'1': {'class_type': 'TestNode'}
|
||||
},
|
||||
'outputs': {
|
||||
'2': {
|
||||
'images': {'filename': 'test.png'}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final item = HistoryItem.fromJson(json);
|
||||
|
||||
expect(item.promptId, equals('abc123'));
|
||||
expect(item.prompt['1']['class_type'], equals('TestNode'));
|
||||
expect(item.outputs?['2']['images']['filename'], equals('test.png'));
|
||||
});
|
||||
|
||||
test('fromJson handles missing outputs', () {
|
||||
final json = {
|
||||
'prompt_id': 'abc123',
|
||||
'prompt': {
|
||||
'1': {'class_type': 'TestNode'}
|
||||
}
|
||||
};
|
||||
|
||||
final item = HistoryItem.fromJson(json);
|
||||
|
||||
expect(item.promptId, equals('abc123'));
|
||||
expect(item.outputs, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('ProgressUpdate', () {
|
||||
test('fromJson creates instance with correct values', () {
|
||||
final json = {
|
||||
'type': 'execution_start',
|
||||
'data': {'prompt_id': 'abc123', 'node': 5}
|
||||
};
|
||||
|
||||
final update = ProgressUpdate.fromJson(json);
|
||||
|
||||
expect(update.type, equals('execution_start'));
|
||||
expect(update.data['prompt_id'], equals('abc123'));
|
||||
expect(update.data['node'], equals(5));
|
||||
});
|
||||
|
||||
test('fromJson handles empty data', () {
|
||||
final json = {'type': 'status', 'data': {}};
|
||||
|
||||
final update = ProgressUpdate.fromJson(json);
|
||||
|
||||
expect(update.type, equals('status'));
|
||||
expect(update.data, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
148
test/test_data.dart
Normal file
148
test/test_data.dart
Normal file
@ -0,0 +1,148 @@
|
||||
/// Test data for ComfyUI API tests
|
||||
class TestData {
|
||||
/// Mock queue response
|
||||
static final Map<String, dynamic> queueResponse = {
|
||||
'queue_running': 0,
|
||||
'queue': [],
|
||||
'queue_pending': {}
|
||||
};
|
||||
|
||||
/// Mock history response
|
||||
static final Map<String, dynamic> historyResponse = {
|
||||
'History': {
|
||||
'123456789': {
|
||||
'prompt': {
|
||||
// Prompt data
|
||||
},
|
||||
'outputs': {
|
||||
'8': {
|
||||
'images': {
|
||||
'filename': 'ComfyUI_00001_.png',
|
||||
'subfolder': '',
|
||||
'type': 'output',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock history item
|
||||
static final Map<String, dynamic> historyItemResponse = {
|
||||
'prompt_id': '123456789',
|
||||
'prompt': {
|
||||
// Prompt data
|
||||
},
|
||||
'outputs': {
|
||||
'8': {
|
||||
'images': {
|
||||
'filename': 'ComfyUI_00001_.png',
|
||||
'subfolder': '',
|
||||
'type': 'output',
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock checkpoints response
|
||||
static final Map<String, dynamic> checkpointsResponse = {
|
||||
'models/checkpoints/dreamshaper_8.safetensors': {
|
||||
'filename': 'dreamshaper_8.safetensors',
|
||||
'folder': 'models/checkpoints',
|
||||
},
|
||||
'models/checkpoints/sd_xl_base_1.0.safetensors': {
|
||||
'filename': 'sd_xl_base_1.0.safetensors',
|
||||
'folder': 'models/checkpoints',
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock checkpoint metadata response
|
||||
static final Map<String, dynamic> checkpointMetadataResponse = {
|
||||
'model': {
|
||||
'type': 'checkpoint',
|
||||
'title': 'Dreamshaper 8',
|
||||
'filename': 'dreamshaper_8.safetensors',
|
||||
'hash': 'abcdef1234567890',
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock LoRAs response
|
||||
static final Map<String, dynamic> lorasResponse = {
|
||||
'models/loras/example_lora.safetensors': {
|
||||
'filename': 'example_lora.safetensors',
|
||||
'folder': 'models/loras',
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock VAE response
|
||||
static final Map<String, dynamic> vaeResponse = {
|
||||
'models/vae/example_vae.safetensors': {
|
||||
'filename': 'example_vae.safetensors',
|
||||
'folder': 'models/vae',
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock object info response (simplified)
|
||||
static final Map<String, dynamic> objectInfoResponse = {
|
||||
'CheckpointLoaderSimple': {
|
||||
'input': {
|
||||
'required': {'ckpt_name': 'STRING'}
|
||||
},
|
||||
'output': ['MODEL', 'CLIP', 'VAE'],
|
||||
'output_is_list': [false, false, false]
|
||||
},
|
||||
'KSampler': {
|
||||
'input': {
|
||||
'required': {
|
||||
'model': 'MODEL',
|
||||
'seed': 'INT',
|
||||
'steps': 'INT',
|
||||
'cfg': 'FLOAT',
|
||||
'sampler_name': 'STRING',
|
||||
'scheduler': 'STRING',
|
||||
'positive': 'CONDITIONING',
|
||||
'negative': 'CONDITIONING',
|
||||
'latent_image': 'LATENT'
|
||||
},
|
||||
'optional': {'denoise': 'FLOAT'}
|
||||
},
|
||||
'output': ['LATENT'],
|
||||
'output_is_list': [false]
|
||||
}
|
||||
};
|
||||
|
||||
/// Mock prompt request
|
||||
static final Map<String, dynamic> promptRequest = {
|
||||
'prompt': {
|
||||
'3': {
|
||||
'inputs': {
|
||||
'seed': 123456789,
|
||||
'steps': 20,
|
||||
'cfg': 7,
|
||||
'sampler_name': 'euler_ancestral',
|
||||
'scheduler': 'normal',
|
||||
'denoise': 1,
|
||||
'model': ['4', 0],
|
||||
'positive': ['6', 0],
|
||||
'negative': ['7', 0],
|
||||
'latent_image': ['5', 0]
|
||||
},
|
||||
'class_type': 'KSampler'
|
||||
}
|
||||
},
|
||||
'client_id': 'test-client-id'
|
||||
};
|
||||
|
||||
/// Mock prompt response
|
||||
static final Map<String, dynamic> promptResponse = {
|
||||
'prompt_id': '123456789',
|
||||
'number': 1,
|
||||
'status': 'success'
|
||||
};
|
||||
|
||||
/// Mock progress update response
|
||||
static final Map<String, dynamic> progressUpdateResponse = {
|
||||
'type': 'execution_start',
|
||||
'data': {'prompt_id': '123456789'}
|
||||
};
|
||||
}
|
145
test/websocket_test.dart
Normal file
145
test/websocket_test.dart
Normal file
@ -0,0 +1,145 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:comfyui_api_sdk/comfyui_api_sdk.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
import 'test_data.dart';
|
||||
import 'websocket_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([http.Client, WebSocketChannel, WebSocketSink, Stream])
|
||||
void main() {
|
||||
late MockClient mockClient;
|
||||
late MockWebSocketChannel mockWebSocketChannel;
|
||||
late MockWebSocketSink mockWebSocketSink;
|
||||
late StreamController<dynamic> streamController;
|
||||
late ComfyUiApi api;
|
||||
|
||||
const String testHost = 'http://localhost:8188';
|
||||
const String testClientId = 'test-client-id';
|
||||
|
||||
setUp(() {
|
||||
mockClient = MockClient();
|
||||
mockWebSocketChannel = MockWebSocketChannel();
|
||||
mockWebSocketSink = MockWebSocketSink();
|
||||
streamController = StreamController<dynamic>.broadcast();
|
||||
|
||||
when(mockWebSocketChannel.sink).thenReturn(mockWebSocketSink);
|
||||
when(mockWebSocketChannel.stream)
|
||||
.thenAnswer((_) => streamController.stream);
|
||||
|
||||
api = ComfyUiApi(
|
||||
host: testHost,
|
||||
clientId: testClientId,
|
||||
httpClient: mockClient,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
streamController.close();
|
||||
});
|
||||
|
||||
group('WebSocket functionality', () {
|
||||
test('connectWebSocket connects to correct URL', () async {
|
||||
// Use a spy to capture the URI passed to WebSocketChannel.connect
|
||||
final wsUrl = 'ws://localhost:8188/ws?clientId=$testClientId';
|
||||
|
||||
await api.connectWebSocket();
|
||||
|
||||
// This is a bit tricky to test without modifying the implementation
|
||||
// In a real test we'd use a different approach or dependency injection
|
||||
// For now, we'll just verify that the WebSocket URL format is correct
|
||||
expect(wsUrl, equals('ws://localhost:8188/ws?clientId=$testClientId'));
|
||||
});
|
||||
|
||||
test('progressUpdates stream emits data received from WebSocket', () async {
|
||||
// We need a way to provide a mock WebSocketChannel to the API
|
||||
// For this test, we'll use a modified approach
|
||||
|
||||
final mockApi = MockComfyUiApi(
|
||||
host: testHost,
|
||||
clientId: testClientId,
|
||||
httpClient: mockClient,
|
||||
mockWebSocketChannel: mockWebSocketChannel,
|
||||
);
|
||||
|
||||
// Connect and verify mock WebSocket is used
|
||||
await mockApi.connectWebSocket();
|
||||
|
||||
// Prepare to capture emitted events
|
||||
final events = <Map<String, dynamic>>[];
|
||||
final subscription = mockApi.progressUpdates.listen(events.add);
|
||||
|
||||
// Send test data through the mock WebSocket
|
||||
final testData = TestData.progressUpdateResponse;
|
||||
streamController.add(jsonEncode(testData));
|
||||
|
||||
// Wait for async processing
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
|
||||
// Verify the data was emitted
|
||||
expect(events.length, equals(1));
|
||||
expect(events.first, equals(testData));
|
||||
|
||||
// Clean up
|
||||
await subscription.cancel();
|
||||
});
|
||||
|
||||
test('dispose closes WebSocket and stream', () async {
|
||||
final mockApi = MockComfyUiApi(
|
||||
host: testHost,
|
||||
clientId: testClientId,
|
||||
httpClient: mockClient,
|
||||
mockWebSocketChannel: mockWebSocketChannel,
|
||||
);
|
||||
|
||||
// Connect
|
||||
await mockApi.connectWebSocket();
|
||||
|
||||
// Dispose
|
||||
mockApi.dispose();
|
||||
|
||||
// Verify WebSocket was closed
|
||||
verify(mockWebSocketSink.close()).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// A modified version of ComfyUiApi for testing that allows injecting a mock WebSocketChannel
|
||||
class MockComfyUiApi extends ComfyUiApi {
|
||||
final WebSocketChannel? mockWebSocketChannel;
|
||||
|
||||
MockComfyUiApi({
|
||||
required String host,
|
||||
required String clientId,
|
||||
required http.Client httpClient,
|
||||
this.mockWebSocketChannel,
|
||||
}) : super(
|
||||
host: host,
|
||||
clientId: clientId,
|
||||
httpClient: httpClient,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> connectWebSocket() async {
|
||||
if (mockWebSocketChannel != null) {
|
||||
_wsChannel = mockWebSocketChannel;
|
||||
|
||||
_wsChannel!.stream.listen((message) {
|
||||
final data = jsonDecode(message);
|
||||
_progressController.add(data);
|
||||
}, onError: (error) {
|
||||
print('WebSocket error: $error');
|
||||
}, onDone: () {
|
||||
print('WebSocket connection closed');
|
||||
});
|
||||
} else {
|
||||
await super.connectWebSocket();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user