@mcptoolshop/style-dataset-lab 1.2.0 → 2.0.0
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.
- package/CHANGELOG.md +45 -0
- package/README.es.md +94 -54
- package/README.fr.md +86 -46
- package/README.hi.md +86 -46
- package/README.it.md +93 -53
- package/README.ja.md +86 -46
- package/README.md +95 -116
- package/README.pt-BR.md +86 -46
- package/README.zh.md +90 -50
- package/bin/sdlab.js +103 -0
- package/lib/args.js +106 -0
- package/lib/comfyui.js +109 -0
- package/lib/config.js +104 -0
- package/lib/deprecation.js +14 -0
- package/lib/paths.js +38 -0
- package/lib/records.js +69 -0
- package/package.json +13 -4
- package/scripts/bulk-curate-wave2-5.js +1 -1
- package/scripts/bulk-curate-waves11-18.js +1 -1
- package/scripts/canon-bind.js +268 -332
- package/scripts/compare.js +40 -30
- package/scripts/curate-wave25.js +1 -1
- package/scripts/curate.js +34 -28
- package/scripts/doctor.js +321 -0
- package/scripts/generate-controlnet.js +36 -121
- package/scripts/generate-identity.js +38 -106
- package/scripts/generate-ipadapter.js +42 -119
- package/scripts/generate.js +22 -81
- package/scripts/init.js +226 -0
- package/scripts/migrate-records.js +15 -21
- package/scripts/painterly-test.js +20 -47
- package/scripts/painterly.js +27 -106
- package/templates/domains/architecture/constitution.json +97 -0
- package/templates/domains/architecture/lanes.json +41 -0
- package/templates/domains/architecture/project.json +20 -0
- package/templates/domains/architecture/rubric.json +40 -0
- package/templates/domains/architecture/terminology.json +19 -0
- package/templates/domains/character-design/constitution.json +88 -0
- package/templates/domains/character-design/lanes.json +35 -0
- package/templates/domains/character-design/project.json +17 -0
- package/templates/domains/character-design/rubric.json +38 -0
- package/templates/domains/character-design/terminology.json +21 -0
- package/templates/domains/creature-design/constitution.json +87 -0
- package/templates/domains/creature-design/lanes.json +41 -0
- package/templates/domains/creature-design/project.json +18 -0
- package/templates/domains/creature-design/rubric.json +39 -0
- package/templates/domains/creature-design/terminology.json +20 -0
- package/templates/domains/game-art/constitution.json +113 -0
- package/templates/domains/game-art/lanes.json +47 -0
- package/templates/domains/game-art/project.json +17 -0
- package/templates/domains/game-art/rubric.json +36 -0
- package/templates/domains/game-art/terminology.json +30 -0
- package/templates/domains/vehicle-mech/constitution.json +87 -0
- package/templates/domains/vehicle-mech/lanes.json +41 -0
- package/templates/domains/vehicle-mech/project.json +17 -0
- package/templates/domains/vehicle-mech/rubric.json +40 -0
- package/templates/domains/vehicle-mech/terminology.json +21 -0
|
@@ -4,31 +4,18 @@
|
|
|
4
4
|
* generate-ipadapter.js — IP-Adapter guided generation for structural subjects.
|
|
5
5
|
*
|
|
6
6
|
* Uses a reference image to guide the shape/structure of the output
|
|
7
|
-
* while the text prompt adds identity-specific details
|
|
7
|
+
* while the text prompt adds identity-specific details.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
10
|
* node scripts/generate-ipadapter.js --subject corrigan --ref <image> --seeds 4
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Options:
|
|
14
|
-
* --subject NAME Subject name for output file naming
|
|
15
|
-
* --ref PATH Reference image path (the structural truth source)
|
|
16
|
-
* --prompt TEXT Generation prompt
|
|
17
|
-
* --negative TEXT Negative prompt
|
|
18
|
-
* --seeds N Number of seeds (default: 4)
|
|
19
|
-
* --weight W IP-Adapter weight (default: 0.55)
|
|
20
|
-
* --start S IP-Adapter start (default: 0.0)
|
|
21
|
-
* --end E IP-Adapter end (default: 0.8)
|
|
22
|
-
* --dry-run Print workflow without running
|
|
11
|
+
* sdlab generate:ipadapter --subject corrigan --ref <image> --seeds 4 --project star-freight
|
|
23
12
|
*/
|
|
24
13
|
|
|
25
14
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
26
15
|
import { join } from "node:path";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
|
|
31
|
-
const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
|
|
16
|
+
import { getProjectName } from "../lib/args.js";
|
|
17
|
+
import { REPO_ROOT } from "../lib/paths.js";
|
|
18
|
+
import { comfyHealth, submitAndWait, downloadImage, uploadImage } from "../lib/comfyui.js";
|
|
32
19
|
|
|
33
20
|
const DEFAULTS = {
|
|
34
21
|
checkpoint: "dreamshaperXL_v21TurboDPMSDE.safetensors",
|
|
@@ -44,11 +31,10 @@ const DEFAULTS = {
|
|
|
44
31
|
base_seed: 27200,
|
|
45
32
|
};
|
|
46
33
|
|
|
47
|
-
function
|
|
48
|
-
const args = process.argv.slice(2);
|
|
34
|
+
function parseLocalArgs(argv) {
|
|
49
35
|
const get = (flag, def) => {
|
|
50
|
-
const i =
|
|
51
|
-
return i >= 0 && i + 1 <
|
|
36
|
+
const i = argv.indexOf(flag);
|
|
37
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : def;
|
|
52
38
|
};
|
|
53
39
|
return {
|
|
54
40
|
subject: get("--subject", "unknown"),
|
|
@@ -59,7 +45,7 @@ function parseArgs() {
|
|
|
59
45
|
weight: parseFloat(get("--weight", "0.55")),
|
|
60
46
|
start: parseFloat(get("--start", "0.0")),
|
|
61
47
|
end: parseFloat(get("--end", "0.8")),
|
|
62
|
-
dryRun:
|
|
48
|
+
dryRun: argv.includes("--dry-run"),
|
|
63
49
|
};
|
|
64
50
|
}
|
|
65
51
|
|
|
@@ -67,7 +53,6 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
|
|
|
67
53
|
const nodes = {};
|
|
68
54
|
let nextId = 1;
|
|
69
55
|
|
|
70
|
-
// Checkpoint
|
|
71
56
|
const ckptId = String(nextId++);
|
|
72
57
|
nodes[ckptId] = {
|
|
73
58
|
class_type: "CheckpointLoaderSimple",
|
|
@@ -77,33 +62,30 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
|
|
|
77
62
|
let modelOut = [ckptId, 0];
|
|
78
63
|
let clipOut = [ckptId, 1];
|
|
79
64
|
|
|
80
|
-
// LoRA
|
|
81
65
|
for (const lora of DEFAULTS.loras) {
|
|
82
66
|
const loraId = String(nextId++);
|
|
83
67
|
nodes[loraId] = {
|
|
84
68
|
class_type: "LoraLoader",
|
|
85
69
|
inputs: {
|
|
86
|
-
model: modelOut,
|
|
87
|
-
clip: clipOut,
|
|
70
|
+
model: modelOut, clip: clipOut,
|
|
88
71
|
lora_name: lora.name,
|
|
89
|
-
strength_model: lora.weight,
|
|
90
|
-
strength_clip: lora.weight,
|
|
72
|
+
strength_model: lora.weight, strength_clip: lora.weight,
|
|
91
73
|
},
|
|
92
74
|
};
|
|
93
75
|
modelOut = [loraId, 0];
|
|
94
76
|
clipOut = [loraId, 1];
|
|
95
77
|
}
|
|
96
78
|
|
|
97
|
-
//
|
|
79
|
+
// CLIP Vision
|
|
98
80
|
const clipVisionId = String(nextId++);
|
|
99
81
|
nodes[clipVisionId] = {
|
|
100
82
|
class_type: "CLIPVisionLoader",
|
|
101
83
|
inputs: { clip_name: DEFAULTS.clip_vision_model },
|
|
102
84
|
};
|
|
103
85
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
nodes[
|
|
86
|
+
// IP-Adapter model
|
|
87
|
+
const ipaLoaderId = String(nextId++);
|
|
88
|
+
nodes[ipaLoaderId] = {
|
|
107
89
|
class_type: "IPAdapterModelLoader",
|
|
108
90
|
inputs: { ipadapter_file: DEFAULTS.ipadapter_model },
|
|
109
91
|
};
|
|
@@ -115,47 +97,43 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
|
|
|
115
97
|
inputs: { image: refImageName },
|
|
116
98
|
};
|
|
117
99
|
|
|
118
|
-
// Apply IP-Adapter
|
|
100
|
+
// Apply IP-Adapter
|
|
119
101
|
const ipaApplyId = String(nextId++);
|
|
120
102
|
nodes[ipaApplyId] = {
|
|
121
103
|
class_type: "IPAdapterAdvanced",
|
|
122
104
|
inputs: {
|
|
123
105
|
model: modelOut,
|
|
124
|
-
ipadapter: [
|
|
125
|
-
image: [refImgId, 0],
|
|
106
|
+
ipadapter: [ipaLoaderId, 0],
|
|
126
107
|
clip_vision: [clipVisionId, 0],
|
|
127
|
-
|
|
108
|
+
image: [refImgId, 0],
|
|
109
|
+
weight,
|
|
128
110
|
weight_type: "linear",
|
|
129
111
|
combine_embeds: "concat",
|
|
112
|
+
embeds_scaling: "V only",
|
|
130
113
|
start_at: start,
|
|
131
114
|
end_at: end,
|
|
132
|
-
embeds_scaling: "V only",
|
|
133
115
|
},
|
|
134
116
|
};
|
|
135
117
|
modelOut = [ipaApplyId, 0];
|
|
136
118
|
|
|
137
|
-
// Positive prompt
|
|
138
119
|
const posId = String(nextId++);
|
|
139
120
|
nodes[posId] = {
|
|
140
121
|
class_type: "CLIPTextEncode",
|
|
141
122
|
inputs: { text: prompt, clip: clipOut },
|
|
142
123
|
};
|
|
143
124
|
|
|
144
|
-
// Negative prompt
|
|
145
125
|
const negId = String(nextId++);
|
|
146
126
|
nodes[negId] = {
|
|
147
127
|
class_type: "CLIPTextEncode",
|
|
148
128
|
inputs: { text: negativePrompt, clip: clipOut },
|
|
149
129
|
};
|
|
150
130
|
|
|
151
|
-
// Empty latent
|
|
152
131
|
const latentId = String(nextId++);
|
|
153
132
|
nodes[latentId] = {
|
|
154
133
|
class_type: "EmptyLatentImage",
|
|
155
134
|
inputs: { width: DEFAULTS.width, height: DEFAULTS.height, batch_size: 1 },
|
|
156
135
|
};
|
|
157
136
|
|
|
158
|
-
// KSampler
|
|
159
137
|
const samplerId = String(nextId++);
|
|
160
138
|
nodes[samplerId] = {
|
|
161
139
|
class_type: "KSampler",
|
|
@@ -173,14 +151,12 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
|
|
|
173
151
|
},
|
|
174
152
|
};
|
|
175
153
|
|
|
176
|
-
// VAE Decode
|
|
177
154
|
const vaeId = String(nextId++);
|
|
178
155
|
nodes[vaeId] = {
|
|
179
156
|
class_type: "VAEDecode",
|
|
180
157
|
inputs: { samples: [samplerId, 0], vae: [ckptId, 2] },
|
|
181
158
|
};
|
|
182
159
|
|
|
183
|
-
// Save
|
|
184
160
|
const saveId = String(nextId++);
|
|
185
161
|
nodes[saveId] = {
|
|
186
162
|
class_type: "SaveImage",
|
|
@@ -190,62 +166,18 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
|
|
|
190
166
|
return nodes;
|
|
191
167
|
}
|
|
192
168
|
|
|
193
|
-
async function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} catch {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const MAX_POLL_MS = 600_000; // 10 minutes
|
|
169
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
170
|
+
const projectName = getProjectName(argv);
|
|
171
|
+
const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
|
|
172
|
+
const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
|
|
203
173
|
|
|
204
|
-
|
|
205
|
-
const clientId = `ipa-${Date.now()}`;
|
|
206
|
-
const res = await fetch(`${COMFY_URL}/prompt`, {
|
|
207
|
-
method: "POST",
|
|
208
|
-
headers: { "Content-Type": "application/json" },
|
|
209
|
-
body: JSON.stringify({ prompt: workflow, client_id: clientId }),
|
|
210
|
-
});
|
|
211
|
-
if (!res.ok) {
|
|
212
|
-
const text = await res.text();
|
|
213
|
-
throw new Error(`ComfyUI submit failed: ${res.status} ${text}`);
|
|
214
|
-
}
|
|
215
|
-
const { prompt_id: promptId } = await res.json();
|
|
216
|
-
const start = Date.now();
|
|
217
|
-
while (true) {
|
|
218
|
-
if (Date.now() - start > MAX_POLL_MS) throw new Error(`ComfyUI poll timeout after ${MAX_POLL_MS/1000}s for prompt ${promptId}`);
|
|
219
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
220
|
-
const histRes = await fetch(`${COMFY_URL}/history/${promptId}`);
|
|
221
|
-
if (!histRes.ok) continue;
|
|
222
|
-
const history = await histRes.json();
|
|
223
|
-
const entry = history[promptId];
|
|
224
|
-
if (!entry) continue;
|
|
225
|
-
if (entry.status?.completed) return entry;
|
|
226
|
-
if (entry.status?.status_str === "error") {
|
|
227
|
-
throw new Error(`ComfyUI error: ${JSON.stringify(entry.status)}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function downloadImage(filename, subfolder, type = "output") {
|
|
233
|
-
const url = `${COMFY_URL}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || "")}&type=${type}`;
|
|
234
|
-
const res = await fetch(url);
|
|
235
|
-
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
236
|
-
return Buffer.from(await res.arrayBuffer());
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async function main() {
|
|
240
|
-
const opts = parseArgs();
|
|
174
|
+
const opts = parseLocalArgs(argv);
|
|
241
175
|
|
|
242
176
|
if (!opts.refPath) {
|
|
243
|
-
|
|
244
|
-
process.exit(1);
|
|
177
|
+
throw new Error("--ref <path> is required");
|
|
245
178
|
}
|
|
246
179
|
if (!opts.prompt) {
|
|
247
|
-
|
|
248
|
-
process.exit(1);
|
|
180
|
+
throw new Error("--prompt is required");
|
|
249
181
|
}
|
|
250
182
|
|
|
251
183
|
const negative = opts.negative ||
|
|
@@ -259,33 +191,21 @@ async function main() {
|
|
|
259
191
|
console.log(` Seeds: ${opts.seeds}`);
|
|
260
192
|
console.log("");
|
|
261
193
|
|
|
194
|
+
const refFilename = `ipa_ref_${opts.subject}.png`;
|
|
195
|
+
|
|
262
196
|
if (!opts.dryRun) {
|
|
263
|
-
const online = await comfyHealth();
|
|
197
|
+
const online = await comfyHealth(COMFY_URL);
|
|
264
198
|
if (!online) {
|
|
265
|
-
|
|
266
|
-
process.exit(1);
|
|
199
|
+
throw new Error("ComfyUI not reachable");
|
|
267
200
|
}
|
|
268
201
|
console.log("\x1b[32m+\x1b[0m ComfyUI online");
|
|
269
202
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const refFilename = `ipa_ref_${opts.subject}.png`;
|
|
273
|
-
const formData = new FormData();
|
|
274
|
-
formData.append("image", new Blob([refData], { type: "image/png" }), refFilename);
|
|
275
|
-
formData.append("overwrite", "true");
|
|
276
|
-
const uploadRes = await fetch(`${COMFY_URL}/upload/image`, { method: "POST", body: formData });
|
|
277
|
-
if (!uploadRes.ok) {
|
|
278
|
-
console.error(`Reference upload failed: ${uploadRes.status}`);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
const uploadResult = await uploadRes.json();
|
|
282
|
-
console.log(`\x1b[32m+\x1b[0m Reference uploaded: ${uploadResult.name}`);
|
|
203
|
+
await uploadImage(join(GAME_ROOT, opts.refPath), refFilename, COMFY_URL);
|
|
204
|
+
console.log(`\x1b[32m+\x1b[0m Reference uploaded: ${refFilename}`);
|
|
283
205
|
}
|
|
284
206
|
|
|
285
207
|
await mkdir(join(GAME_ROOT, "outputs/candidates"), { recursive: true });
|
|
286
208
|
|
|
287
|
-
const refFilename = `ipa_ref_${opts.subject}.png`;
|
|
288
|
-
|
|
289
209
|
for (let si = 0; si < opts.seeds; si++) {
|
|
290
210
|
const seed = DEFAULTS.base_seed + si;
|
|
291
211
|
const assetId = `${opts.subject}_ipa_s${si}`;
|
|
@@ -302,7 +222,7 @@ async function main() {
|
|
|
302
222
|
);
|
|
303
223
|
|
|
304
224
|
try {
|
|
305
|
-
const result = await submitAndWait(workflow);
|
|
225
|
+
const result = await submitAndWait(workflow, COMFY_URL, { clientPrefix: 'ipa' });
|
|
306
226
|
const elapsed = Date.now() - startMs;
|
|
307
227
|
|
|
308
228
|
const outputs = result.outputs || {};
|
|
@@ -321,7 +241,7 @@ async function main() {
|
|
|
321
241
|
continue;
|
|
322
242
|
}
|
|
323
243
|
|
|
324
|
-
const imgData = await downloadImage(imageFile, imageSubfolder);
|
|
244
|
+
const imgData = await downloadImage(imageFile, imageSubfolder, COMFY_URL);
|
|
325
245
|
await writeFile(join(GAME_ROOT, destPath), imgData);
|
|
326
246
|
console.log(` \x1b[32m+\x1b[0m ${destPath} (${elapsed}ms, ${imgData.length} bytes)`);
|
|
327
247
|
} catch (err) {
|
|
@@ -333,7 +253,10 @@ async function main() {
|
|
|
333
253
|
if (opts.dryRun) console.log(" (dry run)");
|
|
334
254
|
}
|
|
335
255
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
256
|
+
// Direct execution guard
|
|
257
|
+
if (process.argv[1] && (process.argv[1].endsWith('generate-ipadapter.js') || process.argv[1].endsWith('generate-ipadapter'))) {
|
|
258
|
+
run().catch((err) => {
|
|
259
|
+
console.error(err.message || err);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
});
|
|
262
|
+
}
|
package/scripts/generate.js
CHANGED
|
@@ -4,29 +4,17 @@
|
|
|
4
4
|
* generate.js — Drive ComfyUI to produce candidate assets from a prompt pack.
|
|
5
5
|
*
|
|
6
6
|
* Usage: node scripts/generate.js [--prompt-pack inputs/prompts/rpg-icons-lane1.json]
|
|
7
|
+
* sdlab generate inputs/prompts/wave1.json --project star-freight
|
|
7
8
|
*
|
|
8
9
|
* For each subject × variation, submits a workflow to ComfyUI HTTP API,
|
|
9
10
|
* downloads the output PNG, and writes a provenance record to records/.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
13
|
-
import { join
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
|
|
18
|
-
const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
|
|
19
|
-
|
|
20
|
-
// ── ComfyUI API helpers ──
|
|
21
|
-
|
|
22
|
-
async function comfyHealth() {
|
|
23
|
-
try {
|
|
24
|
-
const res = await fetch(`${COMFY_URL}/system_stats`);
|
|
25
|
-
return res.ok;
|
|
26
|
-
} catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { getProjectName } from "../lib/args.js";
|
|
16
|
+
import { REPO_ROOT } from "../lib/paths.js";
|
|
17
|
+
import { comfyHealth, submitAndWait, downloadImage } from "../lib/comfyui.js";
|
|
30
18
|
|
|
31
19
|
function buildWorkflow(prompt, negativePrompt, checkpoint, loras, seed, steps, cfg, sampler, scheduler, width, height, ipAdapterConfig) {
|
|
32
20
|
const nodes = {};
|
|
@@ -164,57 +152,13 @@ function buildWorkflow(prompt, negativePrompt, checkpoint, loras, seed, steps, c
|
|
|
164
152
|
return { nodes, saveNodeId: saveId };
|
|
165
153
|
}
|
|
166
154
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
// Submit
|
|
173
|
-
const res = await fetch(`${COMFY_URL}/prompt`, {
|
|
174
|
-
method: "POST",
|
|
175
|
-
headers: { "Content-Type": "application/json" },
|
|
176
|
-
body: JSON.stringify({ prompt: workflow, client_id: clientId }),
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
if (!res.ok) {
|
|
180
|
-
const text = await res.text();
|
|
181
|
-
throw new Error(`ComfyUI submit failed: ${res.status} ${text}`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const { prompt_id: promptId } = await res.json();
|
|
185
|
-
|
|
186
|
-
// Poll history until complete
|
|
187
|
-
const start = Date.now();
|
|
188
|
-
while (true) {
|
|
189
|
-
if (Date.now() - start > MAX_POLL_MS) throw new Error(`ComfyUI poll timeout after ${MAX_POLL_MS/1000}s for prompt ${promptId}`);
|
|
190
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
191
|
-
const histRes = await fetch(`${COMFY_URL}/history/${promptId}`);
|
|
192
|
-
if (!histRes.ok) continue;
|
|
193
|
-
const history = await histRes.json();
|
|
194
|
-
const entry = history[promptId];
|
|
195
|
-
if (!entry) continue;
|
|
196
|
-
if (entry.status?.completed) {
|
|
197
|
-
return entry;
|
|
198
|
-
}
|
|
199
|
-
if (entry.status?.status_str === "error") {
|
|
200
|
-
throw new Error(`ComfyUI generation failed: ${JSON.stringify(entry.status)}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function downloadImage(filename, subfolder, type = "output") {
|
|
206
|
-
const url = `${COMFY_URL}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || "")}&type=${type}`;
|
|
207
|
-
const res = await fetch(url);
|
|
208
|
-
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
209
|
-
return Buffer.from(await res.arrayBuffer());
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ── Main ──
|
|
155
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
156
|
+
const projectName = getProjectName(argv);
|
|
157
|
+
const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
|
|
158
|
+
const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
|
|
213
159
|
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
const packPath = args.find((a) => !a.startsWith("--")) || "inputs/prompts/rpg-icons-lane1.json";
|
|
217
|
-
const dryRun = args.includes("--dry-run");
|
|
160
|
+
const packPath = argv.find((a) => !a.startsWith("--")) || "inputs/prompts/rpg-icons-lane1.json";
|
|
161
|
+
const dryRun = argv.includes("--dry-run");
|
|
218
162
|
|
|
219
163
|
const fullPackPath = join(GAME_ROOT, packPath);
|
|
220
164
|
const pack = JSON.parse(await readFile(fullPackPath, "utf-8"));
|
|
@@ -227,11 +171,9 @@ async function main() {
|
|
|
227
171
|
console.log("");
|
|
228
172
|
|
|
229
173
|
if (!dryRun) {
|
|
230
|
-
const online = await comfyHealth();
|
|
174
|
+
const online = await comfyHealth(COMFY_URL);
|
|
231
175
|
if (!online) {
|
|
232
|
-
|
|
233
|
-
console.error("Start ComfyUI: cd F:/AI-Models/ComfyUI-runtime && python main.py --listen 127.0.0.1 --port 8188");
|
|
234
|
-
process.exit(1);
|
|
176
|
+
throw new Error("ComfyUI not reachable at " + COMFY_URL);
|
|
235
177
|
}
|
|
236
178
|
console.log("\x1b[32m✓\x1b[0m ComfyUI online");
|
|
237
179
|
}
|
|
@@ -244,7 +186,7 @@ async function main() {
|
|
|
244
186
|
const totalExpected = pack.subjects.length * pack.variations.length;
|
|
245
187
|
|
|
246
188
|
for (const subject of pack.subjects) {
|
|
247
|
-
for (const variation of
|
|
189
|
+
for (const variation of pack.variations) {
|
|
248
190
|
const assetId = `${subject.id}_${variation.id}`;
|
|
249
191
|
const seed = (d.base_seed + generated) + (variation.seed_offset || 0);
|
|
250
192
|
const fullPrompt = `${pack.style_prefix}, ${subject.prompt}`;
|
|
@@ -266,7 +208,7 @@ async function main() {
|
|
|
266
208
|
);
|
|
267
209
|
|
|
268
210
|
try {
|
|
269
|
-
const result = await submitAndWait(nodes);
|
|
211
|
+
const result = await submitAndWait(nodes, COMFY_URL);
|
|
270
212
|
const elapsed = Date.now() - startMs;
|
|
271
213
|
|
|
272
214
|
// Find output image
|
|
@@ -288,7 +230,7 @@ async function main() {
|
|
|
288
230
|
}
|
|
289
231
|
|
|
290
232
|
// Download and save
|
|
291
|
-
const imgData = await downloadImage(imageFile, imageSubfolder);
|
|
233
|
+
const imgData = await downloadImage(imageFile, imageSubfolder, COMFY_URL);
|
|
292
234
|
const destPath = `outputs/candidates/${assetId}.png`;
|
|
293
235
|
await writeFile(join(GAME_ROOT, destPath), imgData);
|
|
294
236
|
|
|
@@ -341,11 +283,10 @@ async function main() {
|
|
|
341
283
|
if (dryRun) console.log(" (dry run — no images produced)");
|
|
342
284
|
}
|
|
343
285
|
|
|
344
|
-
|
|
345
|
-
|
|
286
|
+
// Direct execution guard
|
|
287
|
+
if (process.argv[1] && (process.argv[1].endsWith('generate.js') || process.argv[1].endsWith('generate'))) {
|
|
288
|
+
run().catch((err) => {
|
|
289
|
+
console.error(err.message || err);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
346
292
|
}
|
|
347
|
-
|
|
348
|
-
main().catch((err) => {
|
|
349
|
-
console.error(err);
|
|
350
|
-
process.exit(1);
|
|
351
|
-
});
|