@mcptoolshop/style-dataset-lab 2.3.0 → 3.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +36 -8
  3. package/bin/sdlab.js +129 -0
  4. package/lib/adapters/comfyui-runner.js +170 -0
  5. package/lib/adapters/comfyui-workflows.js +163 -0
  6. package/lib/batch-compiler.js +118 -0
  7. package/lib/batch-modes.js +111 -0
  8. package/lib/batch-runs.js +168 -0
  9. package/lib/batch-sheet-render.js +206 -0
  10. package/lib/brief-compiler.js +598 -0
  11. package/lib/brief-render.js +205 -0
  12. package/lib/critique-engine.js +413 -0
  13. package/lib/critique-render.js +171 -0
  14. package/lib/generated-provenance.js +87 -0
  15. package/lib/paths.js +31 -0
  16. package/lib/refine-briefs.js +329 -0
  17. package/lib/reingest-selected.js +173 -0
  18. package/lib/run-summary.js +131 -0
  19. package/lib/runtime-runs.js +168 -0
  20. package/lib/selections.js +323 -0
  21. package/lib/workflow-profiles.js +161 -0
  22. package/package.json +6 -3
  23. package/scripts/batch-generate.js +167 -0
  24. package/scripts/batch-sheet.js +46 -0
  25. package/scripts/batch-show.js +86 -0
  26. package/scripts/brief-compile.js +134 -0
  27. package/scripts/brief-show.js +52 -0
  28. package/scripts/critique-show.js +50 -0
  29. package/scripts/critique.js +70 -0
  30. package/scripts/doctor.js +169 -1
  31. package/scripts/init.js +22 -0
  32. package/scripts/refine.js +119 -0
  33. package/scripts/reingest-selected.js +78 -0
  34. package/scripts/reingest.js +7 -1
  35. package/scripts/run-generate.js +76 -0
  36. package/scripts/run-list.js +58 -0
  37. package/scripts/run-show.js +63 -0
  38. package/scripts/select.js +118 -0
  39. package/scripts/selection-show.js +88 -0
  40. package/scripts/workflow-list.js +61 -0
  41. package/scripts/workflow-show.js +96 -0
  42. package/templates/domains/architecture/workflows/profiles/environment-moodboard.json +62 -0
  43. package/templates/domains/architecture/workflows/profiles/interior-variant-pack.json +60 -0
  44. package/templates/domains/character-design/workflows/profiles/character-portrait-set.json +61 -0
  45. package/templates/domains/character-design/workflows/profiles/expression-sheet.json +59 -0
  46. package/templates/domains/creature-design/workflows/profiles/creature-portrait-set.json +61 -0
  47. package/templates/domains/creature-design/workflows/profiles/silhouette-exploration.json +58 -0
  48. package/templates/domains/game-art/workflows/profiles/character-portrait-set.json +62 -0
  49. package/templates/domains/game-art/workflows/profiles/environment-moodboard.json +61 -0
  50. package/templates/domains/vehicle-mech/workflows/profiles/variant-pack.json +60 -0
  51. package/templates/domains/vehicle-mech/workflows/profiles/vehicle-silhouette-pack.json +59 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Batch sheet renderer — assembles run outputs into reviewable HTML surfaces.
