comfyui-ruby 0.1.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +35 -0
- data/lib/comfyui/client.rb +290 -0
- data/lib/comfyui/result.rb +15 -0
- data/lib/comfyui/version.rb +3 -0
- data/lib/comfyui/workflow.rb +155 -0
- data/lib/comfyui.rb +15 -0
- metadata +81 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 318858dd755608980d1b251b0d5271458646ee04aeba16167d4590e4293bf175
|
|
4
|
+
data.tar.gz: e23e068a97520ab5d5d7f3de2077071b9c817e5edbdd9a1bd0bdb2d32bf2badd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 25d9e51bc4582af348695f1b762c82a6e7a686b3dbe8ff908355c9c8e874a75526dd851d5853106e39f3cd18110f915345b1056828a3bb6e5823436f40aaf928
|
|
7
|
+
data.tar.gz: 0a32aba52fda57833fe399dd49cec55a81ab05f4e5b166e9455748a770c65d98bc402a293b58d2fdca6be51ef9b08ad38a7e55ac9ca31c17f21af2952c9e36e3
|
data/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Comfyui::Ruby
|
|
2
|
+
|
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
|
4
|
+
|
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/comfyui/ruby`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
|
+
|
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/comfyui-ruby.
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module ComfyUI
|
|
8
|
+
# Ruby client for the ComfyUI HTTP + WebSocket API.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# client = ComfyUI::Client.new("http://localhost:8188")
|
|
12
|
+
# client.system_stats
|
|
13
|
+
# client.models
|
|
14
|
+
# client.generate(prompt: "a cat in space")
|
|
15
|
+
#
|
|
16
|
+
class Client
|
|
17
|
+
attr_reader :url
|
|
18
|
+
|
|
19
|
+
# @param url [String] ComfyUI base URL
|
|
20
|
+
# @param timeout [Integer] HTTP timeout in seconds
|
|
21
|
+
def initialize(url = nil, timeout: 30)
|
|
22
|
+
@url = url || ENV.fetch("COMFYUI_URL", DEFAULT_URL)
|
|
23
|
+
@conn = Faraday.new(url: @url) do |f|
|
|
24
|
+
f.options.timeout = timeout
|
|
25
|
+
f.options.open_timeout = 10
|
|
26
|
+
f.request :json
|
|
27
|
+
f.response :json
|
|
28
|
+
f.response :raise_error
|
|
29
|
+
end
|
|
30
|
+
@raw_conn = Faraday.new(url: @url) do |f|
|
|
31
|
+
f.options.timeout = timeout
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# ----------------------------------------------------------------
|
|
36
|
+
# Query endpoints
|
|
37
|
+
# ----------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
# Get system stats (GPU, RAM, etc.)
|
|
40
|
+
# @return [Hash]
|
|
41
|
+
def system_stats
|
|
42
|
+
get("/system_stats")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get queue status.
|
|
46
|
+
# @return [Hash] with queue_running and queue_pending
|
|
47
|
+
def queue_status
|
|
48
|
+
get("/queue")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Clear the queue.
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def clear_queue
|
|
54
|
+
post("/queue", {clear: true})
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get available nodes and their configurations.
|
|
59
|
+
# @return [Hash]
|
|
60
|
+
def object_info
|
|
61
|
+
get("/object_info")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get available models grouped by type.
|
|
65
|
+
# @return [Hash{String => Array<String>}]
|
|
66
|
+
def models
|
|
67
|
+
info = object_info
|
|
68
|
+
result = {}
|
|
69
|
+
|
|
70
|
+
model_types = {
|
|
71
|
+
"checkpoints" => ["CheckpointLoaderSimple", "ckpt_name"],
|
|
72
|
+
"loras" => ["LoraLoader", "lora_name"],
|
|
73
|
+
"vae" => ["VAELoader", "vae_name"],
|
|
74
|
+
"clip" => ["CLIPLoader", "clip_name"],
|
|
75
|
+
"controlnet" => ["ControlNetLoader", "control_net_name"],
|
|
76
|
+
"upscale_models" => ["UpscaleModelLoader", "model_name"]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
model_types.each do |type, (node_class, input_name)|
|
|
80
|
+
next unless info[node_class]
|
|
81
|
+
|
|
82
|
+
inputs = info.dig(node_class, "input", "required") || {}
|
|
83
|
+
next unless inputs[input_name]
|
|
84
|
+
|
|
85
|
+
input_def = inputs[input_name]
|
|
86
|
+
if input_def.is_a?(Array) && input_def[0].is_a?(Array)
|
|
87
|
+
result[type] = input_def[0]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Get generation history.
|
|
95
|
+
# @param prompt_id [String, nil] specific prompt ID (nil = recent history)
|
|
96
|
+
# @param max_items [Integer] maximum items to return
|
|
97
|
+
# @return [Hash]
|
|
98
|
+
def history(prompt_id: nil, max_items: 100)
|
|
99
|
+
if prompt_id
|
|
100
|
+
get("/history/#{prompt_id}")
|
|
101
|
+
else
|
|
102
|
+
get("/history", max_items: max_items)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# ----------------------------------------------------------------
|
|
107
|
+
# Workflow execution
|
|
108
|
+
# ----------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
# Queue a workflow for execution.
|
|
111
|
+
# @param workflow [Hash] ComfyUI API format workflow
|
|
112
|
+
# @param client_id [String] client ID for WebSocket tracking
|
|
113
|
+
# @return [Hash] with prompt_id and number
|
|
114
|
+
def queue_prompt(workflow, client_id: nil)
|
|
115
|
+
client_id ||= SecureRandom.uuid
|
|
116
|
+
payload = {prompt: workflow, client_id: client_id}
|
|
117
|
+
result = post("/prompt", payload)
|
|
118
|
+
|
|
119
|
+
if result["error"]
|
|
120
|
+
raise APIError, "Workflow error: #{result["error"]}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
result
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Run a workflow and wait for completion via polling.
|
|
127
|
+
# @param workflow [Hash] ComfyUI API format workflow
|
|
128
|
+
# @param timeout [Float] max wait in seconds
|
|
129
|
+
# @param on_progress [Proc, nil] callback(step, total, message)
|
|
130
|
+
# @return [WorkflowResult]
|
|
131
|
+
def run_workflow(workflow, timeout: 600, on_progress: nil)
|
|
132
|
+
client_id = SecureRandom.uuid
|
|
133
|
+
result = queue_prompt(workflow, client_id: client_id)
|
|
134
|
+
prompt_id = result["prompt_id"]
|
|
135
|
+
|
|
136
|
+
poll_for_completion(prompt_id, timeout: timeout, on_progress: on_progress)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Generate an image with a simple text-to-image workflow.
|
|
140
|
+
# @param prompt [String] positive prompt
|
|
141
|
+
# @param negative_prompt [String] negative prompt
|
|
142
|
+
# @param model [String, nil] checkpoint name (nil = first available)
|
|
143
|
+
# @param width [Integer] image width
|
|
144
|
+
# @param height [Integer] image height
|
|
145
|
+
# @param steps [Integer] sampling steps
|
|
146
|
+
# @param cfg [Float] CFG scale
|
|
147
|
+
# @param seed [Integer] seed (-1 = random)
|
|
148
|
+
# @param sampler [String] sampler name
|
|
149
|
+
# @param scheduler [String] scheduler name
|
|
150
|
+
# @param lora_name [String, nil] LoRA filename
|
|
151
|
+
# @param lora_strength [Float] LoRA strength
|
|
152
|
+
# @param batch_size [Integer] images per batch
|
|
153
|
+
# @param vae [String, nil] VAE filename
|
|
154
|
+
# @param timeout [Float] max wait in seconds
|
|
155
|
+
# @param on_progress [Proc, nil] callback(step, total, message)
|
|
156
|
+
# @return [GenerationResult]
|
|
157
|
+
def generate(
|
|
158
|
+
prompt:,
|
|
159
|
+
negative_prompt: "",
|
|
160
|
+
model: nil,
|
|
161
|
+
width: 1024,
|
|
162
|
+
height: 1024,
|
|
163
|
+
steps: 20,
|
|
164
|
+
cfg: 7.0,
|
|
165
|
+
seed: -1,
|
|
166
|
+
sampler: "euler",
|
|
167
|
+
scheduler: "normal",
|
|
168
|
+
lora_name: nil,
|
|
169
|
+
lora_strength: 1.0,
|
|
170
|
+
batch_size: 1,
|
|
171
|
+
vae: nil,
|
|
172
|
+
timeout: 600,
|
|
173
|
+
on_progress: nil
|
|
174
|
+
)
|
|
175
|
+
# Auto-select first checkpoint if none specified
|
|
176
|
+
unless model
|
|
177
|
+
available = models
|
|
178
|
+
model = available["checkpoints"]&.first
|
|
179
|
+
raise Error, "No checkpoints available" unless model
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
workflow = ComfyUI::Workflow.build(
|
|
183
|
+
prompt: prompt,
|
|
184
|
+
negative_prompt: negative_prompt,
|
|
185
|
+
model: model,
|
|
186
|
+
width: width,
|
|
187
|
+
height: height,
|
|
188
|
+
steps: steps,
|
|
189
|
+
cfg: cfg,
|
|
190
|
+
seed: seed,
|
|
191
|
+
sampler: sampler,
|
|
192
|
+
scheduler: scheduler,
|
|
193
|
+
lora_name: lora_name,
|
|
194
|
+
lora_strength: lora_strength,
|
|
195
|
+
batch_size: batch_size,
|
|
196
|
+
vae: vae
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
result = run_workflow(workflow, timeout: timeout, on_progress: on_progress)
|
|
200
|
+
|
|
201
|
+
images = result.outputs.each_with_object([]) do |(_node_id, output), acc|
|
|
202
|
+
next unless output["images"]
|
|
203
|
+
|
|
204
|
+
output["images"].each do |img_info|
|
|
205
|
+
filename = img_info["filename"]
|
|
206
|
+
subfolder = img_info["subfolder"] || ""
|
|
207
|
+
type = img_info["type"] || "output"
|
|
208
|
+
acc << {filename: filename, subfolder: subfolder, type: type} if type == "output"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
GenerationResult.new(
|
|
213
|
+
prompt_id: result.prompt_id,
|
|
214
|
+
images: images,
|
|
215
|
+
node_errors: result.node_errors,
|
|
216
|
+
success: result.success
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Download a generated image.
|
|
221
|
+
# @param filename [String] image filename
|
|
222
|
+
# @param subfolder [String] subfolder
|
|
223
|
+
# @param folder_type [String] output, input, or temp
|
|
224
|
+
# @return [String] image bytes
|
|
225
|
+
def image(filename, subfolder: "", folder_type: "output")
|
|
226
|
+
response = @raw_conn.get("/view", {
|
|
227
|
+
filename: filename,
|
|
228
|
+
subfolder: subfolder,
|
|
229
|
+
type: folder_type
|
|
230
|
+
})
|
|
231
|
+
response.body
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
def get(path, params = {})
|
|
237
|
+
response = @conn.get(path, params)
|
|
238
|
+
response.body
|
|
239
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
240
|
+
raise ConnectionError, "Failed to connect to ComfyUI at #{@url}: #{e.message}"
|
|
241
|
+
rescue Faraday::ClientError, Faraday::ServerError => e
|
|
242
|
+
raise APIError, "ComfyUI API error: #{e.message}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def post(path, body = {})
|
|
246
|
+
response = @conn.post(path) { |req| req.body = body }
|
|
247
|
+
response.body
|
|
248
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
249
|
+
raise ConnectionError, "Failed to connect to ComfyUI at #{@url}: #{e.message}"
|
|
250
|
+
rescue Faraday::ClientError, Faraday::ServerError => e
|
|
251
|
+
raise APIError, "ComfyUI API error: #{e.message}"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def poll_for_completion(prompt_id, timeout: 600, on_progress: nil)
|
|
255
|
+
start = Time.now
|
|
256
|
+
interval = 0.5
|
|
257
|
+
|
|
258
|
+
loop do
|
|
259
|
+
elapsed = Time.now - start
|
|
260
|
+
raise TimeoutError, "Workflow did not complete within #{timeout}s" if elapsed > timeout
|
|
261
|
+
|
|
262
|
+
hist = history(prompt_id: prompt_id)
|
|
263
|
+
|
|
264
|
+
if hist[prompt_id]
|
|
265
|
+
entry = hist[prompt_id]
|
|
266
|
+
outputs = entry["outputs"] || {}
|
|
267
|
+
status = entry.dig("status", "status_str")
|
|
268
|
+
|
|
269
|
+
if status == "error"
|
|
270
|
+
return WorkflowResult.new(
|
|
271
|
+
prompt_id: prompt_id,
|
|
272
|
+
outputs: outputs,
|
|
273
|
+
node_errors: entry.dig("status", "messages") || {},
|
|
274
|
+
success: false
|
|
275
|
+
)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
return WorkflowResult.new(
|
|
279
|
+
prompt_id: prompt_id,
|
|
280
|
+
outputs: outputs,
|
|
281
|
+
success: true
|
|
282
|
+
)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
on_progress&.call(0, 0, "Running...")
|
|
286
|
+
sleep interval
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComfyUI
|
|
4
|
+
GenerationResult = Data.define(:prompt_id, :images, :node_errors, :success) do
|
|
5
|
+
def initialize(prompt_id:, images: [], node_errors: {}, success: true)
|
|
6
|
+
super
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
WorkflowResult = Data.define(:prompt_id, :outputs, :node_errors, :success) do
|
|
11
|
+
def initialize(prompt_id:, outputs: {}, node_errors: {}, success: true)
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module ComfyUI
|
|
7
|
+
# Default SDXL/Illustrious/Pony compatible workflow template.
|
|
8
|
+
# Uses separate VAE loader for better quality with modern models.
|
|
9
|
+
DEFAULT_WORKFLOW = {
|
|
10
|
+
"3" => {
|
|
11
|
+
"class_type" => "KSampler",
|
|
12
|
+
"inputs" => {
|
|
13
|
+
"seed" => 0,
|
|
14
|
+
"steps" => 20,
|
|
15
|
+
"cfg" => 7.0,
|
|
16
|
+
"sampler_name" => "euler",
|
|
17
|
+
"scheduler" => "normal",
|
|
18
|
+
"denoise" => 1.0,
|
|
19
|
+
"model" => ["4", 0],
|
|
20
|
+
"positive" => ["6", 0],
|
|
21
|
+
"negative" => ["7", 0],
|
|
22
|
+
"latent_image" => ["5", 0]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"4" => {
|
|
26
|
+
"class_type" => "CheckpointLoaderSimple",
|
|
27
|
+
"inputs" => {"ckpt_name" => ""}
|
|
28
|
+
},
|
|
29
|
+
"5" => {
|
|
30
|
+
"class_type" => "EmptyLatentImage",
|
|
31
|
+
"inputs" => {"width" => 1024, "height" => 1024, "batch_size" => 1}
|
|
32
|
+
},
|
|
33
|
+
"6" => {
|
|
34
|
+
"class_type" => "CLIPTextEncode",
|
|
35
|
+
"inputs" => {"text" => "", "clip" => ["4", 1]}
|
|
36
|
+
},
|
|
37
|
+
"7" => {
|
|
38
|
+
"class_type" => "CLIPTextEncode",
|
|
39
|
+
"inputs" => {"text" => "", "clip" => ["4", 1]}
|
|
40
|
+
},
|
|
41
|
+
"8" => {
|
|
42
|
+
"class_type" => "VAEDecode",
|
|
43
|
+
"inputs" => {"samples" => ["3", 0], "vae" => ["11", 0]}
|
|
44
|
+
},
|
|
45
|
+
"9" => {
|
|
46
|
+
"class_type" => "SaveImage",
|
|
47
|
+
"inputs" => {"filename_prefix" => "comfy", "images" => ["8", 0]}
|
|
48
|
+
},
|
|
49
|
+
"11" => {
|
|
50
|
+
"class_type" => "VAELoader",
|
|
51
|
+
"inputs" => {"vae_name" => "sdxl_vae.safetensors"}
|
|
52
|
+
}
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
LORA_LOADER_NODE = {
|
|
56
|
+
"class_type" => "LoraLoader",
|
|
57
|
+
"inputs" => {
|
|
58
|
+
"lora_name" => "",
|
|
59
|
+
"strength_model" => 1.0,
|
|
60
|
+
"strength_clip" => 1.0,
|
|
61
|
+
"model" => ["4", 0],
|
|
62
|
+
"clip" => ["4", 1]
|
|
63
|
+
}
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
DEFAULT_VAE = "sdxl_vae.safetensors"
|
|
67
|
+
|
|
68
|
+
module Workflow
|
|
69
|
+
module_function
|
|
70
|
+
|
|
71
|
+
# Build a text-to-image workflow from parameters.
|
|
72
|
+
#
|
|
73
|
+
# @param prompt [String] positive prompt
|
|
74
|
+
# @param negative_prompt [String] negative prompt
|
|
75
|
+
# @param model [String, nil] checkpoint filename
|
|
76
|
+
# @param width [Integer] image width
|
|
77
|
+
# @param height [Integer] image height
|
|
78
|
+
# @param steps [Integer] sampling steps
|
|
79
|
+
# @param cfg [Float] CFG scale
|
|
80
|
+
# @param seed [Integer] random seed (-1 for random)
|
|
81
|
+
# @param sampler [String] sampler name
|
|
82
|
+
# @param scheduler [String] scheduler name
|
|
83
|
+
# @param lora_name [String, nil] LoRA filename
|
|
84
|
+
# @param lora_strength [Float] LoRA strength
|
|
85
|
+
# @param batch_size [Integer] images per batch
|
|
86
|
+
# @param vae [String, nil] VAE filename (nil = use checkpoint's VAE)
|
|
87
|
+
# @return [Hash] ComfyUI API workflow
|
|
88
|
+
def build(
|
|
89
|
+
prompt:,
|
|
90
|
+
negative_prompt: "",
|
|
91
|
+
model: nil,
|
|
92
|
+
width: 1024,
|
|
93
|
+
height: 1024,
|
|
94
|
+
steps: 20,
|
|
95
|
+
cfg: 7.0,
|
|
96
|
+
seed: -1,
|
|
97
|
+
sampler: "euler",
|
|
98
|
+
scheduler: "normal",
|
|
99
|
+
lora_name: nil,
|
|
100
|
+
lora_strength: 1.0,
|
|
101
|
+
batch_size: 1,
|
|
102
|
+
vae: nil
|
|
103
|
+
)
|
|
104
|
+
wf = JSON.parse(JSON.generate(DEFAULT_WORKFLOW)) # deep copy
|
|
105
|
+
|
|
106
|
+
actual_seed = seed >= 0 ? seed : rand(2**32)
|
|
107
|
+
|
|
108
|
+
# KSampler
|
|
109
|
+
wf["3"]["inputs"].merge!(
|
|
110
|
+
"seed" => actual_seed,
|
|
111
|
+
"steps" => steps,
|
|
112
|
+
"cfg" => cfg,
|
|
113
|
+
"sampler_name" => sampler,
|
|
114
|
+
"scheduler" => scheduler
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Checkpoint
|
|
118
|
+
wf["4"]["inputs"]["ckpt_name"] = model if model
|
|
119
|
+
|
|
120
|
+
# Dimensions
|
|
121
|
+
wf["5"]["inputs"].merge!("width" => width, "height" => height, "batch_size" => batch_size)
|
|
122
|
+
|
|
123
|
+
# Prompts
|
|
124
|
+
wf["6"]["inputs"]["text"] = prompt
|
|
125
|
+
wf["7"]["inputs"]["text"] = negative_prompt
|
|
126
|
+
|
|
127
|
+
# VAE
|
|
128
|
+
if vae
|
|
129
|
+
wf["11"]["inputs"]["vae_name"] = vae
|
|
130
|
+
else
|
|
131
|
+
wf.delete("11")
|
|
132
|
+
wf["8"]["inputs"]["vae"] = ["4", 2]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# LoRA injection
|
|
136
|
+
if lora_name
|
|
137
|
+
lora = JSON.parse(JSON.generate(LORA_LOADER_NODE))
|
|
138
|
+
lora["inputs"].merge!(
|
|
139
|
+
"lora_name" => lora_name,
|
|
140
|
+
"strength_model" => lora_strength,
|
|
141
|
+
"strength_clip" => lora_strength,
|
|
142
|
+
"model" => ["4", 0],
|
|
143
|
+
"clip" => ["4", 1]
|
|
144
|
+
)
|
|
145
|
+
wf["10"] = lora
|
|
146
|
+
|
|
147
|
+
wf["3"]["inputs"]["model"] = ["10", 0]
|
|
148
|
+
wf["6"]["inputs"]["clip"] = ["10", 1]
|
|
149
|
+
wf["7"]["inputs"]["clip"] = ["10", 1]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
wf
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
data/lib/comfyui.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "comfyui/version"
|
|
4
|
+
require_relative "comfyui/client"
|
|
5
|
+
require_relative "comfyui/workflow"
|
|
6
|
+
require_relative "comfyui/result"
|
|
7
|
+
|
|
8
|
+
module ComfyUI
|
|
9
|
+
DEFAULT_URL = "http://127.0.0.1:8188"
|
|
10
|
+
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
class ConnectionError < Error; end
|
|
13
|
+
class APIError < Error; end
|
|
14
|
+
class TimeoutError < Error; end
|
|
15
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: comfyui-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.pre1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- aladac
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: faye-websocket
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.11'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.11'
|
|
41
|
+
description: A Ruby client for ComfyUI — query models, queue workflows, generate images,
|
|
42
|
+
and track progress via WebSocket.
|
|
43
|
+
email:
|
|
44
|
+
- aladac@saiden.dev
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/comfyui.rb
|
|
51
|
+
- lib/comfyui/client.rb
|
|
52
|
+
- lib/comfyui/result.rb
|
|
53
|
+
- lib/comfyui/version.rb
|
|
54
|
+
- lib/comfyui/workflow.rb
|
|
55
|
+
homepage: https://github.com/aladac/comfyui-ruby
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata:
|
|
59
|
+
homepage_uri: https://github.com/aladac/comfyui-ruby
|
|
60
|
+
source_code_uri: https://github.com/aladac/comfyui-ruby
|
|
61
|
+
changelog_uri: https://github.com/aladac/comfyui-ruby/blob/main/CHANGELOG.md
|
|
62
|
+
post_install_message:
|
|
63
|
+
rdoc_options: []
|
|
64
|
+
require_paths:
|
|
65
|
+
- lib
|
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: 3.2.0
|
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
requirements: []
|
|
77
|
+
rubygems_version: 3.5.22
|
|
78
|
+
signing_key:
|
|
79
|
+
specification_version: 4
|
|
80
|
+
summary: Ruby client for the ComfyUI API
|
|
81
|
+
test_files: []
|