@weirdfingers/boards 0.2.1 → 0.3.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/README.md +678 -0
- package/dist/index.d.mts +225 -2
- package/dist/index.d.ts +225 -2
- package/dist/index.js +155 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/hooks/useGenerators.ts +6 -2
- package/src/index.ts +5 -0
- package/src/types/generatorSchema.ts +187 -0
- package/src/utils/__tests__/schemaParser.test.ts +576 -0
- package/src/utils/schemaParser.ts +310 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for parsing generator JSON Schemas into structured data
|
|
3
|
+
* suitable for dynamic UI generation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
7
|
+
import type {
|
|
8
|
+
ParsedGeneratorSchema,
|
|
9
|
+
ArtifactSlot,
|
|
10
|
+
PromptField,
|
|
11
|
+
SettingsField,
|
|
12
|
+
} from "../types/generatorSchema";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if a JSON Schema property references an artifact type.
|
|
16
|
+
*
|
|
17
|
+
* Artifacts are identified by $ref paths containing "Artifact" in their name,
|
|
18
|
+
* e.g., "#/$defs/AudioArtifact" or "#/$defs/VideoArtifact".
|
|
19
|
+
*
|
|
20
|
+
* @param property - The JSON Schema property to check
|
|
21
|
+
* @returns True if the property references an artifact type
|
|
22
|
+
*/
|
|
23
|
+
export function isArtifactReference(
|
|
24
|
+
property: JSONSchema7 | JSONSchema7Definition | undefined
|
|
25
|
+
): boolean {
|
|
26
|
+
if (!property || typeof property === "boolean") {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Direct $ref to artifact
|
|
31
|
+
if (property.$ref && property.$ref.includes("Artifact")) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Array with items that reference an artifact
|
|
36
|
+
if (
|
|
37
|
+
property.type === "array" &&
|
|
38
|
+
property.items &&
|
|
39
|
+
typeof property.items === "object" &&
|
|
40
|
+
!Array.isArray(property.items)
|
|
41
|
+
) {
|
|
42
|
+
const items = property.items as JSONSchema7;
|
|
43
|
+
return !!(items.$ref && items.$ref.includes("Artifact"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extracts the artifact type from a $ref path.
|
|
51
|
+
*
|
|
52
|
+
* Examples:
|
|
53
|
+
* - "#/$defs/AudioArtifact" -> "audio"
|
|
54
|
+
* - "#/$defs/VideoArtifact" -> "video"
|
|
55
|
+
* - "#/$defs/ImageArtifact" -> "image"
|
|
56
|
+
* - "#/$defs/TextArtifact" -> "text"
|
|
57
|
+
*
|
|
58
|
+
* @param ref - The $ref string from the JSON Schema
|
|
59
|
+
* @returns The artifact type in lowercase
|
|
60
|
+
*/
|
|
61
|
+
export function getArtifactType(
|
|
62
|
+
ref: string
|
|
63
|
+
): "audio" | "video" | "image" | "text" {
|
|
64
|
+
const match = ref.match(/(Audio|Video|Image|Text)Artifact/);
|
|
65
|
+
if (match) {
|
|
66
|
+
return match[1].toLowerCase() as "audio" | "video" | "image" | "text";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Fallback to "image" if pattern doesn't match
|
|
70
|
+
return "image";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parses an artifact property into an ArtifactSlot structure.
|
|
75
|
+
*
|
|
76
|
+
* @param name - The property name from the schema
|
|
77
|
+
* @param property - The JSON Schema property definition
|
|
78
|
+
* @param required - Whether this field is in the required array
|
|
79
|
+
* @returns Parsed artifact slot information
|
|
80
|
+
*/
|
|
81
|
+
export function parseArtifactSlot(
|
|
82
|
+
name: string,
|
|
83
|
+
property: JSONSchema7,
|
|
84
|
+
required: boolean
|
|
85
|
+
): ArtifactSlot {
|
|
86
|
+
const title = property.title || name;
|
|
87
|
+
const description = property.description;
|
|
88
|
+
|
|
89
|
+
// Check if this is an array of artifacts
|
|
90
|
+
if (property.type === "array" && property.items) {
|
|
91
|
+
const items =
|
|
92
|
+
typeof property.items === "object" && !Array.isArray(property.items)
|
|
93
|
+
? (property.items as JSONSchema7)
|
|
94
|
+
: undefined;
|
|
95
|
+
|
|
96
|
+
const artifactType = items?.$ref ? getArtifactType(items.$ref) : "image";
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
name: title,
|
|
100
|
+
fieldName: name,
|
|
101
|
+
artifactType,
|
|
102
|
+
required,
|
|
103
|
+
description,
|
|
104
|
+
isArray: true,
|
|
105
|
+
minItems: property.minItems,
|
|
106
|
+
maxItems: property.maxItems,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Single artifact
|
|
111
|
+
const artifactType = property.$ref ? getArtifactType(property.$ref) : "image";
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
name: title,
|
|
115
|
+
fieldName: name,
|
|
116
|
+
artifactType,
|
|
117
|
+
required,
|
|
118
|
+
description,
|
|
119
|
+
isArray: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Determines if a numeric property should be rendered as a slider.
|
|
125
|
+
*
|
|
126
|
+
* A property is considered a slider if it's a number or integer type
|
|
127
|
+
* and has both minimum and maximum values defined.
|
|
128
|
+
*
|
|
129
|
+
* @param property - The JSON Schema property to check
|
|
130
|
+
* @returns True if this should be a slider
|
|
131
|
+
*/
|
|
132
|
+
function isSlider(property: JSONSchema7): boolean {
|
|
133
|
+
return (
|
|
134
|
+
(property.type === "number" || property.type === "integer") &&
|
|
135
|
+
property.minimum !== undefined &&
|
|
136
|
+
property.maximum !== undefined
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parses a settings field into its appropriate type (slider, dropdown, text, number).
|
|
142
|
+
*
|
|
143
|
+
* @param name - The property name from the schema
|
|
144
|
+
* @param property - The JSON Schema property definition
|
|
145
|
+
* @returns Parsed settings field information
|
|
146
|
+
*/
|
|
147
|
+
export function parseSettingsField(
|
|
148
|
+
name: string,
|
|
149
|
+
property: JSONSchema7
|
|
150
|
+
): SettingsField | null {
|
|
151
|
+
const title = property.title || name;
|
|
152
|
+
const description = property.description;
|
|
153
|
+
|
|
154
|
+
// Dropdown (enum)
|
|
155
|
+
if (property.enum && Array.isArray(property.enum)) {
|
|
156
|
+
const options = property.enum.map((val) => String(val));
|
|
157
|
+
const defaultValue =
|
|
158
|
+
property.default !== undefined ? String(property.default) : undefined;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
type: "dropdown",
|
|
162
|
+
fieldName: name,
|
|
163
|
+
title,
|
|
164
|
+
description,
|
|
165
|
+
options,
|
|
166
|
+
default: defaultValue,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Slider (number/integer with min/max)
|
|
171
|
+
if (isSlider(property)) {
|
|
172
|
+
const isInteger = property.type === "integer";
|
|
173
|
+
return {
|
|
174
|
+
type: "slider",
|
|
175
|
+
fieldName: name,
|
|
176
|
+
title,
|
|
177
|
+
description,
|
|
178
|
+
min: property.minimum as number,
|
|
179
|
+
max: property.maximum as number,
|
|
180
|
+
step: property.multipleOf,
|
|
181
|
+
default:
|
|
182
|
+
property.default !== undefined
|
|
183
|
+
? (property.default as number)
|
|
184
|
+
: undefined,
|
|
185
|
+
isInteger,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Number input (without slider constraints)
|
|
190
|
+
if (property.type === "number" || property.type === "integer") {
|
|
191
|
+
const isInteger = property.type === "integer";
|
|
192
|
+
return {
|
|
193
|
+
type: "number",
|
|
194
|
+
fieldName: name,
|
|
195
|
+
title,
|
|
196
|
+
description,
|
|
197
|
+
default:
|
|
198
|
+
property.default !== undefined
|
|
199
|
+
? (property.default as number)
|
|
200
|
+
: undefined,
|
|
201
|
+
min: property.minimum as number | undefined,
|
|
202
|
+
max: property.maximum as number | undefined,
|
|
203
|
+
isInteger,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Text input
|
|
208
|
+
if (property.type === "string") {
|
|
209
|
+
return {
|
|
210
|
+
type: "text",
|
|
211
|
+
fieldName: name,
|
|
212
|
+
title,
|
|
213
|
+
description,
|
|
214
|
+
default:
|
|
215
|
+
property.default !== undefined ? String(property.default) : undefined,
|
|
216
|
+
pattern: property.pattern,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Unsupported type
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Parses a complete generator JSON Schema into structured data for UI generation.
|
|
226
|
+
*
|
|
227
|
+
* This function categorizes schema properties into:
|
|
228
|
+
* - Artifact slots (for selecting existing artifacts)
|
|
229
|
+
* - Prompt field (special text input for generation prompts)
|
|
230
|
+
* - Settings fields (sliders, dropdowns, text inputs, etc.)
|
|
231
|
+
*
|
|
232
|
+
* @param schema - The JSON Schema from the generator's inputSchema field
|
|
233
|
+
* @returns Parsed schema structure ready for dynamic UI generation
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```typescript
|
|
237
|
+
* const generator = generators[0];
|
|
238
|
+
* const parsed = parseGeneratorSchema(generator.inputSchema);
|
|
239
|
+
*
|
|
240
|
+
* // Render artifact slots
|
|
241
|
+
* parsed.artifactSlots.forEach(slot => {
|
|
242
|
+
* console.log(`${slot.name}: ${slot.artifactType} (required: ${slot.required})`);
|
|
243
|
+
* });
|
|
244
|
+
*
|
|
245
|
+
* // Render prompt field
|
|
246
|
+
* if (parsed.promptField) {
|
|
247
|
+
* console.log(`Prompt: ${parsed.promptField.description}`);
|
|
248
|
+
* }
|
|
249
|
+
*
|
|
250
|
+
* // Render settings
|
|
251
|
+
* parsed.settingsFields.forEach(field => {
|
|
252
|
+
* if (field.type === 'slider') {
|
|
253
|
+
* console.log(`${field.title}: ${field.min} - ${field.max}`);
|
|
254
|
+
* }
|
|
255
|
+
* });
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export function parseGeneratorSchema(
|
|
259
|
+
schema: JSONSchema7
|
|
260
|
+
): ParsedGeneratorSchema {
|
|
261
|
+
const artifactSlots: ArtifactSlot[] = [];
|
|
262
|
+
const settingsFields: SettingsField[] = [];
|
|
263
|
+
let promptField: PromptField | null = null;
|
|
264
|
+
|
|
265
|
+
if (!schema.properties) {
|
|
266
|
+
return { artifactSlots, promptField, settingsFields };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const required = schema.required || [];
|
|
270
|
+
|
|
271
|
+
for (const [name, propertyDef] of Object.entries(schema.properties)) {
|
|
272
|
+
if (typeof propertyDef === "boolean") {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const property = propertyDef as JSONSchema7;
|
|
277
|
+
const isRequired = required.includes(name);
|
|
278
|
+
|
|
279
|
+
// Check if this is an artifact reference
|
|
280
|
+
if (isArtifactReference(property)) {
|
|
281
|
+
const slot = parseArtifactSlot(name, property, isRequired);
|
|
282
|
+
artifactSlots.push(slot);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check if this is the prompt field
|
|
287
|
+
if (name === "prompt" && property.type === "string") {
|
|
288
|
+
promptField = {
|
|
289
|
+
fieldName: name,
|
|
290
|
+
description: property.description,
|
|
291
|
+
required: isRequired,
|
|
292
|
+
default:
|
|
293
|
+
property.default !== undefined ? String(property.default) : undefined,
|
|
294
|
+
};
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Everything else goes to settings
|
|
299
|
+
const settingsField = parseSettingsField(name, property);
|
|
300
|
+
if (settingsField) {
|
|
301
|
+
settingsFields.push(settingsField);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
artifactSlots,
|
|
307
|
+
promptField,
|
|
308
|
+
settingsFields,
|
|
309
|
+
};
|
|
310
|
+
}
|