3
+ *
4
+ * Produces an HTML file with a CSS grid layout that references output images.
5
+ * Zero dependencies — works in any browser. Slot labels, seeds, and metadata
6
+ * are embedded for human review.
7
+ *
8
+ * Future: add PNG rendering via sharp if needed.
9
+ */
10
+
11
+ import { writeFile } from 'node:fs/promises';
12
+ import { join, relative } from 'node:path';
13
+ import { existsSync } from 'node:fs';
14
+ import { getRunsDir } from './paths.js';
15
+
16
+ /**
17
+ * Resolve the image path for a slot result.
18
+ * Returns a relative path from the sheet directory to the output image.
19
+ */
20
+ function resolveImagePath(projectRoot, result, batchDir) {
21
+ if (!result.selected_output || !result.run_id) return null;
22
+
23
+ const imagePath = join(getRunsDir(projectRoot), result.run_id, 'outputs', result.selected_output);
24
+ if (!existsSync(imagePath)) return null;
25
+
26
+ // Relative from sheets/ dir to the image
27
+ const sheetsDir = join(batchDir, 'sheets');
28
+ return relative(sheetsDir, imagePath).replace(/\\/g, '/');
29
+ }
30
+
31
+ /**
32
+ * Render a batch sheet as HTML.
33
+ *
34
+ * @param {Object} opts
35
+ * @param {Object} opts.manifest — batch manifest
36
+ * @param {Object} opts.mode — batch mode definition
37
+ * @param {string} opts.projectRoot
38
+ * @param {string} opts.batchDir
39
+ * @returns {string} HTML content
40
+ */
41
+ export function renderSheetHTML({ manifest, mode, projectRoot, batchDir }) {
42
+ const assembly = mode.assembly || {};
43
+ const layout = assembly.layout || 'grid';
44
+ const columns = assembly.columns || computeColumns(manifest.slots.length);
45
+ const showSlotLabels = assembly.annotate_slots !== false;
46
+ const showSeed = assembly.show_seed || false;
47
+
48
+ const title = `${manifest.mode_id} — ${manifest.subject_id || manifest.theme || manifest.project_id}`;
49
+ const slots = manifest.slots || [];
50
+
51
+ // Build slot cells
52
+ const cells = slots.map(slot => {
53
+ const imgPath = resolveImagePath(projectRoot, slot, batchDir);
54
+ const hasImage = !!imgPath;
55
+
56
+ return {
57
+ slot_id: slot.slot_id,
58
+ label: slot.label,
59
+ run_id: slot.run_id,
60
+ selected_output: slot.selected_output,
61
+ imgPath,
62
+ hasImage,
63
+ seed: findSlotSeed(manifest, slot),
64
+ };
65
+ });
66
+
67
+ // Generate HTML
68
+ const css = generateCSS(layout, columns, cells.length);
69
+ const grid = cells.map((cell, i) => renderCell(cell, i, showSlotLabels, showSeed)).join('\n');
70
+
71
+ return `<!DOCTYPE html>
72
+ <html lang="en">
73
+ <head>
74
+ <meta charset="utf-8">
75
+ <title>${escapeHtml(title)}</title>
76
+ <style>${css}</style>
77
+ </head>
78
+ <body>
79
+ <header>
80
+ <h1>${escapeHtml(title)}</h1>
81
+ <p class="meta">
82
+ Batch: ${escapeHtml(manifest.batch_id)} |
83
+ Mode: ${escapeHtml(manifest.mode_id)} |
84
+ Type: ${escapeHtml(mode.batch_type)} |
85
+ ${slots.length} slots |
86
+ ${manifest.created_at?.slice(0, 16) || '?'}
87
+ </p>
88
+ </header>
89
+ <div class="grid">${grid}
90
+ </div>
91
+ <footer>
92
+ <p>Generated by style-dataset-lab — ${manifest.created_at || ''}</p>
93
+ </footer>
94
+ </body>
95
+ </html>`;
96
+ }
97
+
98
+ /**
99
+ * Compute optimal column count based on slot count.
100
+ */
101
+ function computeColumns(slotCount) {
102
+ if (slotCount <= 3) return slotCount;
103
+ if (slotCount <= 4) return 2;
104
+ if (slotCount <= 6) return 3;
105
+ if (slotCount <= 9) return 3;
106
+ return 4;
107
+ }
108
+
109
+ /**
110
+ * Generate CSS for the layout.
111
+ */
112
+ function generateCSS(layout, columns, slotCount) {
113
+ return `
114
+ * { margin: 0; padding: 0; box-sizing: border-box; }
115
+ body {
116
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
117
+ background: #1a1a1a; color: #e0e0e0;
118
+ padding: 24px;
119
+ }
120
+ header { margin-bottom: 24px; }
121
+ h1 { font-size: 1.4rem; font-weight: 600; }
122
+ .meta { font-size: 0.85rem; color: #888; margin-top: 4px; }
123
+ .grid {
124
+ display: grid;
125
+ grid-template-columns: repeat(${columns}, 1fr);
126
+ gap: 12px;
127
+ }
128
+ .cell {
129
+ background: #252525;
130
+ border: 1px solid #333;
131
+ border-radius: 6px;
132
+ overflow: hidden;
133
+ position: relative;
134
+ }
135
+ .cell img {
136
+ width: 100%; height: auto; display: block;
137
+ }
138
+ .cell .placeholder {
139
+ width: 100%; aspect-ratio: 1;
140
+ background: #2a2a2a;
141
+ display: flex; align-items: center; justify-content: center;
142
+ color: #555; font-size: 0.9rem;
143
+ }
144
+ .cell .label-bar {
145
+ padding: 6px 10px;
146
+ font-size: 0.8rem;
147
+ background: #2a2a2a;
148
+ border-top: 1px solid #333;
149
+ }
150
+ .cell .label-bar .slot-label { font-weight: 600; color: #ccc; }
151
+ .cell .label-bar .seed { color: #666; margin-left: 8px; }
152
+ footer {
153
+ margin-top: 24px;
154
+ font-size: 0.75rem; color: #555;
155
+ text-align: center;
156
+ }
157
+ `;
158
+ }
159
+
160
+ /**
161
+ * Render a single cell.
162
+ */
163
+ function renderCell(cell, index, showLabel, showSeed) {
164
+ const imgTag = cell.hasImage
165
+ ? `<img src="${escapeHtml(cell.imgPath)}" alt="${escapeHtml(cell.label)}" loading="lazy">`
166
+ : `<div class="placeholder">${escapeHtml(cell.label)}<br><small>${cell.selected_output || 'no output'}</small></div>`;
167
+
168
+ const labelHtml = showLabel
169
+ ? `<div class="label-bar">
170
+ <span class="slot-label">${escapeHtml(cell.label)}</span>
171
+ ${showSeed && cell.seed != null ? `<span class="seed">seed: ${cell.seed}</span>` : ''}
172
+ </div>`
173
+ : '';
174
+
175
+ return ` <div class="cell">${imgTag}${labelHtml}</div>`;
176
+ }
177
+
178
+ /**
179
+ * Find the seed used for a slot's selected output.
180
+ */
181
+ function findSlotSeed(manifest, slot) {
182
+ // Not always available — return null if not found
183
+ return null; // Could be enriched from run manifests later
184
+ }
185
+
186
+ /**
187
+ * Escape HTML entities.
188
+ */
189
+ function escapeHtml(str) {
190
+ if (!str) return '';
191
+ return String(str)
192
+ .replace(/&/g, '&amp;')
193
+ .replace(/</g, '&lt;')
194
+ .replace(/>/g, '&gt;')
195
+ .replace(/"/g, '&quot;');
196
+ }
197
+
198
+ /**
199
+ * Save the sheet HTML to the batch sheets directory.
200
+ */
201
+ export async function saveSheet(batchDir, filename, html) {
202
+ const sheetsDir = join(batchDir, 'sheets');
203
+ const sheetPath = join(sheetsDir, filename);
204
+ await writeFile(sheetPath, html);
205
+ return sheetPath;
206
+ }