lumina-slides 9.0.5 → 9.0.7
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 +63 -0
- package/dist/lumina-slides.js +21750 -19334
- package/dist/lumina-slides.umd.cjs +223 -223
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/LandingPage.vue +1 -1
- package/src/components/LuminaDeck.vue +237 -232
- package/src/components/base/LuminaElement.vue +2 -0
- package/src/components/layouts/LayoutFeatures.vue +125 -123
- package/src/components/layouts/LayoutFlex.vue +212 -212
- package/src/components/layouts/LayoutStatement.vue +5 -2
- package/src/components/layouts/LayoutSteps.vue +110 -108
- package/src/components/parts/FlexHtml.vue +65 -65
- package/src/components/parts/FlexImage.vue +81 -81
- package/src/components/site/SiteDocs.vue +3313 -3314
- package/src/components/site/SiteExamples.vue +66 -66
- package/src/components/studio/EditorLayoutChart.vue +18 -0
- package/src/components/studio/EditorLayoutCustom.vue +18 -0
- package/src/components/studio/EditorLayoutVideo.vue +18 -0
- package/src/components/studio/LuminaStudioEmbed.vue +68 -0
- package/src/components/studio/StudioEmbedRoot.vue +19 -0
- package/src/components/studio/StudioInspector.vue +1113 -7
- package/src/components/studio/StudioJsonEditor.vue +10 -3
- package/src/components/studio/StudioSettings.vue +658 -7
- package/src/components/studio/StudioToolbar.vue +26 -7
- package/src/composables/useElementState.ts +12 -1
- package/src/composables/useFlexLayout.ts +128 -128
- package/src/core/Lumina.ts +174 -113
- package/src/core/animationConfig.ts +10 -0
- package/src/core/elementController.ts +18 -0
- package/src/core/elementResolver.ts +4 -2
- package/src/core/schema.ts +503 -503
- package/src/core/store.ts +465 -465
- package/src/core/types.ts +26 -11
- package/src/index.ts +2 -2
- package/src/utils/prepareDeckForExport.ts +47 -0
- package/src/utils/templateInterpolation.ts +52 -52
- package/src/views/DeckView.vue +313 -313
package/src/core/schema.ts
CHANGED
|
@@ -1,503 +1,503 @@
|
|
|
1
|
-
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
// Import types to ensure we are matching the contract
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Normalizes short aliases (t -> title, s -> subtitle) to full property names.
|
|
7
|
-
*/
|
|
8
|
-
export const normalizeAliases = (obj: any): any => {
|
|
9
|
-
if (!obj || typeof obj !== 'object') return obj;
|
|
10
|
-
|
|
11
|
-
// Handle Arrays Recursively
|
|
12
|
-
if (Array.isArray(obj)) {
|
|
13
|
-
return obj.map(item => normalizeAliases(item));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const map: Record<string, string> = {
|
|
17
|
-
't': 'title',
|
|
18
|
-
's': 'subtitle',
|
|
19
|
-
'd': 'description',
|
|
20
|
-
'desc': 'description',
|
|
21
|
-
'bg': 'background',
|
|
22
|
-
'img': 'image',
|
|
23
|
-
'fs': 'features',
|
|
24
|
-
'tl': 'timeline',
|
|
25
|
-
'st': 'steps'
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const newObj: any = {};
|
|
29
|
-
for (const key in obj) {
|
|
30
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
31
|
-
const normalizedKey = map[key] || key;
|
|
32
|
-
const value = obj[key];
|
|
33
|
-
|
|
34
|
-
// Recurse into values (objects or arrays)
|
|
35
|
-
newObj[normalizedKey] = normalizeAliases(value);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return newObj;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// --- 1. Fuzzy Helpers (Improvement: "Fuzzy Validation") ---
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Creates a schema that accepts T, [T], null, or undefined,
|
|
46
|
-
* and always returns T[] (empty array if null/undefined).
|
|
47
|
-
*/
|
|
48
|
-
const fuzzyArray = <T extends z.ZodTypeAny>(schema: T) => {
|
|
49
|
-
return z
|
|
50
|
-
.union([schema, z.array(schema), z.null(), z.undefined()])
|
|
51
|
-
.transform((val) => {
|
|
52
|
-
if (val === null || val === undefined) return [];
|
|
53
|
-
if (Array.isArray(val)) return val;
|
|
54
|
-
return [val];
|
|
55
|
-
});
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Coerces numbers/nulls to string.
|
|
60
|
-
*/
|
|
61
|
-
const fuzzyString = z
|
|
62
|
-
.union([z.string(), z.number(), z.null(), z.undefined()])
|
|
63
|
-
.transform((val) => (val === null || val === undefined) ? "" : String(val));
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// --- 2. Primitives ---
|
|
68
|
-
|
|
69
|
-
const IconSchema = z.string().describe("Icon name (e.g. 'star', 'users', 'arrow-right'). Use standard Lucide/FontAwesome names.");
|
|
70
|
-
|
|
71
|
-
// --- 3. Slide Schemas (Improvement: "Semantic Descriptions") ---
|
|
72
|
-
|
|
73
|
-
// --- 3. Slide Schemas (Improvement: "Semantic Descriptions") ---
|
|
74
|
-
|
|
75
|
-
// 1. Define the BASE SHAPE (ZodObject) so we can extend it.
|
|
76
|
-
const SlideBaseShape = z.object({
|
|
77
|
-
id: z.string().optional().describe("Unique ID for the slide. Useful for navigation."),
|
|
78
|
-
sizing: z.enum(['viewport', 'container']).optional().describe("How the slide fits the screen. Default: 'viewport'."),
|
|
79
|
-
meta: z.record(z.string(), z.any()).optional().describe("Arbitrary metadata for custom behavior."),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Layout: Statement
|
|
83
|
-
const SlideStatementSchema = SlideBaseShape.extend({
|
|
84
|
-
type: z.literal('statement').describe("Use for high-impact titles, covers, or quotes."),
|
|
85
|
-
title: fuzzyString.describe("Main headline. Keep it punchy (3-6 words)."),
|
|
86
|
-
subtitle: fuzzyString.optional().describe("Supporting text or attribution."),
|
|
87
|
-
tag: fuzzyString.optional().describe("Small eyebrow tag above the title."),
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Layout: Features
|
|
91
|
-
const FeatureItemSchema = z.object({
|
|
92
|
-
title: fuzzyString.describe("Feature name."),
|
|
93
|
-
description: fuzzyString.describe("Short benefit description."),
|
|
94
|
-
icon: IconSchema.optional().describe("Visual icon for the feature."),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const SlideFeaturesSchema = SlideBaseShape.extend({
|
|
98
|
-
type: z.literal('features').describe("Use for lists of benefits, stats, or grid items."),
|
|
99
|
-
title: fuzzyString.describe("Section header."),
|
|
100
|
-
description: fuzzyString.optional().describe("Contextual description."),
|
|
101
|
-
features: fuzzyArray(FeatureItemSchema).describe("Array of feature cards."),
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Layout: Timeline
|
|
105
|
-
const TimelineItemSchema = z.object({
|
|
106
|
-
date: fuzzyString.describe("Time marker (Year, Date, or 'Now')."),
|
|
107
|
-
title: fuzzyString.describe("Event title."),
|
|
108
|
-
description: fuzzyString.describe("Event details (1-2 sentences)."),
|
|
109
|
-
icon: IconSchema.optional().describe("Optional marker icon."),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const SlideTimelineSchema = SlideBaseShape.extend({
|
|
113
|
-
type: z.literal('timeline').describe("Use for history, roadmaps, or chronological steps."),
|
|
114
|
-
title: fuzzyString.describe("Timeline header."),
|
|
115
|
-
subtitle: fuzzyString.optional(),
|
|
116
|
-
timeline: fuzzyArray(TimelineItemSchema).describe("Ordered events."),
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Layout: Steps
|
|
120
|
-
const StepItemSchema = z.object({
|
|
121
|
-
step: fuzzyString.describe("Step number/label (e.g., '01', 'A')."),
|
|
122
|
-
title: fuzzyString.describe("Action title."),
|
|
123
|
-
description: fuzzyString.optional().describe("Action details."),
|
|
124
|
-
icon: IconSchema.optional(),
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const SlideStepsSchema = SlideBaseShape.extend({
|
|
128
|
-
type: z.literal('steps').describe("Use for tutorials, 'how-to' guides, or process flows."),
|
|
129
|
-
title: fuzzyString.describe("Process header."),
|
|
130
|
-
subtitle: fuzzyString.optional(),
|
|
131
|
-
steps: fuzzyArray(StepItemSchema).describe("Sequential steps."),
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Layout: Half
|
|
135
|
-
const SlideHalfSchema = SlideBaseShape.extend({
|
|
136
|
-
type: z.literal('half').describe("Use when you have a strong visual + explanations."),
|
|
137
|
-
imageSide: z.enum(['left', 'right']).default('left').describe("Which side the image goes on."),
|
|
138
|
-
image: fuzzyString.describe("URL to the image."),
|
|
139
|
-
title: fuzzyString.describe("Headline."),
|
|
140
|
-
tag: fuzzyString.optional(),
|
|
141
|
-
paragraphs: fuzzyArray(z.string()).describe("Array of text blocks/paragraphs."),
|
|
142
|
-
cta: fuzzyString.optional().describe("Call to Action label (button)."),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Layout: Flex - Spacing and Size Tokens
|
|
146
|
-
const SpacingTokenSchema = z.enum(['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']).describe("Spacing size token.");
|
|
147
|
-
const FlexSizeSchema = z.enum(['auto', 'quarter', 'third', 'half', 'two-thirds', 'three-quarters', 'full']).describe("Element size in container.");
|
|
148
|
-
const VAlignSchema = z.enum(['top', 'center', 'bottom']).describe("Vertical alignment.");
|
|
149
|
-
const HAlignSchema = z.enum(['left', 'center', 'right']).describe("Horizontal alignment.");
|
|
150
|
-
const TextAlignSchema = z.enum(['left', 'center', 'right']).describe("Text alignment.");
|
|
151
|
-
|
|
152
|
-
// Flex Element Schemas
|
|
153
|
-
const FlexElementTitleSchema = z.object({
|
|
154
|
-
type: z.literal('title').describe("Large heading text."),
|
|
155
|
-
text: fuzzyString.describe("The heading text."),
|
|
156
|
-
size: z.enum(['lg', 'xl', '2xl', '3xl']).optional().describe("Heading size. Default: 'xl'."),
|
|
157
|
-
align: TextAlignSchema.optional().describe("Text alignment. Default: 'left'."),
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const FlexElementTextSchema = z.object({
|
|
161
|
-
type: z.literal('text').describe("Body paragraph text."),
|
|
162
|
-
text: fuzzyString.describe("The paragraph text."),
|
|
163
|
-
align: TextAlignSchema.optional().describe("Text alignment. Default: 'left'."),
|
|
164
|
-
muted: z.boolean().optional().describe("Muted/subtle appearance. Default: false."),
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const FlexElementBulletsSchema = z.object({
|
|
168
|
-
type: z.literal('bullets').describe("Unordered bullet list."),
|
|
169
|
-
items: fuzzyArray(z.string()).describe("List items."),
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const FlexElementOrderedSchema = z.object({
|
|
173
|
-
type: z.literal('ordered').describe("Numbered list."),
|
|
174
|
-
items: fuzzyArray(z.string()).describe("List items."),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const FlexElementImageSchema = z.object({
|
|
178
|
-
type: z.literal('image').describe("Visual media element."),
|
|
179
|
-
src: fuzzyString.describe("Image URL."),
|
|
180
|
-
alt: fuzzyString.optional().describe("Alt text for accessibility."),
|
|
181
|
-
fill: z.boolean().optional().describe("Fill container edge-to-edge. Default: true."),
|
|
182
|
-
fit: z.enum(['cover', 'contain', 'fill', 'none', 'scale-down']).optional().describe("Object-fit mode. Default: 'cover' when fill is true."),
|
|
183
|
-
position: fuzzyString.optional().describe("Object-position for image placement. Default: 'center'."),
|
|
184
|
-
rounded: z.enum(['none', 'sm', 'md', 'lg', 'xl', 'full']).optional().describe("Border radius."),
|
|
185
|
-
href: fuzzyString.optional().describe("Link URL when image is clicked."),
|
|
186
|
-
target: z.enum(['_blank', '_self']).optional().describe("Link target. Default: '_blank'."),
|
|
187
|
-
class: fuzzyString.optional().describe("Custom CSS class."),
|
|
188
|
-
style: z.record(z.string()).optional().describe("Custom CSS style object."),
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const FlexElementHtmlSchema = z.object({
|
|
192
|
-
type: z.literal('html').describe("Raw HTML content element."),
|
|
193
|
-
html: fuzzyString.describe("Raw HTML string to render."),
|
|
194
|
-
class: fuzzyString.optional().describe("Custom CSS class."),
|
|
195
|
-
style: z.record(z.string()).optional().describe("Custom CSS style object."),
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const FlexElementButtonSchema = z.object({
|
|
199
|
-
type: z.literal('button').describe("Call-to-action button."),
|
|
200
|
-
label: fuzzyString.describe("Button label text."),
|
|
201
|
-
action: fuzzyString.optional().describe("Action identifier emitted on click."),
|
|
202
|
-
variant: z.enum(['primary', 'secondary', 'outline', 'ghost']).optional().describe("Visual variant. Default: 'primary'."),
|
|
203
|
-
fullWidth: z.boolean().optional().describe("Full width button. Default: false."),
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const FlexElementTimelineSchema = z.object({
|
|
207
|
-
type: z.literal('timeline').describe("Embedded timeline with events."),
|
|
208
|
-
items: fuzzyArray(TimelineItemSchema).describe("Timeline events."),
|
|
209
|
-
compact: z.boolean().optional().describe("Compact display mode. Default: false."),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const FlexElementStepperSchema = z.object({
|
|
213
|
-
type: z.literal('stepper').describe("Embedded step-by-step process."),
|
|
214
|
-
items: fuzzyArray(StepItemSchema).describe("Step items."),
|
|
215
|
-
compact: z.boolean().optional().describe("Compact display mode. Default: false."),
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
const FlexElementSpacerSchema = z.object({
|
|
219
|
-
type: z.literal('spacer').describe("Adds visual spacing."),
|
|
220
|
-
size: SpacingTokenSchema.optional().describe("Size of the space. Default: 'md'."),
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Child elements (inside content container) - supports nested content, html, and image
|
|
224
|
-
// We need to use lazy evaluation for recursive content support
|
|
225
|
-
let FlexChildElementSchema: z.ZodType<any>;
|
|
226
|
-
let FlexElementContentSchema: z.ZodType<any>;
|
|
227
|
-
|
|
228
|
-
// Define content schema with lazy reference to child elements (for recursion)
|
|
229
|
-
FlexElementContentSchema = z.lazy(() => z.object({
|
|
230
|
-
type: z.literal('content').describe("Groups child elements with alignment control. Supports nested content."),
|
|
231
|
-
elements: fuzzyArray(FlexChildElementSchema).describe("Child elements. Can include nested content containers."),
|
|
232
|
-
direction: z.enum(['horizontal', 'vertical']).optional().describe("Layout direction. Default: 'vertical'."),
|
|
233
|
-
valign: VAlignSchema.optional().describe("Vertical alignment. Default: 'center'."),
|
|
234
|
-
halign: HAlignSchema.optional().describe("Horizontal alignment. Default: 'left'."),
|
|
235
|
-
gap: SpacingTokenSchema.optional().describe("Gap between children. Default: 'md'."),
|
|
236
|
-
padding: SpacingTokenSchema.optional().describe("Internal padding. Default: 'lg'."),
|
|
237
|
-
size: FlexSizeSchema.optional().describe("Size in parent flex container."),
|
|
238
|
-
class: fuzzyString.optional().describe("Custom CSS class."),
|
|
239
|
-
style: z.record(z.string()).optional().describe("Custom CSS style object."),
|
|
240
|
-
}));
|
|
241
|
-
|
|
242
|
-
// Define child element schema with reference to content (for nesting)
|
|
243
|
-
FlexChildElementSchema = z.discriminatedUnion('type', [
|
|
244
|
-
FlexElementTitleSchema,
|
|
245
|
-
FlexElementTextSchema,
|
|
246
|
-
FlexElementBulletsSchema,
|
|
247
|
-
FlexElementOrderedSchema,
|
|
248
|
-
FlexElementButtonSchema,
|
|
249
|
-
FlexElementTimelineSchema,
|
|
250
|
-
FlexElementStepperSchema,
|
|
251
|
-
FlexElementSpacerSchema,
|
|
252
|
-
FlexElementHtmlSchema,
|
|
253
|
-
FlexElementImageSchema,
|
|
254
|
-
FlexElementContentSchema,
|
|
255
|
-
]);
|
|
256
|
-
|
|
257
|
-
// Top-level flex element (with size property)
|
|
258
|
-
// Note: FlexElementContentSchema already includes size in its definition, so we don't need to extend it
|
|
259
|
-
const FlexElementSchema = z.discriminatedUnion('type', [
|
|
260
|
-
FlexElementImageSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
261
|
-
FlexElementContentSchema, // Already includes size, no need to extend
|
|
262
|
-
FlexElementHtmlSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
263
|
-
FlexElementTitleSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
264
|
-
FlexElementTextSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
265
|
-
FlexElementBulletsSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
266
|
-
FlexElementOrderedSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
267
|
-
FlexElementButtonSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
268
|
-
FlexElementTimelineSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
269
|
-
FlexElementStepperSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
270
|
-
]);
|
|
271
|
-
|
|
272
|
-
const SlideFlexSchema = SlideBaseShape.extend({
|
|
273
|
-
type: z.literal('flex').describe("Flow-based layout. Elements flow in order with semantic sizing."),
|
|
274
|
-
direction: z.enum(['horizontal', 'vertical']).optional().describe("Main flow direction. Default: 'horizontal'."),
|
|
275
|
-
gap: SpacingTokenSchema.optional().describe("Gap between elements. Default: 'none'."),
|
|
276
|
-
padding: SpacingTokenSchema.optional().describe("Container padding. Default: 'none'."),
|
|
277
|
-
elements: fuzzyArray(FlexElementSchema).describe("Flex elements in flow order."),
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Union of all Slides (Validation Layer)
|
|
281
|
-
export const SlideSchema = z.discriminatedUnion('type', [
|
|
282
|
-
SlideStatementSchema,
|
|
283
|
-
SlideFeaturesSchema,
|
|
284
|
-
SlideTimelineSchema,
|
|
285
|
-
SlideStepsSchema,
|
|
286
|
-
SlideHalfSchema,
|
|
287
|
-
SlideFlexSchema,
|
|
288
|
-
]);
|
|
289
|
-
|
|
290
|
-
// --- THEME CONFIGURATION SCHEMA ---
|
|
291
|
-
// Comprehensive design token system for LLM-driven theming
|
|
292
|
-
|
|
293
|
-
const ThemeColorsSchema = z.object({
|
|
294
|
-
primary: z.string().optional().describe("Main brand/accent color. Used for highlights, buttons, links. Example: '#3b82f6'"),
|
|
295
|
-
secondary: z.string().optional().describe("Secondary brand color. Used for complementary elements. Example: '#8b5cf6'"),
|
|
296
|
-
accent: z.string().optional().describe("Tertiary/accent color. Used for special highlights. Example: '#06b6d4'"),
|
|
297
|
-
background: z.string().optional().describe("Main background color. Example: '#030303'"),
|
|
298
|
-
surface: z.string().optional().describe("Elevated surface color (cards, panels, modals). Example: '#0a0a0a'"),
|
|
299
|
-
text: z.string().optional().describe("Primary text color. Example: '#ffffff'"),
|
|
300
|
-
textSecondary: z.string().optional().describe("Secondary/subdued text color. Example: '#e5e7eb'"),
|
|
301
|
-
muted: z.string().optional().describe("Muted/disabled text color. Example: '#9ca3af'"),
|
|
302
|
-
success: z.string().optional().describe("Success state color (green tones). Example: '#10b981'"),
|
|
303
|
-
warning: z.string().optional().describe("Warning state color (yellow/amber). Example: '#f59e0b'"),
|
|
304
|
-
danger: z.string().optional().describe("Danger/error state color (red). Example: '#ef4444'"),
|
|
305
|
-
info: z.string().optional().describe("Informational state color (blue). Example: '#3b82f6'"),
|
|
306
|
-
border: z.string().optional().describe("Border color for containers, cards. Example: 'rgba(255,255,255,0.1)'"),
|
|
307
|
-
borderSubtle: z.string().optional().describe("Subtle border for dividers. Example: 'rgba(255,255,255,0.05)'"),
|
|
308
|
-
shadow: z.string().optional().describe("Shadow color. Example: '#000000'"),
|
|
309
|
-
overlay: z.string().optional().describe("Overlay color for modals. Example: 'rgba(0,0,0,0.5)'"),
|
|
310
|
-
highlight: z.string().optional().describe("Highlight/selection color."),
|
|
311
|
-
buttonPrimary: z.string().optional().describe("Primary button background."),
|
|
312
|
-
buttonPrimaryText: z.string().optional().describe("Primary button text color."),
|
|
313
|
-
buttonSecondary: z.string().optional().describe("Secondary button background."),
|
|
314
|
-
buttonSecondaryText: z.string().optional().describe("Secondary button text color."),
|
|
315
|
-
link: z.string().optional().describe("Link color."),
|
|
316
|
-
linkHover: z.string().optional().describe("Link hover color."),
|
|
317
|
-
gradientFrom: z.string().optional().describe("Gradient start color. Example: '#3b82f6'"),
|
|
318
|
-
gradientVia: z.string().optional().describe("Gradient middle color (optional)."),
|
|
319
|
-
gradientTo: z.string().optional().describe("Gradient end color. Example: '#8b5cf6'"),
|
|
320
|
-
}).describe("Color palette configuration with 25+ customizable colors.");
|
|
321
|
-
|
|
322
|
-
const ThemeTypographySchema = z.object({
|
|
323
|
-
fontFamily: z.object({
|
|
324
|
-
heading: z.string().optional().describe("Font for headings. Example: 'Inter, sans-serif'"),
|
|
325
|
-
body: z.string().optional().describe("Font for body text. Example: 'Inter, sans-serif'"),
|
|
326
|
-
mono: z.string().optional().describe("Font for code. Example: 'monospace'"),
|
|
327
|
-
}).optional().describe("Font family definitions."),
|
|
328
|
-
fontSize: z.object({
|
|
329
|
-
xs: z.string().optional().describe("Extra small: 0.75rem"),
|
|
330
|
-
sm: z.string().optional().describe("Small: 0.875rem"),
|
|
331
|
-
base: z.string().optional().describe("Base: 1rem"),
|
|
332
|
-
lg: z.string().optional().describe("Large: 1.125rem"),
|
|
333
|
-
xl: z.string().optional().describe("Extra large: 1.25rem"),
|
|
334
|
-
'2xl': z.string().optional().describe("2XL: 1.5rem"),
|
|
335
|
-
'3xl': z.string().optional().describe("3XL: 1.875rem"),
|
|
336
|
-
'4xl': z.string().optional().describe("4XL: 2.25rem"),
|
|
337
|
-
'5xl': z.string().optional().describe("5XL: 3rem"),
|
|
338
|
-
'6xl': z.string().optional().describe("6XL: 3.75rem"),
|
|
339
|
-
'7xl': z.string().optional().describe("7XL: 4.5rem"),
|
|
340
|
-
}).optional().describe("Font size scale."),
|
|
341
|
-
fontWeight: z.object({
|
|
342
|
-
light: z.number().optional().describe("Light: 300"),
|
|
343
|
-
normal: z.number().optional().describe("Normal: 400"),
|
|
344
|
-
medium: z.number().optional().describe("Medium: 500"),
|
|
345
|
-
semibold: z.number().optional().describe("Semibold: 600"),
|
|
346
|
-
bold: z.number().optional().describe("Bold: 700"),
|
|
347
|
-
extrabold: z.number().optional().describe("Extra bold: 800"),
|
|
348
|
-
}).optional().describe("Font weight scale."),
|
|
349
|
-
lineHeight: z.object({
|
|
350
|
-
tight: z.string().optional().describe("Tight: 1.1"),
|
|
351
|
-
snug: z.string().optional().describe("Snug: 1.25"),
|
|
352
|
-
normal: z.string().optional().describe("Normal: 1.5"),
|
|
353
|
-
relaxed: z.string().optional().describe("Relaxed: 1.625"),
|
|
354
|
-
loose: z.string().optional().describe("Loose: 2"),
|
|
355
|
-
}).optional().describe("Line height scale."),
|
|
356
|
-
letterSpacing: z.object({
|
|
357
|
-
tighter: z.string().optional().describe("-0.05em"),
|
|
358
|
-
tight: z.string().optional().describe("-0.025em"),
|
|
359
|
-
normal: z.string().optional().describe("0"),
|
|
360
|
-
wide: z.string().optional().describe("0.025em"),
|
|
361
|
-
wider: z.string().optional().describe("0.05em"),
|
|
362
|
-
widest: z.string().optional().describe("0.1em"),
|
|
363
|
-
}).optional().describe("Letter spacing scale."),
|
|
364
|
-
}).describe("Typography configuration: fonts, sizes, weights, line-heights.");
|
|
365
|
-
|
|
366
|
-
const ThemeSpacingSchema = z.object({
|
|
367
|
-
none: z.string().optional().describe("0"),
|
|
368
|
-
xs: z.string().optional().describe("0.25rem / 4px"),
|
|
369
|
-
sm: z.string().optional().describe("0.5rem / 8px"),
|
|
370
|
-
md: z.string().optional().describe("1rem / 16px"),
|
|
371
|
-
lg: z.string().optional().describe("1.5rem / 24px"),
|
|
372
|
-
xl: z.string().optional().describe("2rem / 32px"),
|
|
373
|
-
'2xl': z.string().optional().describe("3rem / 48px"),
|
|
374
|
-
'3xl': z.string().optional().describe("4rem / 64px"),
|
|
375
|
-
'4xl': z.string().optional().describe("6rem / 96px"),
|
|
376
|
-
}).describe("Spacing scale for gaps, padding, margins.");
|
|
377
|
-
|
|
378
|
-
const ThemeBorderRadiusSchema = z.object({
|
|
379
|
-
none: z.string().optional().describe("No rounding: 0"),
|
|
380
|
-
sm: z.string().optional().describe("Small: 0.25rem"),
|
|
381
|
-
md: z.string().optional().describe("Medium: 0.5rem"),
|
|
382
|
-
lg: z.string().optional().describe("Large: 0.75rem"),
|
|
383
|
-
xl: z.string().optional().describe("Extra large: 1rem"),
|
|
384
|
-
'2xl': z.string().optional().describe("2XL: 1.5rem"),
|
|
385
|
-
'3xl': z.string().optional().describe("3XL: 2rem"),
|
|
386
|
-
full: z.string().optional().describe("Full circle: 9999px"),
|
|
387
|
-
}).describe("Border radius scale for rounded corners.");
|
|
388
|
-
|
|
389
|
-
const ThemeEffectsSchema = z.object({
|
|
390
|
-
useGradients: z.boolean().optional().describe("Enable/disable gradient effects globally."),
|
|
391
|
-
gradientDirection: z.enum(['to-t', 'to-b', 'to-l', 'to-r', 'to-tl', 'to-tr', 'to-bl', 'to-br']).optional().describe("Gradient direction."),
|
|
392
|
-
gradientFrom: z.string().optional().describe("Override gradient start color."),
|
|
393
|
-
gradientVia: z.string().optional().describe("Override gradient middle color."),
|
|
394
|
-
gradientTo: z.string().optional().describe("Override gradient end color."),
|
|
395
|
-
useShadows: z.boolean().optional().describe("Enable/disable shadow effects."),
|
|
396
|
-
shadowIntensity: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).optional().describe("Shadow intensity level."),
|
|
397
|
-
shadowColor: z.string().optional().describe("Shadow color."),
|
|
398
|
-
useGlass: z.boolean().optional().describe("Enable/disable glassmorphism effect."),
|
|
399
|
-
glassOpacity: z.number().optional().describe("Glass panel opacity (0-1)."),
|
|
400
|
-
glassBlur: z.string().optional().describe("Glass blur amount. Example: '20px'"),
|
|
401
|
-
glassBorderOpacity: z.number().optional().describe("Glass border opacity (0-1)."),
|
|
402
|
-
useOrb: z.boolean().optional().describe("Enable/disable ambient orb/glow effect."),
|
|
403
|
-
orbOpacity: z.number().optional().describe("Orb opacity (0-1)."),
|
|
404
|
-
orbBlur: z.string().optional().describe("Orb blur amount. Example: '120px'"),
|
|
405
|
-
orbSize: z.string().optional().describe("Orb size. Example: '60vw'"),
|
|
406
|
-
animationsEnabled: z.boolean().optional().describe("Enable/disable animations globally."),
|
|
407
|
-
transitionDuration: z.string().optional().describe("Default transition duration. Example: '0.3s'"),
|
|
408
|
-
transitionEasing: z.string().optional().describe("Default transition easing. Example: 'ease-out'"),
|
|
409
|
-
hoverScale: z.number().optional().describe("Hover scale factor. Example: 1.05"),
|
|
410
|
-
}).describe("Visual effects: gradients, shadows, glass, orb, animations.");
|
|
411
|
-
|
|
412
|
-
const ThemeComponentsSchema = z.object({
|
|
413
|
-
buttonRadius: z.string().optional().describe("Button border radius."),
|
|
414
|
-
buttonPadding: z.string().optional().describe("Button padding. Example: '0.75rem 1.5rem'"),
|
|
415
|
-
buttonFontWeight: z.number().optional().describe("Button font weight."),
|
|
416
|
-
buttonTextTransform: z.enum(['none', 'uppercase', 'capitalize']).optional().describe("Button text transform."),
|
|
417
|
-
cardRadius: z.string().optional().describe("Card border radius."),
|
|
418
|
-
cardPadding: z.string().optional().describe("Card padding."),
|
|
419
|
-
cardBorderWidth: z.string().optional().describe("Card border width. Example: '1px'"),
|
|
420
|
-
cardBackground: z.string().optional().describe("Card background override."),
|
|
421
|
-
timelineNodeSize: z.string().optional().describe("Timeline node size. Example: '1rem'"),
|
|
422
|
-
timelineLineWidth: z.string().optional().describe("Timeline line width. Example: '2px'"),
|
|
423
|
-
timelineNodeColor: z.string().optional().describe("Timeline node color."),
|
|
424
|
-
timelineLineColor: z.string().optional().describe("Timeline line color."),
|
|
425
|
-
stepBadgeSize: z.string().optional().describe("Step badge size."),
|
|
426
|
-
stepFontSize: z.string().optional().describe("Step font size."),
|
|
427
|
-
progressHeight: z.string().optional().describe("Progress bar height."),
|
|
428
|
-
progressRadius: z.string().optional().describe("Progress bar border radius."),
|
|
429
|
-
progressBackground: z.string().optional().describe("Progress bar background."),
|
|
430
|
-
progressFill: z.string().optional().describe("Progress bar fill color."),
|
|
431
|
-
tagPadding: z.string().optional().describe("Tag padding."),
|
|
432
|
-
tagRadius: z.string().optional().describe("Tag border radius."),
|
|
433
|
-
tagFontSize: z.string().optional().describe("Tag font size."),
|
|
434
|
-
inputRadius: z.string().optional().describe("Input border radius."),
|
|
435
|
-
inputPadding: z.string().optional().describe("Input padding."),
|
|
436
|
-
inputBorder: z.string().optional().describe("Input border color."),
|
|
437
|
-
inputFocusBorder: z.string().optional().describe("Input focus border color."),
|
|
438
|
-
}).describe("Component-specific theming: buttons, cards, timeline, steps, etc.");
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Complete Theme Configuration Schema.
|
|
442
|
-
* Comprehensive design token system for full slide customization by LLMs.
|
|
443
|
-
*/
|
|
444
|
-
export const ThemeConfigSchema = z.object({
|
|
445
|
-
colors: ThemeColorsSchema.optional().describe("Color palette with 25+ customizable colors."),
|
|
446
|
-
typography: ThemeTypographySchema.optional().describe("Typography: fonts, sizes, weights, line-heights."),
|
|
447
|
-
spacing: ThemeSpacingSchema.optional().describe("Spacing scale for consistent gaps and padding."),
|
|
448
|
-
borderRadius: ThemeBorderRadiusSchema.optional().describe("Border radius tokens for rounded corners."),
|
|
449
|
-
effects: ThemeEffectsSchema.optional().describe("Visual effects: gradients, shadows, glass, orb."),
|
|
450
|
-
components: ThemeComponentsSchema.optional().describe("Component-specific styling tokens."),
|
|
451
|
-
}).describe("Complete theme configuration for full visual customization.");
|
|
452
|
-
|
|
453
|
-
// Available theme preset names
|
|
454
|
-
const ThemePresetSchema = z.enum([
|
|
455
|
-
'default', 'ocean', 'midnight', 'forest', 'cyber', 'latte', 'sunset', 'monochrome'
|
|
456
|
-
]).describe("Built-in theme preset name.");
|
|
457
|
-
|
|
458
|
-
// Deck Schema
|
|
459
|
-
export const DeckMetaSchema = z.object({
|
|
460
|
-
title: fuzzyString.describe("Title of the entire presentation."),
|
|
461
|
-
author: fuzzyString.optional(),
|
|
462
|
-
date: fuzzyString.optional(),
|
|
463
|
-
theme: ThemePresetSchema.optional().describe("Theme preset to use. Options: default, ocean, midnight, forest, cyber, latte, sunset, monochrome."),
|
|
464
|
-
themeConfig: ThemeConfigSchema.optional().describe("Custom theme configuration to override preset values."),
|
|
465
|
-
}).passthrough();
|
|
466
|
-
|
|
467
|
-
export const DeckSchema = z.object({
|
|
468
|
-
meta: DeckMetaSchema.describe("Global deck settings including title and theme."),
|
|
469
|
-
// Apply normalization (Alias -> Full key) at the entrance of each slide
|
|
470
|
-
slides: z.array(z.preprocess(normalizeAliases, SlideSchema)).describe("Ordered array of slide definitions."),
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// --- 4. Utilities (Improvement: "JSON Schema Export") ---
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Exports the JSON Schema for the Deck structure.
|
|
478
|
-
* Useful for passing to OpenAI `response_format`.
|
|
479
|
-
*/
|
|
480
|
-
export function getLuminaJsonSchema() {
|
|
481
|
-
try {
|
|
482
|
-
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
483
|
-
return zodToJsonSchema(DeckSchema, "LuminaDeck");
|
|
484
|
-
} catch (e) {
|
|
485
|
-
console.warn("zod-to-json-schema not installed. Install it to export raw JSON Schema.");
|
|
486
|
-
return {};
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Exports the JSON Schema for Theme Configuration only.
|
|
492
|
-
* Useful for LLMs to understand all available theming options.
|
|
493
|
-
*/
|
|
494
|
-
export function getThemeJsonSchema() {
|
|
495
|
-
try {
|
|
496
|
-
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
497
|
-
return zodToJsonSchema(ThemeConfigSchema, "LuminaThemeConfig");
|
|
498
|
-
} catch (e) {
|
|
499
|
-
console.warn("zod-to-json-schema not installed. Install it to export raw JSON Schema.");
|
|
500
|
-
return {};
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
1
|
+
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
// Import types to ensure we are matching the contract
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes short aliases (t -> title, s -> subtitle) to full property names.
|
|
7
|
+
*/
|
|
8
|
+
export const normalizeAliases = (obj: any): any => {
|
|
9
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
10
|
+
|
|
11
|
+
// Handle Arrays Recursively
|
|
12
|
+
if (Array.isArray(obj)) {
|
|
13
|
+
return obj.map(item => normalizeAliases(item));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const map: Record<string, string> = {
|
|
17
|
+
't': 'title',
|
|
18
|
+
's': 'subtitle',
|
|
19
|
+
'd': 'description',
|
|
20
|
+
'desc': 'description',
|
|
21
|
+
'bg': 'background',
|
|
22
|
+
'img': 'image',
|
|
23
|
+
'fs': 'features',
|
|
24
|
+
'tl': 'timeline',
|
|
25
|
+
'st': 'steps'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const newObj: any = {};
|
|
29
|
+
for (const key in obj) {
|
|
30
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
31
|
+
const normalizedKey = map[key] || key;
|
|
32
|
+
const value = obj[key];
|
|
33
|
+
|
|
34
|
+
// Recurse into values (objects or arrays)
|
|
35
|
+
newObj[normalizedKey] = normalizeAliases(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return newObj;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// --- 1. Fuzzy Helpers (Improvement: "Fuzzy Validation") ---
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a schema that accepts T, [T], null, or undefined,
|
|
46
|
+
* and always returns T[] (empty array if null/undefined).
|
|
47
|
+
*/
|
|
48
|
+
const fuzzyArray = <T extends z.ZodTypeAny>(schema: T) => {
|
|
49
|
+
return z
|
|
50
|
+
.union([schema, z.array(schema), z.null(), z.undefined()])
|
|
51
|
+
.transform((val) => {
|
|
52
|
+
if (val === null || val === undefined) return [];
|
|
53
|
+
if (Array.isArray(val)) return val;
|
|
54
|
+
return [val];
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Coerces numbers/nulls to string.
|
|
60
|
+
*/
|
|
61
|
+
const fuzzyString = z
|
|
62
|
+
.union([z.string(), z.number(), z.null(), z.undefined()])
|
|
63
|
+
.transform((val) => (val === null || val === undefined) ? "" : String(val));
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// --- 2. Primitives ---
|
|
68
|
+
|
|
69
|
+
const IconSchema = z.string().describe("Icon name (e.g. 'star', 'users', 'arrow-right'). Use standard Lucide/FontAwesome names.");
|
|
70
|
+
|
|
71
|
+
// --- 3. Slide Schemas (Improvement: "Semantic Descriptions") ---
|
|
72
|
+
|
|
73
|
+
// --- 3. Slide Schemas (Improvement: "Semantic Descriptions") ---
|
|
74
|
+
|
|
75
|
+
// 1. Define the BASE SHAPE (ZodObject) so we can extend it.
|
|
76
|
+
const SlideBaseShape = z.object({
|
|
77
|
+
id: z.string().optional().describe("Unique ID for the slide. Useful for navigation."),
|
|
78
|
+
sizing: z.enum(['viewport', 'container']).optional().describe("How the slide fits the screen. Default: 'viewport'."),
|
|
79
|
+
meta: z.record(z.string(), z.any()).optional().describe("Arbitrary metadata for custom behavior."),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Layout: Statement
|
|
83
|
+
const SlideStatementSchema = SlideBaseShape.extend({
|
|
84
|
+
type: z.literal('statement').describe("Use for high-impact titles, covers, or quotes."),
|
|
85
|
+
title: fuzzyString.describe("Main headline. Keep it punchy (3-6 words)."),
|
|
86
|
+
subtitle: fuzzyString.optional().describe("Supporting text or attribution."),
|
|
87
|
+
tag: fuzzyString.optional().describe("Small eyebrow tag above the title."),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Layout: Features
|
|
91
|
+
const FeatureItemSchema = z.object({
|
|
92
|
+
title: fuzzyString.describe("Feature name."),
|
|
93
|
+
description: fuzzyString.describe("Short benefit description."),
|
|
94
|
+
icon: IconSchema.optional().describe("Visual icon for the feature."),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const SlideFeaturesSchema = SlideBaseShape.extend({
|
|
98
|
+
type: z.literal('features').describe("Use for lists of benefits, stats, or grid items."),
|
|
99
|
+
title: fuzzyString.describe("Section header."),
|
|
100
|
+
description: fuzzyString.optional().describe("Contextual description."),
|
|
101
|
+
features: fuzzyArray(FeatureItemSchema).describe("Array of feature cards."),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Layout: Timeline
|
|
105
|
+
const TimelineItemSchema = z.object({
|
|
106
|
+
date: fuzzyString.describe("Time marker (Year, Date, or 'Now')."),
|
|
107
|
+
title: fuzzyString.describe("Event title."),
|
|
108
|
+
description: fuzzyString.describe("Event details (1-2 sentences)."),
|
|
109
|
+
icon: IconSchema.optional().describe("Optional marker icon."),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const SlideTimelineSchema = SlideBaseShape.extend({
|
|
113
|
+
type: z.literal('timeline').describe("Use for history, roadmaps, or chronological steps."),
|
|
114
|
+
title: fuzzyString.describe("Timeline header."),
|
|
115
|
+
subtitle: fuzzyString.optional(),
|
|
116
|
+
timeline: fuzzyArray(TimelineItemSchema).describe("Ordered events."),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Layout: Steps
|
|
120
|
+
const StepItemSchema = z.object({
|
|
121
|
+
step: fuzzyString.describe("Step number/label (e.g., '01', 'A')."),
|
|
122
|
+
title: fuzzyString.describe("Action title."),
|
|
123
|
+
description: fuzzyString.optional().describe("Action details."),
|
|
124
|
+
icon: IconSchema.optional(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const SlideStepsSchema = SlideBaseShape.extend({
|
|
128
|
+
type: z.literal('steps').describe("Use for tutorials, 'how-to' guides, or process flows."),
|
|
129
|
+
title: fuzzyString.describe("Process header."),
|
|
130
|
+
subtitle: fuzzyString.optional(),
|
|
131
|
+
steps: fuzzyArray(StepItemSchema).describe("Sequential steps."),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Layout: Half
|
|
135
|
+
const SlideHalfSchema = SlideBaseShape.extend({
|
|
136
|
+
type: z.literal('half').describe("Use when you have a strong visual + explanations."),
|
|
137
|
+
imageSide: z.enum(['left', 'right']).default('left').describe("Which side the image goes on."),
|
|
138
|
+
image: fuzzyString.describe("URL to the image."),
|
|
139
|
+
title: fuzzyString.describe("Headline."),
|
|
140
|
+
tag: fuzzyString.optional(),
|
|
141
|
+
paragraphs: fuzzyArray(z.string()).describe("Array of text blocks/paragraphs."),
|
|
142
|
+
cta: fuzzyString.optional().describe("Call to Action label (button)."),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Layout: Flex - Spacing and Size Tokens
|
|
146
|
+
const SpacingTokenSchema = z.enum(['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']).describe("Spacing size token.");
|
|
147
|
+
const FlexSizeSchema = z.enum(['auto', 'quarter', 'third', 'half', 'two-thirds', 'three-quarters', 'full']).describe("Element size in container.");
|
|
148
|
+
const VAlignSchema = z.enum(['top', 'center', 'bottom']).describe("Vertical alignment.");
|
|
149
|
+
const HAlignSchema = z.enum(['left', 'center', 'right']).describe("Horizontal alignment.");
|
|
150
|
+
const TextAlignSchema = z.enum(['left', 'center', 'right']).describe("Text alignment.");
|
|
151
|
+
|
|
152
|
+
// Flex Element Schemas
|
|
153
|
+
const FlexElementTitleSchema = z.object({
|
|
154
|
+
type: z.literal('title').describe("Large heading text."),
|
|
155
|
+
text: fuzzyString.describe("The heading text."),
|
|
156
|
+
size: z.enum(['lg', 'xl', '2xl', '3xl']).optional().describe("Heading size. Default: 'xl'."),
|
|
157
|
+
align: TextAlignSchema.optional().describe("Text alignment. Default: 'left'."),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const FlexElementTextSchema = z.object({
|
|
161
|
+
type: z.literal('text').describe("Body paragraph text."),
|
|
162
|
+
text: fuzzyString.describe("The paragraph text."),
|
|
163
|
+
align: TextAlignSchema.optional().describe("Text alignment. Default: 'left'."),
|
|
164
|
+
muted: z.boolean().optional().describe("Muted/subtle appearance. Default: false."),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const FlexElementBulletsSchema = z.object({
|
|
168
|
+
type: z.literal('bullets').describe("Unordered bullet list."),
|
|
169
|
+
items: fuzzyArray(z.string()).describe("List items."),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const FlexElementOrderedSchema = z.object({
|
|
173
|
+
type: z.literal('ordered').describe("Numbered list."),
|
|
174
|
+
items: fuzzyArray(z.string()).describe("List items."),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const FlexElementImageSchema = z.object({
|
|
178
|
+
type: z.literal('image').describe("Visual media element."),
|
|
179
|
+
src: fuzzyString.describe("Image URL."),
|
|
180
|
+
alt: fuzzyString.optional().describe("Alt text for accessibility."),
|
|
181
|
+
fill: z.boolean().optional().describe("Fill container edge-to-edge. Default: true."),
|
|
182
|
+
fit: z.enum(['cover', 'contain', 'fill', 'none', 'scale-down']).optional().describe("Object-fit mode. Default: 'cover' when fill is true."),
|
|
183
|
+
position: fuzzyString.optional().describe("Object-position for image placement. Default: 'center'."),
|
|
184
|
+
rounded: z.enum(['none', 'sm', 'md', 'lg', 'xl', 'full']).optional().describe("Border radius."),
|
|
185
|
+
href: fuzzyString.optional().describe("Link URL when image is clicked."),
|
|
186
|
+
target: z.enum(['_blank', '_self']).optional().describe("Link target. Default: '_blank'."),
|
|
187
|
+
class: fuzzyString.optional().describe("Custom CSS class."),
|
|
188
|
+
style: z.record(z.string()).optional().describe("Custom CSS style object."),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const FlexElementHtmlSchema = z.object({
|
|
192
|
+
type: z.literal('html').describe("Raw HTML content element."),
|
|
193
|
+
html: fuzzyString.describe("Raw HTML string to render."),
|
|
194
|
+
class: fuzzyString.optional().describe("Custom CSS class."),
|
|
195
|
+
style: z.record(z.string()).optional().describe("Custom CSS style object."),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const FlexElementButtonSchema = z.object({
|
|
199
|
+
type: z.literal('button').describe("Call-to-action button."),
|
|
200
|
+
label: fuzzyString.describe("Button label text."),
|
|
201
|
+
action: fuzzyString.optional().describe("Action identifier emitted on click."),
|
|
202
|
+
variant: z.enum(['primary', 'secondary', 'outline', 'ghost']).optional().describe("Visual variant. Default: 'primary'."),
|
|
203
|
+
fullWidth: z.boolean().optional().describe("Full width button. Default: false."),
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const FlexElementTimelineSchema = z.object({
|
|
207
|
+
type: z.literal('timeline').describe("Embedded timeline with events."),
|
|
208
|
+
items: fuzzyArray(TimelineItemSchema).describe("Timeline events."),
|
|
209
|
+
compact: z.boolean().optional().describe("Compact display mode. Default: false."),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const FlexElementStepperSchema = z.object({
|
|
213
|
+
type: z.literal('stepper').describe("Embedded step-by-step process."),
|
|
214
|
+
items: fuzzyArray(StepItemSchema).describe("Step items."),
|
|
215
|
+
compact: z.boolean().optional().describe("Compact display mode. Default: false."),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const FlexElementSpacerSchema = z.object({
|
|
219
|
+
type: z.literal('spacer').describe("Adds visual spacing."),
|
|
220
|
+
size: SpacingTokenSchema.optional().describe("Size of the space. Default: 'md'."),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Child elements (inside content container) - supports nested content, html, and image
|
|
224
|
+
// We need to use lazy evaluation for recursive content support
|
|
225
|
+
let FlexChildElementSchema: z.ZodType<any>;
|
|
226
|
+
let FlexElementContentSchema: z.ZodType<any>;
|
|
227
|
+
|
|
228
|
+
// Define content schema with lazy reference to child elements (for recursion)
|
|
229
|
+
FlexElementContentSchema = z.lazy(() => z.object({
|
|
230
|
+
type: z.literal('content').describe("Groups child elements with alignment control. Supports nested content."),
|
|
231
|
+
elements: fuzzyArray(FlexChildElementSchema).describe("Child elements. Can include nested content containers."),
|
|
232
|
+
direction: z.enum(['horizontal', 'vertical']).optional().describe("Layout direction. Default: 'vertical'."),
|
|
233
|
+
valign: VAlignSchema.optional().describe("Vertical alignment. Default: 'center'."),
|
|
234
|
+
halign: HAlignSchema.optional().describe("Horizontal alignment. Default: 'left'."),
|
|
235
|
+
gap: SpacingTokenSchema.optional().describe("Gap between children. Default: 'md'."),
|
|
236
|
+
padding: SpacingTokenSchema.optional().describe("Internal padding. Default: 'lg'."),
|
|
237
|
+
size: FlexSizeSchema.optional().describe("Size in parent flex container."),
|
|
238
|
+
class: fuzzyString.optional().describe("Custom CSS class."),
|
|
239
|
+
style: z.record(z.string()).optional().describe("Custom CSS style object."),
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
// Define child element schema with reference to content (for nesting)
|
|
243
|
+
FlexChildElementSchema = z.discriminatedUnion('type', [
|
|
244
|
+
FlexElementTitleSchema,
|
|
245
|
+
FlexElementTextSchema,
|
|
246
|
+
FlexElementBulletsSchema,
|
|
247
|
+
FlexElementOrderedSchema,
|
|
248
|
+
FlexElementButtonSchema,
|
|
249
|
+
FlexElementTimelineSchema,
|
|
250
|
+
FlexElementStepperSchema,
|
|
251
|
+
FlexElementSpacerSchema,
|
|
252
|
+
FlexElementHtmlSchema,
|
|
253
|
+
FlexElementImageSchema,
|
|
254
|
+
FlexElementContentSchema,
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
// Top-level flex element (with size property)
|
|
258
|
+
// Note: FlexElementContentSchema already includes size in its definition, so we don't need to extend it
|
|
259
|
+
const FlexElementSchema = z.discriminatedUnion('type', [
|
|
260
|
+
FlexElementImageSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
261
|
+
FlexElementContentSchema, // Already includes size, no need to extend
|
|
262
|
+
FlexElementHtmlSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
263
|
+
FlexElementTitleSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
264
|
+
FlexElementTextSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
265
|
+
FlexElementBulletsSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
266
|
+
FlexElementOrderedSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
267
|
+
FlexElementButtonSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
268
|
+
FlexElementTimelineSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
269
|
+
FlexElementStepperSchema.extend({ size: FlexSizeSchema.optional() }),
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
const SlideFlexSchema = SlideBaseShape.extend({
|
|
273
|
+
type: z.literal('flex').describe("Flow-based layout. Elements flow in order with semantic sizing."),
|
|
274
|
+
direction: z.enum(['horizontal', 'vertical']).optional().describe("Main flow direction. Default: 'horizontal'."),
|
|
275
|
+
gap: SpacingTokenSchema.optional().describe("Gap between elements. Default: 'none'."),
|
|
276
|
+
padding: SpacingTokenSchema.optional().describe("Container padding. Default: 'none'."),
|
|
277
|
+
elements: fuzzyArray(FlexElementSchema).describe("Flex elements in flow order."),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Union of all Slides (Validation Layer)
|
|
281
|
+
export const SlideSchema = z.discriminatedUnion('type', [
|
|
282
|
+
SlideStatementSchema,
|
|
283
|
+
SlideFeaturesSchema,
|
|
284
|
+
SlideTimelineSchema,
|
|
285
|
+
SlideStepsSchema,
|
|
286
|
+
SlideHalfSchema,
|
|
287
|
+
SlideFlexSchema,
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
// --- THEME CONFIGURATION SCHEMA ---
|
|
291
|
+
// Comprehensive design token system for LLM-driven theming
|
|
292
|
+
|
|
293
|
+
const ThemeColorsSchema = z.object({
|
|
294
|
+
primary: z.string().optional().describe("Main brand/accent color. Used for highlights, buttons, links. Example: '#3b82f6'"),
|
|
295
|
+
secondary: z.string().optional().describe("Secondary brand color. Used for complementary elements. Example: '#8b5cf6'"),
|
|
296
|
+
accent: z.string().optional().describe("Tertiary/accent color. Used for special highlights. Example: '#06b6d4'"),
|
|
297
|
+
background: z.string().optional().describe("Main background color. Example: '#030303'"),
|
|
298
|
+
surface: z.string().optional().describe("Elevated surface color (cards, panels, modals). Example: '#0a0a0a'"),
|
|
299
|
+
text: z.string().optional().describe("Primary text color. Example: '#ffffff'"),
|
|
300
|
+
textSecondary: z.string().optional().describe("Secondary/subdued text color. Example: '#e5e7eb'"),
|
|
301
|
+
muted: z.string().optional().describe("Muted/disabled text color. Example: '#9ca3af'"),
|
|
302
|
+
success: z.string().optional().describe("Success state color (green tones). Example: '#10b981'"),
|
|
303
|
+
warning: z.string().optional().describe("Warning state color (yellow/amber). Example: '#f59e0b'"),
|
|
304
|
+
danger: z.string().optional().describe("Danger/error state color (red). Example: '#ef4444'"),
|
|
305
|
+
info: z.string().optional().describe("Informational state color (blue). Example: '#3b82f6'"),
|
|
306
|
+
border: z.string().optional().describe("Border color for containers, cards. Example: 'rgba(255,255,255,0.1)'"),
|
|
307
|
+
borderSubtle: z.string().optional().describe("Subtle border for dividers. Example: 'rgba(255,255,255,0.05)'"),
|
|
308
|
+
shadow: z.string().optional().describe("Shadow color. Example: '#000000'"),
|
|
309
|
+
overlay: z.string().optional().describe("Overlay color for modals. Example: 'rgba(0,0,0,0.5)'"),
|
|
310
|
+
highlight: z.string().optional().describe("Highlight/selection color."),
|
|
311
|
+
buttonPrimary: z.string().optional().describe("Primary button background."),
|
|
312
|
+
buttonPrimaryText: z.string().optional().describe("Primary button text color."),
|
|
313
|
+
buttonSecondary: z.string().optional().describe("Secondary button background."),
|
|
314
|
+
buttonSecondaryText: z.string().optional().describe("Secondary button text color."),
|
|
315
|
+
link: z.string().optional().describe("Link color."),
|
|
316
|
+
linkHover: z.string().optional().describe("Link hover color."),
|
|
317
|
+
gradientFrom: z.string().optional().describe("Gradient start color. Example: '#3b82f6'"),
|
|
318
|
+
gradientVia: z.string().optional().describe("Gradient middle color (optional)."),
|
|
319
|
+
gradientTo: z.string().optional().describe("Gradient end color. Example: '#8b5cf6'"),
|
|
320
|
+
}).describe("Color palette configuration with 25+ customizable colors.");
|
|
321
|
+
|
|
322
|
+
const ThemeTypographySchema = z.object({
|
|
323
|
+
fontFamily: z.object({
|
|
324
|
+
heading: z.string().optional().describe("Font for headings. Example: 'Inter, sans-serif'"),
|
|
325
|
+
body: z.string().optional().describe("Font for body text. Example: 'Inter, sans-serif'"),
|
|
326
|
+
mono: z.string().optional().describe("Font for code. Example: 'monospace'"),
|
|
327
|
+
}).optional().describe("Font family definitions."),
|
|
328
|
+
fontSize: z.object({
|
|
329
|
+
xs: z.string().optional().describe("Extra small: 0.75rem"),
|
|
330
|
+
sm: z.string().optional().describe("Small: 0.875rem"),
|
|
331
|
+
base: z.string().optional().describe("Base: 1rem"),
|
|
332
|
+
lg: z.string().optional().describe("Large: 1.125rem"),
|
|
333
|
+
xl: z.string().optional().describe("Extra large: 1.25rem"),
|
|
334
|
+
'2xl': z.string().optional().describe("2XL: 1.5rem"),
|
|
335
|
+
'3xl': z.string().optional().describe("3XL: 1.875rem"),
|
|
336
|
+
'4xl': z.string().optional().describe("4XL: 2.25rem"),
|
|
337
|
+
'5xl': z.string().optional().describe("5XL: 3rem"),
|
|
338
|
+
'6xl': z.string().optional().describe("6XL: 3.75rem"),
|
|
339
|
+
'7xl': z.string().optional().describe("7XL: 4.5rem"),
|
|
340
|
+
}).optional().describe("Font size scale."),
|
|
341
|
+
fontWeight: z.object({
|
|
342
|
+
light: z.number().optional().describe("Light: 300"),
|
|
343
|
+
normal: z.number().optional().describe("Normal: 400"),
|
|
344
|
+
medium: z.number().optional().describe("Medium: 500"),
|
|
345
|
+
semibold: z.number().optional().describe("Semibold: 600"),
|
|
346
|
+
bold: z.number().optional().describe("Bold: 700"),
|
|
347
|
+
extrabold: z.number().optional().describe("Extra bold: 800"),
|
|
348
|
+
}).optional().describe("Font weight scale."),
|
|
349
|
+
lineHeight: z.object({
|
|
350
|
+
tight: z.string().optional().describe("Tight: 1.1"),
|
|
351
|
+
snug: z.string().optional().describe("Snug: 1.25"),
|
|
352
|
+
normal: z.string().optional().describe("Normal: 1.5"),
|
|
353
|
+
relaxed: z.string().optional().describe("Relaxed: 1.625"),
|
|
354
|
+
loose: z.string().optional().describe("Loose: 2"),
|
|
355
|
+
}).optional().describe("Line height scale."),
|
|
356
|
+
letterSpacing: z.object({
|
|
357
|
+
tighter: z.string().optional().describe("-0.05em"),
|
|
358
|
+
tight: z.string().optional().describe("-0.025em"),
|
|
359
|
+
normal: z.string().optional().describe("0"),
|
|
360
|
+
wide: z.string().optional().describe("0.025em"),
|
|
361
|
+
wider: z.string().optional().describe("0.05em"),
|
|
362
|
+
widest: z.string().optional().describe("0.1em"),
|
|
363
|
+
}).optional().describe("Letter spacing scale."),
|
|
364
|
+
}).describe("Typography configuration: fonts, sizes, weights, line-heights.");
|
|
365
|
+
|
|
366
|
+
const ThemeSpacingSchema = z.object({
|
|
367
|
+
none: z.string().optional().describe("0"),
|
|
368
|
+
xs: z.string().optional().describe("0.25rem / 4px"),
|
|
369
|
+
sm: z.string().optional().describe("0.5rem / 8px"),
|
|
370
|
+
md: z.string().optional().describe("1rem / 16px"),
|
|
371
|
+
lg: z.string().optional().describe("1.5rem / 24px"),
|
|
372
|
+
xl: z.string().optional().describe("2rem / 32px"),
|
|
373
|
+
'2xl': z.string().optional().describe("3rem / 48px"),
|
|
374
|
+
'3xl': z.string().optional().describe("4rem / 64px"),
|
|
375
|
+
'4xl': z.string().optional().describe("6rem / 96px"),
|
|
376
|
+
}).describe("Spacing scale for gaps, padding, margins.");
|
|
377
|
+
|
|
378
|
+
const ThemeBorderRadiusSchema = z.object({
|
|
379
|
+
none: z.string().optional().describe("No rounding: 0"),
|
|
380
|
+
sm: z.string().optional().describe("Small: 0.25rem"),
|
|
381
|
+
md: z.string().optional().describe("Medium: 0.5rem"),
|
|
382
|
+
lg: z.string().optional().describe("Large: 0.75rem"),
|
|
383
|
+
xl: z.string().optional().describe("Extra large: 1rem"),
|
|
384
|
+
'2xl': z.string().optional().describe("2XL: 1.5rem"),
|
|
385
|
+
'3xl': z.string().optional().describe("3XL: 2rem"),
|
|
386
|
+
full: z.string().optional().describe("Full circle: 9999px"),
|
|
387
|
+
}).describe("Border radius scale for rounded corners.");
|
|
388
|
+
|
|
389
|
+
const ThemeEffectsSchema = z.object({
|
|
390
|
+
useGradients: z.boolean().optional().describe("Enable/disable gradient effects globally."),
|
|
391
|
+
gradientDirection: z.enum(['to-t', 'to-b', 'to-l', 'to-r', 'to-tl', 'to-tr', 'to-bl', 'to-br']).optional().describe("Gradient direction."),
|
|
392
|
+
gradientFrom: z.string().optional().describe("Override gradient start color."),
|
|
393
|
+
gradientVia: z.string().optional().describe("Override gradient middle color."),
|
|
394
|
+
gradientTo: z.string().optional().describe("Override gradient end color."),
|
|
395
|
+
useShadows: z.boolean().optional().describe("Enable/disable shadow effects."),
|
|
396
|
+
shadowIntensity: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).optional().describe("Shadow intensity level."),
|
|
397
|
+
shadowColor: z.string().optional().describe("Shadow color."),
|
|
398
|
+
useGlass: z.boolean().optional().describe("Enable/disable glassmorphism effect."),
|
|
399
|
+
glassOpacity: z.number().optional().describe("Glass panel opacity (0-1)."),
|
|
400
|
+
glassBlur: z.string().optional().describe("Glass blur amount. Example: '20px'"),
|
|
401
|
+
glassBorderOpacity: z.number().optional().describe("Glass border opacity (0-1)."),
|
|
402
|
+
useOrb: z.boolean().optional().describe("Enable/disable ambient orb/glow effect."),
|
|
403
|
+
orbOpacity: z.number().optional().describe("Orb opacity (0-1)."),
|
|
404
|
+
orbBlur: z.string().optional().describe("Orb blur amount. Example: '120px'"),
|
|
405
|
+
orbSize: z.string().optional().describe("Orb size. Example: '60vw'"),
|
|
406
|
+
animationsEnabled: z.boolean().optional().describe("Enable/disable animations globally."),
|
|
407
|
+
transitionDuration: z.string().optional().describe("Default transition duration. Example: '0.3s'"),
|
|
408
|
+
transitionEasing: z.string().optional().describe("Default transition easing. Example: 'ease-out'"),
|
|
409
|
+
hoverScale: z.number().optional().describe("Hover scale factor. Example: 1.05"),
|
|
410
|
+
}).describe("Visual effects: gradients, shadows, glass, orb, animations.");
|
|
411
|
+
|
|
412
|
+
const ThemeComponentsSchema = z.object({
|
|
413
|
+
buttonRadius: z.string().optional().describe("Button border radius."),
|
|
414
|
+
buttonPadding: z.string().optional().describe("Button padding. Example: '0.75rem 1.5rem'"),
|
|
415
|
+
buttonFontWeight: z.number().optional().describe("Button font weight."),
|
|
416
|
+
buttonTextTransform: z.enum(['none', 'uppercase', 'capitalize']).optional().describe("Button text transform."),
|
|
417
|
+
cardRadius: z.string().optional().describe("Card border radius."),
|
|
418
|
+
cardPadding: z.string().optional().describe("Card padding."),
|
|
419
|
+
cardBorderWidth: z.string().optional().describe("Card border width. Example: '1px'"),
|
|
420
|
+
cardBackground: z.string().optional().describe("Card background override."),
|
|
421
|
+
timelineNodeSize: z.string().optional().describe("Timeline node size. Example: '1rem'"),
|
|
422
|
+
timelineLineWidth: z.string().optional().describe("Timeline line width. Example: '2px'"),
|
|
423
|
+
timelineNodeColor: z.string().optional().describe("Timeline node color."),
|
|
424
|
+
timelineLineColor: z.string().optional().describe("Timeline line color."),
|
|
425
|
+
stepBadgeSize: z.string().optional().describe("Step badge size."),
|
|
426
|
+
stepFontSize: z.string().optional().describe("Step font size."),
|
|
427
|
+
progressHeight: z.string().optional().describe("Progress bar height."),
|
|
428
|
+
progressRadius: z.string().optional().describe("Progress bar border radius."),
|
|
429
|
+
progressBackground: z.string().optional().describe("Progress bar background."),
|
|
430
|
+
progressFill: z.string().optional().describe("Progress bar fill color."),
|
|
431
|
+
tagPadding: z.string().optional().describe("Tag padding."),
|
|
432
|
+
tagRadius: z.string().optional().describe("Tag border radius."),
|
|
433
|
+
tagFontSize: z.string().optional().describe("Tag font size."),
|
|
434
|
+
inputRadius: z.string().optional().describe("Input border radius."),
|
|
435
|
+
inputPadding: z.string().optional().describe("Input padding."),
|
|
436
|
+
inputBorder: z.string().optional().describe("Input border color."),
|
|
437
|
+
inputFocusBorder: z.string().optional().describe("Input focus border color."),
|
|
438
|
+
}).describe("Component-specific theming: buttons, cards, timeline, steps, etc.");
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Complete Theme Configuration Schema.
|
|
442
|
+
* Comprehensive design token system for full slide customization by LLMs.
|
|
443
|
+
*/
|
|
444
|
+
export const ThemeConfigSchema = z.object({
|
|
445
|
+
colors: ThemeColorsSchema.optional().describe("Color palette with 25+ customizable colors."),
|
|
446
|
+
typography: ThemeTypographySchema.optional().describe("Typography: fonts, sizes, weights, line-heights."),
|
|
447
|
+
spacing: ThemeSpacingSchema.optional().describe("Spacing scale for consistent gaps and padding."),
|
|
448
|
+
borderRadius: ThemeBorderRadiusSchema.optional().describe("Border radius tokens for rounded corners."),
|
|
449
|
+
effects: ThemeEffectsSchema.optional().describe("Visual effects: gradients, shadows, glass, orb."),
|
|
450
|
+
components: ThemeComponentsSchema.optional().describe("Component-specific styling tokens."),
|
|
451
|
+
}).describe("Complete theme configuration for full visual customization.");
|
|
452
|
+
|
|
453
|
+
// Available theme preset names
|
|
454
|
+
const ThemePresetSchema = z.enum([
|
|
455
|
+
'default', 'ocean', 'midnight', 'forest', 'cyber', 'latte', 'sunset', 'monochrome'
|
|
456
|
+
]).describe("Built-in theme preset name.");
|
|
457
|
+
|
|
458
|
+
// Deck Schema
|
|
459
|
+
export const DeckMetaSchema = z.object({
|
|
460
|
+
title: fuzzyString.describe("Title of the entire presentation."),
|
|
461
|
+
author: fuzzyString.optional(),
|
|
462
|
+
date: fuzzyString.optional(),
|
|
463
|
+
theme: ThemePresetSchema.optional().describe("Theme preset to use. Options: default, ocean, midnight, forest, cyber, latte, sunset, monochrome."),
|
|
464
|
+
themeConfig: ThemeConfigSchema.optional().describe("Custom theme configuration to override preset values."),
|
|
465
|
+
}).passthrough();
|
|
466
|
+
|
|
467
|
+
export const DeckSchema = z.object({
|
|
468
|
+
meta: DeckMetaSchema.describe("Global deck settings including title and theme."),
|
|
469
|
+
// Apply normalization (Alias -> Full key) at the entrance of each slide
|
|
470
|
+
slides: z.array(z.preprocess(normalizeAliases, SlideSchema)).describe("Ordered array of slide definitions."),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
// --- 4. Utilities (Improvement: "JSON Schema Export") ---
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Exports the JSON Schema for the Deck structure.
|
|
478
|
+
* Useful for passing to OpenAI `response_format`.
|
|
479
|
+
*/
|
|
480
|
+
export function getLuminaJsonSchema() {
|
|
481
|
+
try {
|
|
482
|
+
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
483
|
+
return zodToJsonSchema(DeckSchema, "LuminaDeck");
|
|
484
|
+
} catch (e) {
|
|
485
|
+
console.warn("zod-to-json-schema not installed. Install it to export raw JSON Schema.");
|
|
486
|
+
return {};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Exports the JSON Schema for Theme Configuration only.
|
|
492
|
+
* Useful for LLMs to understand all available theming options.
|
|
493
|
+
*/
|
|
494
|
+
export function getThemeJsonSchema() {
|
|
495
|
+
try {
|
|
496
|
+
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
497
|
+
return zodToJsonSchema(ThemeConfigSchema, "LuminaThemeConfig");
|
|
498
|
+
} catch (e) {
|
|
499
|
+
console.warn("zod-to-json-schema not installed. Install it to export raw JSON Schema.");
|
|
500
|
+
return {};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|