@blockslides/ai-context 0.1.6 → 0.2.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.
@@ -0,0 +1,672 @@
1
+ // TODO: Add additional block helpers (tables, marks) to cover all extensions.
2
+ // TODO: Add more slide presets (hero, imageCover, agenda) once shapes are finalized.
3
+ // TODO: Add server-side validation helper that checks slides against schemasV1.
4
+ // TODO: Add tests/examples that demonstrate agent/tool usage.
5
+
6
+ import type { JSONContent } from "@blockslides/core";
7
+ import type {
8
+ SizeKey,
9
+ SlideAttrs,
10
+ RowAttrs,
11
+ ColumnAttrs,
12
+ ImageBlockAttrs,
13
+ } from "../../types/v1";
14
+
15
+ type Block = JSONContent;
16
+ type SlideNode = JSONContent; // slide -> row -> column -> blocks
17
+ type TextMark = { type: string; attrs?: Record<string, any> };
18
+
19
+ const defaults = {
20
+ slide: (attrs?: Partial<SlideAttrs>): SlideAttrs => ({
21
+ id: attrs?.id ?? "slide-1",
22
+ size: attrs?.size ?? ("16x9" as SizeKey),
23
+ className: attrs?.className ?? "",
24
+ }),
25
+ row: (attrs?: Partial<RowAttrs>): RowAttrs => ({
26
+ layout: attrs?.layout ?? "1",
27
+ className: attrs?.className ?? "",
28
+ }),
29
+ column: (attrs?: Partial<ColumnAttrs>): ColumnAttrs => ({
30
+ className: attrs?.className ?? "",
31
+ contentMode: attrs?.contentMode ?? "default",
32
+ verticalAlign: attrs?.verticalAlign,
33
+ horizontalAlign: attrs?.horizontalAlign,
34
+ padding: attrs?.padding,
35
+ }),
36
+ };
37
+
38
+ const textNode = (text: string) =>
39
+ ({
40
+ type: "text",
41
+ text,
42
+ }) satisfies Block;
43
+
44
+ export const blocks = {
45
+ text: (text: string, marks?: TextMark[]): Block =>
46
+ marks && marks.length > 0
47
+ ? ({ type: "text", text, marks } as Block)
48
+ : textNode(text),
49
+
50
+ paragraph: (text?: string): Block => ({
51
+ type: "paragraph",
52
+ content: text ? [textNode(text)] : [],
53
+ }),
54
+
55
+ heading: (text: string, level: 1 | 2 | 3 | 4 | 5 | 6 = 2): Block => ({
56
+ type: "heading",
57
+ attrs: { level },
58
+ content: [textNode(text)],
59
+ }),
60
+
61
+ bulletList: (items: (string | Block)[]): Block => ({
62
+ type: "bulletList",
63
+ content: items.map((item) =>
64
+ typeof item === "string"
65
+ ? {
66
+ type: "listItem",
67
+ content: [{ type: "paragraph", content: [textNode(item)] }],
68
+ }
69
+ : { type: "listItem", content: [item] }
70
+ ),
71
+ }),
72
+
73
+ horizontalRule: (): Block => ({ type: "horizontalRule" }),
74
+
75
+ hardBreak: (): Block => ({ type: "hardBreak" }),
76
+
77
+ codeBlock: (code: string, language?: string): Block => ({
78
+ type: "codeBlock",
79
+ attrs: language ? { language } : undefined,
80
+ content: code ? [textNode(code)] : [],
81
+ }),
82
+
83
+ imageBlock: (attrs: ImageBlockAttrs): Block => ({
84
+ type: "imageBlock",
85
+ attrs,
86
+ }),
87
+
88
+ // Additional nodes to mirror extensions/schemas
89
+ blockquote: (content: Block[] = []): Block => ({
90
+ type: "blockquote",
91
+ content,
92
+ }),
93
+
94
+ listItem: (content: Block[] = []): Block => ({
95
+ type: "listItem",
96
+ content,
97
+ }),
98
+
99
+ image: (attrs: {
100
+ src: string;
101
+ alt?: string | null;
102
+ title?: string | null;
103
+ width?: number | null;
104
+ height?: number | null;
105
+ }): Block => ({
106
+ type: "image",
107
+ attrs,
108
+ }),
109
+
110
+ youtube: (attrs: {
111
+ src?: string | null;
112
+ start?: number;
113
+ width?: number;
114
+ height?: number;
115
+ }): Block => ({
116
+ type: "youtube",
117
+ attrs,
118
+ }),
119
+ };
120
+
121
+ type SingleColOpts = {
122
+ slideAttrs?: Partial<SlideAttrs>;
123
+ rowAttrs?: Partial<RowAttrs>;
124
+ columnAttrs?: Partial<ColumnAttrs>;
125
+ content?: Block[];
126
+ };
127
+
128
+ type TwoColOpts = {
129
+ slideAttrs?: Partial<SlideAttrs>;
130
+ rowAttrs?: Partial<RowAttrs>;
131
+ leftColumnAttrs?: Partial<ColumnAttrs>;
132
+ rightColumnAttrs?: Partial<ColumnAttrs>;
133
+ left?: Block[];
134
+ right?: Block[];
135
+ };
136
+
137
+ export const slide = Object.assign(
138
+ (opts: {
139
+ slideAttrs?: Partial<SlideAttrs>;
140
+ rowAttrs?: Partial<RowAttrs>;
141
+ columnAttrs?: Partial<ColumnAttrs>;
142
+ } = {}): SlideNode => ({
143
+ type: "slide",
144
+ attrs: defaults.slide(opts.slideAttrs),
145
+ content: [
146
+ {
147
+ type: "row",
148
+ attrs: defaults.row(opts.rowAttrs),
149
+ content: [
150
+ {
151
+ type: "column",
152
+ attrs: defaults.column(opts.columnAttrs),
153
+ content: [],
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ }),
159
+ {
160
+ singleCol: (opts: SingleColOpts = {}): SlideNode => ({
161
+ type: "slide",
162
+ attrs: defaults.slide(opts.slideAttrs),
163
+ content: [
164
+ {
165
+ type: "row",
166
+ attrs: defaults.row(opts.rowAttrs),
167
+ content: [
168
+ {
169
+ type: "column",
170
+ attrs: defaults.column(opts.columnAttrs),
171
+ content: opts.content ?? [],
172
+ },
173
+ ],
174
+ },
175
+ ],
176
+ }),
177
+
178
+ twoCol: (opts: TwoColOpts = {}): SlideNode => ({
179
+ type: "slide",
180
+ attrs: defaults.slide(opts.slideAttrs),
181
+ content: [
182
+ {
183
+ type: "row",
184
+ attrs: defaults.row({
185
+ layout: opts.rowAttrs?.layout ?? "1-1",
186
+ ...opts.rowAttrs,
187
+ }),
188
+ content: [
189
+ {
190
+ type: "column",
191
+ attrs: defaults.column(opts.leftColumnAttrs),
192
+ content: opts.left ?? [],
193
+ },
194
+ {
195
+ type: "column",
196
+ attrs: defaults.column(opts.rightColumnAttrs),
197
+ content: opts.right ?? [],
198
+ },
199
+ ],
200
+ },
201
+ ],
202
+ }),
203
+ }
204
+ );
205
+
206
+ // Slide layout presets beyond the base helpers
207
+ type HeroOpts = {
208
+ slideAttrs?: Partial<SlideAttrs>;
209
+ rowAttrs?: Partial<RowAttrs>;
210
+ columnAttrs?: Partial<ColumnAttrs>;
211
+ content?: Block[];
212
+ };
213
+
214
+ type ImageCoverOpts = {
215
+ slideAttrs?: Partial<SlideAttrs>;
216
+ image?: ImageBlockAttrs;
217
+ overlay?: Block[];
218
+ columnAttrs?: Partial<ColumnAttrs>;
219
+ rowAttrs?: Partial<RowAttrs>;
220
+ };
221
+
222
+ type QuoteOpts = {
223
+ slideAttrs?: Partial<SlideAttrs>;
224
+ rowAttrs?: Partial<RowAttrs>;
225
+ columnAttrs?: Partial<ColumnAttrs>;
226
+ quote?: Block[];
227
+ };
228
+
229
+ type AgendaOpts = {
230
+ slideAttrs?: Partial<SlideAttrs>;
231
+ rowAttrs?: Partial<RowAttrs>;
232
+ columnAttrs?: Partial<ColumnAttrs>;
233
+ items?: (string | Block)[];
234
+ };
235
+
236
+ type MultiColOpts = {
237
+ slideAttrs?: Partial<SlideAttrs>;
238
+ rowAttrs?: Partial<RowAttrs>;
239
+ columns: {
240
+ content?: Block[];
241
+ attrs?: Partial<ColumnAttrs>;
242
+ }[];
243
+ };
244
+
245
+ type MediaTextOpts = {
246
+ slideAttrs?: Partial<SlideAttrs>;
247
+ rowAttrs?: Partial<RowAttrs>;
248
+ media?: Block[];
249
+ text?: Block[];
250
+ mediaColumnAttrs?: Partial<ColumnAttrs>;
251
+ textColumnAttrs?: Partial<ColumnAttrs>;
252
+ };
253
+
254
+ type Stack2Opts = {
255
+ slideAttrs?: Partial<SlideAttrs>;
256
+ topRow?: {
257
+ rowAttrs?: Partial<RowAttrs>;
258
+ columns: { content?: Block[]; attrs?: Partial<ColumnAttrs> }[];
259
+ };
260
+ bottomRow?: {
261
+ rowAttrs?: Partial<RowAttrs>;
262
+ columns: { content?: Block[]; attrs?: Partial<ColumnAttrs> }[];
263
+ };
264
+ };
265
+
266
+ type TemplatePreset =
267
+ | "slide.empty"
268
+ | "slide.singleCol"
269
+ | "slide.twoCol"
270
+ | "slide.hero"
271
+ | "slide.imageCover"
272
+ | "slide.quote"
273
+ | "slide.agenda"
274
+ | "slide.grid3"
275
+ | "slide.grid4"
276
+ | "slide.oneTwo"
277
+ | "slide.twoOne"
278
+ | "slide.oneTwoOne"
279
+ | "slide.textMedia"
280
+ | "slide.mediaText"
281
+ | "slide.stack2";
282
+
283
+ type CreateTemplateInput = {
284
+ preset: TemplatePreset;
285
+ slideAttrs?: Partial<SlideAttrs>;
286
+ rowAttrs?: Partial<RowAttrs>;
287
+ columnAttrs?: Partial<ColumnAttrs>;
288
+ leftColumnAttrs?: Partial<ColumnAttrs>;
289
+ rightColumnAttrs?: Partial<ColumnAttrs>;
290
+ content?: Block[];
291
+ left?: Block[];
292
+ right?: Block[];
293
+ heroOpts?: HeroOpts;
294
+ imageCoverOpts?: ImageCoverOpts;
295
+ quoteOpts?: QuoteOpts;
296
+ agendaOpts?: AgendaOpts;
297
+ multiColOpts?: MultiColOpts;
298
+ mediaTextOpts?: MediaTextOpts;
299
+ stack2Opts?: Stack2Opts;
300
+ };
301
+
302
+ const column = (attrs: Partial<ColumnAttrs> | undefined, content: Block[] = []): Block => ({
303
+ type: "column",
304
+ attrs: defaults.column(attrs),
305
+ content,
306
+ });
307
+
308
+ const row = (layout: RowAttrs["layout"], content: Block[], attrs?: Partial<RowAttrs>): Block => ({
309
+ type: "row",
310
+ attrs: defaults.row({ layout, ...attrs }),
311
+ content,
312
+ });
313
+
314
+ export const createTemplate = (input: CreateTemplateInput): SlideNode => {
315
+ switch (input.preset) {
316
+ case "slide.empty":
317
+ return slide({
318
+ slideAttrs: input.slideAttrs,
319
+ rowAttrs: input.rowAttrs,
320
+ columnAttrs: input.columnAttrs,
321
+ });
322
+ case "slide.singleCol":
323
+ return slide.singleCol({
324
+ slideAttrs: input.slideAttrs,
325
+ rowAttrs: input.rowAttrs,
326
+ columnAttrs: input.columnAttrs,
327
+ content: input.content ?? [],
328
+ });
329
+ case "slide.twoCol":
330
+ return slide.twoCol({
331
+ slideAttrs: input.slideAttrs,
332
+ rowAttrs: input.rowAttrs,
333
+ leftColumnAttrs: input.leftColumnAttrs,
334
+ rightColumnAttrs: input.rightColumnAttrs,
335
+ left: input.left ?? [],
336
+ right: input.right ?? [],
337
+ });
338
+ case "slide.hero": {
339
+ const opts = input.heroOpts ?? {};
340
+ return {
341
+ type: "slide",
342
+ attrs: defaults.slide({ className: "bg-slate-950 text-white", ...opts.slideAttrs }),
343
+ content: [
344
+ row(
345
+ opts.rowAttrs?.layout ?? "1",
346
+ [
347
+ column(
348
+ opts.columnAttrs ?? { className: "max-w-4xl gap-6 p-12" },
349
+ opts.content ?? [
350
+ blocks.heading("Your headline", 1),
351
+ blocks.paragraph("Subhead goes here."),
352
+ blocks.paragraph("Add supporting details here."),
353
+ ]
354
+ ),
355
+ ],
356
+ { className: "min-h-[720px] items-center justify-center p-12", ...opts.rowAttrs }
357
+ ),
358
+ ],
359
+ };
360
+ }
361
+ case "slide.imageCover": {
362
+ const opts = input.imageCoverOpts ?? {};
363
+ const image = opts.image ?? {
364
+ src: "https://placehold.co/1600x900/png",
365
+ layout: "cover",
366
+ fullBleed: true,
367
+ align: "center",
368
+ };
369
+ const overlay = opts.overlay ?? [blocks.heading("Overlay title", 1)];
370
+ return {
371
+ type: "slide",
372
+ attrs: defaults.slide({ className: "bg-black text-white", ...opts.slideAttrs }),
373
+ content: [
374
+ row(
375
+ opts.rowAttrs?.layout ?? "1",
376
+ [
377
+ {
378
+ type: "column",
379
+ attrs: defaults.column({ className: "relative w-full h-full", ...opts.columnAttrs }),
380
+ content: [
381
+ blocks.imageBlock(image),
382
+ ...overlay.map((node) => ({
383
+ ...node,
384
+ attrs: {
385
+ ...(node as any).attrs,
386
+ className: `${((node as any).attrs?.className ?? "")} absolute bottom-12 left-12 drop-shadow-lg`.trim(),
387
+ },
388
+ })),
389
+ ],
390
+ },
391
+ ],
392
+ { className: "min-h-[720px]", ...opts.rowAttrs }
393
+ ),
394
+ ],
395
+ };
396
+ }
397
+ case "slide.quote": {
398
+ const opts = input.quoteOpts ?? {};
399
+ return {
400
+ type: "slide",
401
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
402
+ content: [
403
+ row(
404
+ opts.rowAttrs?.layout ?? "1",
405
+ [
406
+ column(
407
+ opts.columnAttrs ?? { className: "max-w-3xl mx-auto gap-4 p-12" },
408
+ [
409
+ blocks.blockquote(
410
+ opts.quote ?? [
411
+ blocks.paragraph("“Add your quote here.”"),
412
+ blocks.paragraph("— Author"),
413
+ ]
414
+ ),
415
+ ]
416
+ ),
417
+ ],
418
+ { className: "min-h-[640px] items-center justify-center", ...opts.rowAttrs }
419
+ ),
420
+ ],
421
+ };
422
+ }
423
+ case "slide.agenda": {
424
+ const opts = input.agendaOpts ?? {};
425
+ return {
426
+ type: "slide",
427
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
428
+ content: [
429
+ row(
430
+ opts.rowAttrs?.layout ?? "1",
431
+ [
432
+ column(
433
+ opts.columnAttrs ?? { className: "p-12 gap-6" },
434
+ [
435
+ blocks.heading("Agenda", 1),
436
+ blocks.bulletList(opts.items ?? ["Topic 1", "Topic 2", "Topic 3"]),
437
+ ]
438
+ ),
439
+ ],
440
+ { className: "min-h-[640px]", ...opts.rowAttrs }
441
+ ),
442
+ ],
443
+ };
444
+ }
445
+ case "slide.grid3": {
446
+ const opts = input.multiColOpts ?? { columns: [] };
447
+ const cols =
448
+ opts.columns.length > 0
449
+ ? opts.columns
450
+ : [
451
+ { content: [blocks.heading("One", 3), blocks.paragraph("Details.")] },
452
+ { content: [blocks.heading("Two", 3), blocks.paragraph("Details.")] },
453
+ { content: [blocks.heading("Three", 3), blocks.paragraph("Details.")] },
454
+ ];
455
+ return {
456
+ type: "slide",
457
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
458
+ content: [
459
+ row(
460
+ opts.rowAttrs?.layout ?? "1-1-1",
461
+ cols.map((c) => column(c.attrs, c.content ?? [])),
462
+ { className: "p-8 gap-4", ...opts.rowAttrs }
463
+ ),
464
+ ],
465
+ };
466
+ }
467
+ case "slide.grid4": {
468
+ const opts = input.multiColOpts ?? { columns: [] };
469
+ const cols =
470
+ opts.columns.length > 0
471
+ ? opts.columns
472
+ : [
473
+ { content: [blocks.heading("One", 4)] },
474
+ { content: [blocks.heading("Two", 4)] },
475
+ { content: [blocks.heading("Three", 4)] },
476
+ { content: [blocks.heading("Four", 4)] },
477
+ ];
478
+ return {
479
+ type: "slide",
480
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
481
+ content: [
482
+ row(
483
+ opts.rowAttrs?.layout ?? "1-1-1-1",
484
+ cols.map((c) => column(c.attrs, c.content ?? [])),
485
+ { className: "p-8 gap-4", ...opts.rowAttrs }
486
+ ),
487
+ ],
488
+ };
489
+ }
490
+ case "slide.oneTwo": {
491
+ const opts = input.mediaTextOpts ?? {};
492
+ return {
493
+ type: "slide",
494
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
495
+ content: [
496
+ row(
497
+ opts.rowAttrs?.layout ?? "1-2",
498
+ [
499
+ column(opts.textColumnAttrs ?? { className: "p-8 gap-4" }, opts.text ?? []),
500
+ column(opts.mediaColumnAttrs ?? { className: "p-8 gap-4" }, opts.media ?? []),
501
+ ],
502
+ { className: "min-h-[640px]", ...opts.rowAttrs }
503
+ ),
504
+ ],
505
+ };
506
+ }
507
+ case "slide.twoOne": {
508
+ const opts = input.mediaTextOpts ?? {};
509
+ return {
510
+ type: "slide",
511
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
512
+ content: [
513
+ row(
514
+ opts.rowAttrs?.layout ?? "2-1",
515
+ [
516
+ column(opts.mediaColumnAttrs ?? { className: "p-8 gap-4" }, opts.media ?? []),
517
+ column(opts.textColumnAttrs ?? { className: "p-8 gap-4" }, opts.text ?? []),
518
+ ],
519
+ { className: "min-h-[640px]", ...opts.rowAttrs }
520
+ ),
521
+ ],
522
+ };
523
+ }
524
+ case "slide.oneTwoOne": {
525
+ const opts = input.multiColOpts ?? { columns: [] };
526
+ const cols =
527
+ opts.columns.length > 0
528
+ ? opts.columns
529
+ : [
530
+ { content: [blocks.paragraph("Left")] },
531
+ { content: [blocks.paragraph("Center")] },
532
+ { content: [blocks.paragraph("Right")] },
533
+ ];
534
+ return {
535
+ type: "slide",
536
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
537
+ content: [
538
+ row(
539
+ opts.rowAttrs?.layout ?? "1-2-1",
540
+ cols.map((c) => column(c.attrs, c.content ?? [])),
541
+ { className: "p-8 gap-4", ...opts.rowAttrs }
542
+ ),
543
+ ],
544
+ };
545
+ }
546
+ case "slide.textMedia": {
547
+ const opts = input.mediaTextOpts ?? {};
548
+ return {
549
+ type: "slide",
550
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
551
+ content: [
552
+ row(
553
+ opts.rowAttrs?.layout ?? "1-1",
554
+ [
555
+ column(opts.textColumnAttrs ?? { className: "p-10 gap-4" }, opts.text ?? []),
556
+ column(opts.mediaColumnAttrs ?? { className: "p-10 gap-4 bg-slate-50" }, opts.media ?? []),
557
+ ],
558
+ { className: "min-h-[640px]", ...opts.rowAttrs }
559
+ ),
560
+ ],
561
+ };
562
+ }
563
+ case "slide.mediaText": {
564
+ const opts = input.mediaTextOpts ?? {};
565
+ return {
566
+ type: "slide",
567
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
568
+ content: [
569
+ row(
570
+ opts.rowAttrs?.layout ?? "1-1",
571
+ [
572
+ column(opts.mediaColumnAttrs ?? { className: "p-10 gap-4 bg-slate-50" }, opts.media ?? []),
573
+ column(opts.textColumnAttrs ?? { className: "p-10 gap-4" }, opts.text ?? []),
574
+ ],
575
+ { className: "min-h-[640px]", ...opts.rowAttrs }
576
+ ),
577
+ ],
578
+ };
579
+ }
580
+ case "slide.stack2": {
581
+ const opts = input.stack2Opts ?? {};
582
+ const top = opts.topRow ?? {
583
+ rowAttrs: { layout: "1" },
584
+ columns: [{ content: [blocks.heading("Title", 1), blocks.paragraph("Subhead")] }],
585
+ };
586
+ const bottom = opts.bottomRow ?? {
587
+ rowAttrs: { layout: "1-1" },
588
+ columns: [
589
+ { content: [blocks.paragraph("Left detail")] },
590
+ { content: [blocks.paragraph("Right detail")] },
591
+ ],
592
+ };
593
+ return {
594
+ type: "slide",
595
+ attrs: defaults.slide({ className: "bg-white text-slate-900", ...opts.slideAttrs }),
596
+ content: [
597
+ row(
598
+ top.rowAttrs?.layout ?? "1",
599
+ top.columns.map((c) => column(c.attrs, c.content ?? [])),
600
+ { className: "p-8 gap-4", ...top.rowAttrs }
601
+ ),
602
+ row(
603
+ bottom.rowAttrs?.layout ?? "1-1",
604
+ bottom.columns.map((c) => column(c.attrs, c.content ?? [])),
605
+ { className: "p-8 gap-4", ...bottom.rowAttrs }
606
+ ),
607
+ ],
608
+ };
609
+ }
610
+ default:
611
+ return slide(); // fallback
612
+ }
613
+ };
614
+
615
+ export const listTemplates = (): TemplatePreset[] => [
616
+ "slide.empty",
617
+ "slide.singleCol",
618
+ "slide.twoCol",
619
+ "slide.hero",
620
+ "slide.imageCover",
621
+ "slide.quote",
622
+ "slide.agenda",
623
+ "slide.grid3",
624
+ "slide.grid4",
625
+ "slide.oneTwo",
626
+ "slide.twoOne",
627
+ "slide.oneTwoOne",
628
+ "slide.textMedia",
629
+ "slide.mediaText",
630
+ "slide.stack2",
631
+ ];
632
+
633
+ export const templatesV1Context = `
634
+ BlockSlides templates API (v1)
635
+
636
+ Presets:
637
+ - slide.empty(): slide with one empty column.
638
+ - slide.singleCol({ content?, slideAttrs?, rowAttrs?, columnAttrs? })
639
+ - slide.twoCol({ left?, right?, slideAttrs?, rowAttrs?, leftColumnAttrs?, rightColumnAttrs? })
640
+ - slide.hero({ content?, slideAttrs?, rowAttrs?, columnAttrs? })
641
+ - slide.imageCover({ image?, overlay?, slideAttrs?, rowAttrs?, columnAttrs? })
642
+ - slide.quote({ quote?, slideAttrs?, rowAttrs?, columnAttrs? })
643
+ - slide.agenda({ items?, slideAttrs?, rowAttrs?, columnAttrs? })
644
+ - slide.grid3/grid4({ columns?, slideAttrs?, rowAttrs? })
645
+ - slide.oneTwo/twoOne/oneTwoOne({ columns?, slideAttrs?, rowAttrs? })
646
+ - slide.textMedia/mediaText({ text?, media?, slideAttrs?, rowAttrs? })
647
+ - slide.stack2({ topRow?, bottomRow?, slideAttrs? })
648
+
649
+ Blocks:
650
+ - blocks.text(text, marks?)
651
+ - blocks.heading(text, level?)
652
+ - blocks.paragraph(text?)
653
+ - blocks.bulletList([string | Block][])
654
+ - blocks.codeBlock(code, language?)
655
+ - blocks.horizontalRule()
656
+ - blocks.hardBreak()
657
+ - blocks.imageBlock({ src, alt?, layout?, fullBleed?, align?, ...ImageBlockAttrs })
658
+ - blocks.blockquote(content?)
659
+ - blocks.listItem(content?)
660
+ - blocks.image({ src, alt?, title?, width?, height? })
661
+ - blocks.youtube({ src?, start?, width?, height? })
662
+
663
+ Agent/tool usage:
664
+ - Call createTemplate({ preset: "slide.twoCol", left: [blocks.paragraph("Left")], right: [blocks.imageBlock({ src })] })
665
+ - Wrap returned slides in { type: "doc", content: [/* slides here */] } before sending to the editor.
666
+
667
+ Notes:
668
+ - Size defaults to 16x9; override via slideAttrs.size.
669
+ - Layout defaults: singleCol uses "1"; twoCol uses "1-1" unless rowAttrs.layout overrides.
670
+ `.trim();
671
+
672
+ export type { Block, SlideNode, TemplatePreset, CreateTemplateInput };