slotdeck 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +16 -1
  2. package/dist/deck/validate.js +12 -5
  3. package/dist/deck/validate.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/llm/gemini.d.ts +4 -0
  8. package/dist/llm/gemini.js +43 -7
  9. package/dist/llm/gemini.js.map +1 -1
  10. package/dist/llm/provider.d.ts +12 -0
  11. package/dist/plan/catalog.d.ts +11 -1
  12. package/dist/plan/catalog.js +20 -7
  13. package/dist/plan/catalog.js.map +1 -1
  14. package/dist/plan/edit.js +9 -5
  15. package/dist/plan/edit.js.map +1 -1
  16. package/dist/plan/plan.d.ts +16 -2
  17. package/dist/plan/plan.js +50 -17
  18. package/dist/plan/plan.js.map +1 -1
  19. package/dist/plan/write.d.ts +8 -1
  20. package/dist/plan/write.js +29 -3
  21. package/dist/plan/write.js.map +1 -1
  22. package/dist/render/render.js +4 -4
  23. package/dist/render/render.js.map +1 -1
  24. package/dist/template/change.d.ts +1 -0
  25. package/dist/template/change.js +12 -0
  26. package/dist/template/change.js.map +1 -1
  27. package/dist/template/instruct.js +4 -2
  28. package/dist/template/instruct.js.map +1 -1
  29. package/dist/template/manifest.d.ts +16 -0
  30. package/dist/template/manifest.js +26 -0
  31. package/dist/template/manifest.js.map +1 -1
  32. package/dist/template/sample.js +4 -3
  33. package/dist/template/sample.js.map +1 -1
  34. package/dist/tools.js +6 -0
  35. package/dist/tools.js.map +1 -1
  36. package/docs/README.md +77 -0
  37. package/docs/api-reference.md +680 -0
  38. package/docs/changelog.md +107 -0
  39. package/docs/chat-tools.md +257 -0
  40. package/docs/concepts.md +126 -0
  41. package/docs/deck-format.md +225 -0
  42. package/docs/generating.md +254 -0
  43. package/docs/getting-started.md +139 -0
  44. package/docs/languages.md +83 -0
  45. package/docs/layout-freedom.md +136 -0
  46. package/docs/manifest-format.md +208 -0
  47. package/docs/providers.md +281 -0
  48. package/docs/rendering.md +172 -0
  49. package/docs/template-studio.md +379 -0
  50. package/docs/troubleshooting.md +174 -0
  51. package/package.json +3 -2
@@ -0,0 +1,225 @@
1
+ # Deck format
2
+
3
+ A deck is a JSON object of type `DeckSpec`. It describes one presentation and contains no binary data.
4
+
5
+ ```ts
6
+ import type { DeckSpec, SlideSpec, SlotValue, ImageRef, TableValue, ChartValue } from 'slotdeck';
7
+ ```
8
+
9
+ ## Complete example
10
+
11
+ ```json
12
+ {
13
+ "version": 1,
14
+ "title": "A year of Riverbend Bike Share",
15
+ "language": "en",
16
+ "direction": "ltr",
17
+ "fields": { "organizationDepartment": "Riverbend Bike Share | Season review" },
18
+ "slides": [
19
+ {
20
+ "pattern": "cover",
21
+ "slots": { "title": "A year of Riverbend Bike Share", "subtitle": "What the 2025 season taught us" }
22
+ },
23
+ {
24
+ "pattern": "text-and-photo",
25
+ "slots": {
26
+ "title": "Bikes became part of the daily commute",
27
+ "body": ["Most rides used to be weekend outings.", "This year the busiest hour is eight on a weekday morning."],
28
+ "photo": { "asset": "photo-commute", "alt": "A commuter on a river path" }
29
+ }
30
+ },
31
+ {
32
+ "pattern": "icon-cards",
33
+ "items": 3,
34
+ "slots": {
35
+ "title": "Three changes for next season",
36
+ "card1Icon": { "asset": "clock" },
37
+ "card1Heading": "Longer hours",
38
+ "card1Text": "Bikes unlock from five in the morning.",
39
+ "card2Icon": { "asset": "shield" },
40
+ "card2Heading": "Safer docks",
41
+ "card2Text": "Every station gets lighting and a camera.",
42
+ "card3Icon": { "asset": "people" },
43
+ "card3Heading": "Group passes",
44
+ "card3Text": "One pass for up to five riders."
45
+ }
46
+ },
47
+ {
48
+ "pattern": "table",
49
+ "slots": {
50
+ "title": "The busiest stations",
51
+ "table": {
52
+ "header": ["Station", "Docks", "Rides a day"],
53
+ "rows": [["Tram Square", "24", "410"], ["River Gate", "18", "365"]]
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "pattern": "chart-native",
59
+ "slots": {
60
+ "title": "Rides grew through the summer",
61
+ "chart": {
62
+ "type": "column",
63
+ "categories": ["Apr-May", "Jun-Jul", "Aug-Sep"],
64
+ "series": [
65
+ { "name": "2024", "values": [5, 9, 11] },
66
+ { "name": "2025", "values": [7, 15, 19] }
67
+ ]
68
+ },
69
+ "chartDescription": "Rides in thousands for each part of the season."
70
+ }
71
+ },
72
+ { "pattern": "closing", "slots": { "title": "Thank you" } }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ The pattern ids and slot names in this example come from the demo template's manifest. Yours come from your own
78
+ manifest.
79
+
80
+ ## `DeckSpec`
81
+
82
+ | Field | Type | Required | Meaning |
83
+ | --- | --- | --- | --- |
84
+ | `version` | `1` | yes | Always the number 1. |
85
+ | `slides` | `SlideSpec[]` | yes | The slides in order. Must not be empty. |
86
+ | `title` | string | no | A name for the deck. Not placed on any slide by itself. |
87
+ | `brief` | string | no | The request the deck was written from. `editDeck` uses it as background. |
88
+ | `language` | string | no | BCP 47 tag of the deck's text, such as `en` or `he`. |
89
+ | `direction` | `"ltr"` or `"rtl"` | no | Reading direction. Defaults to the template's. See [languages.md](languages.md). |
90
+ | `fields` | object of strings | no | Values shared by every slide, by field name. See "Deck fields" below. |
91
+ | `textScale` | number | no | Size of body text as a factor on the template's: `1` as designed, `1.25` large, `1.5` extra large. See [layout-freedom.md](layout-freedom.md). |
92
+
93
+ ## `SlideSpec`
94
+
95
+ | Field | Type | Required | Meaning |
96
+ | --- | --- | --- | --- |
97
+ | `pattern` | string | yes | The `id` of a pattern in the manifest. |
98
+ | `slots` | object | yes | Slot name to value. |
99
+ | `items` | integer | no | Only for a pattern with a row or grid of items: how many items this slide has. Defaults to the number drawn on the template slide. Must be within the pattern's range. |
100
+
101
+ ## Slot values
102
+
103
+ The value depends on the slot's kind in the manifest.
104
+
105
+ ### Text slot
106
+
107
+ A string, or an array of strings with one entry per paragraph.
108
+
109
+ ```json
110
+ "title": "Deliveries are faster"
111
+ "body": ["First paragraph.", "Second paragraph."]
112
+ "body": "First paragraph.\nSecond paragraph."
113
+ ```
114
+
115
+ - A newline inside a string starts a new paragraph, so the last two lines above mean the same.
116
+ - Empty paragraphs are dropped.
117
+ - Each paragraph takes its formatting (font, size, colour, bullet) from the template paragraph in the same
118
+ position. When there are more paragraphs than the template slot has, the last template paragraph's formatting
119
+ is reused. This is how a bulleted list works: the template shape has bulleted paragraphs, and the deck gives
120
+ one string per bullet.
121
+ - Do not put bullet characters, numbering or Markdown in the text. The text is used literally.
122
+ - Limits: `maxChars` counts all characters of all paragraphs together; `maxParagraphs` counts paragraphs. Both
123
+ are soft (a warning, not an error).
124
+
125
+ ### Image slot
126
+
127
+ An `ImageRef`:
128
+
129
+ ```json
130
+ "photo": { "asset": "photo-commute", "alt": "A commuter on a river path" }
131
+ ```
132
+
133
+ | Field | Type | Required | Meaning |
134
+ | --- | --- | --- | --- |
135
+ | `asset` | string | yes | The id of the picture. The bytes are passed to `renderDeck` in `options.assets` under this id. |
136
+ | `alt` | string | no | Alternative text for screen readers, stored on the picture. |
137
+ | `prompt` | string | no | A description the picture can be generated from. Written by the planner for photos; lets the picture be made again later. |
138
+
139
+ How the picture is placed depends on the slot (`fit` in the manifest):
140
+
141
+ - `cover` (the default for photos): the picture fills the frame and is cropped at the edges, keeping its
142
+ proportions. The frame's shape (rounded corners, a circle) is kept.
143
+ - `contain` (the default for icons and graphics): the whole picture is shown, centred, inside the frame's
144
+ area, keeping its proportions.
145
+
146
+ Supported formats are PNG, JPEG and GIF.
147
+
148
+ ### Table slot
149
+
150
+ A `TableValue`:
151
+
152
+ ```json
153
+ "table": { "header": ["Station", "Docks"], "rows": [["Tram Square", "24"], ["River Gate", "18"]] }
154
+ ```
155
+
156
+ - `header` sets the number of columns. Every row must have exactly that many cells, or the deck is invalid.
157
+ - Cells are strings. Numbers must be written as strings (`"24"`).
158
+ - The template table's first row supplies the header style and its following rows supply the body styles in
159
+ turn (so banding is kept). Columns share the table's width equally.
160
+ - Limits (`maxColumns`, `maxRows`, `maxCellChars`) are soft.
161
+
162
+ ### Chart slot
163
+
164
+ A `ChartValue`, which becomes a native PowerPoint chart that can be changed with Edit Data:
165
+
166
+ ```json
167
+ "chart": {
168
+ "type": "column",
169
+ "categories": ["Q1", "Q2", "Q3"],
170
+ "series": [{ "name": "Revenue", "values": [12, 15, 19] }],
171
+ "format": "number"
172
+ }
173
+ ```
174
+
175
+ | Field | Type | Required | Meaning |
176
+ | --- | --- | --- | --- |
177
+ | `type` | `"column"`, `"bar"`, `"line"` or `"pie"` | yes | `column` has vertical bars, `bar` horizontal ones. |
178
+ | `categories` | string[] | yes | Labels along the axis, or the names of the pie slices. At least one. |
179
+ | `series` | `{ name, values }[]` | yes | At least one. Each `values` array must have exactly one number per category. A pie takes exactly one series. |
180
+ | `format` | `"number"` or `"percent"` | no | `percent` shows values with a percent sign (45 is shown as 45%). Any other value is ignored with a warning. |
181
+
182
+ Bars start at zero. Colours and fonts come from the manifest.
183
+
184
+ ### Leaving a slot out
185
+
186
+ - Omit the slot, or give an empty string: the slot has no value, and its `empty` policy decides what happens
187
+ (see [concepts.md](concepts.md)).
188
+ - Give `null`: the slot is explicitly left out and its shape is removed. This needs `layout: 'adaptive'` and a
189
+ slot that is not required.
190
+
191
+ A name in `slots` that the pattern does not have is ignored with a warning.
192
+
193
+ ## Deck fields
194
+
195
+ A text slot whose manifest entry has `"deckField": "x"` takes its value from `deck.fields.x` when the slide does
196
+ not give that slot a value itself. Use it for text that repeats on every slide.
197
+
198
+ ```json
199
+ { "fields": { "organizationDepartment": "Acme | Operations" }, "slides": [ ... ] }
200
+ ```
201
+
202
+ If a required slot depends on a field that the deck does not give, validation reports `this slot is required`
203
+ for that slot on every slide that has it.
204
+
205
+ ## Items of a row or grid
206
+
207
+ For a pattern with a row or grid of items, `items` says how many. The slots of each item are numbered:
208
+ `card1Heading`, `card2Heading`, and so on. With `"items": 3`, give slots for items 1 to 3 only. With more items
209
+ than the template slide draws, the extra items are copies of the last one and have the next numbers
210
+ (`card5Heading`, ...).
211
+
212
+ The first item is the first in the deck's reading direction. In a right-to-left deck on a left-to-right
213
+ template, `card1...` appears at the right.
214
+
215
+ ## Checking a deck
216
+
217
+ ```ts
218
+ import { validateDeck } from 'slotdeck';
219
+
220
+ const issues = validateDeck(manifest, deck, { layout: 'adaptive', assets: ['photo-commute', 'clock'] });
221
+ // [{ severity: 'error' | 'warning', slide?: number, slot?: string, message: string }]
222
+ ```
223
+
224
+ Errors make `renderDeck` throw. Warnings do not. Every message is listed in
225
+ [troubleshooting.md](troubleshooting.md).
@@ -0,0 +1,254 @@
1
+ # Generating and editing decks with a model
2
+
3
+ The planner turns a brief into deck JSON by asking a language model you provide. The model never produces a
4
+ file and never positions anything: it chooses patterns from the manifest and writes text for their slots, inside
5
+ a JSON schema built from the manifest.
6
+
7
+ ```ts
8
+ import { generateDeck, editDeck, planDeck, planOutline, editPlan, writeDeck, planEdit, resolveAssets, PlanError, DeckValidationError } from 'slotdeck';
9
+ import type { Brief, PlanOptions, GenerateOptions, IconDef, AssetDef } from 'slotdeck';
10
+ ```
11
+
12
+ All functions on this page need an `LlmProvider` (see [providers.md](providers.md)).
13
+
14
+ ## `generateDeck(template, brief, options)`: brief in, pptx out
15
+
16
+ ```ts
17
+ const result = await generateDeck(template, brief, options);
18
+ // result.pptx Uint8Array the file
19
+ // result.deck DeckSpec store this: it is what gets edited and rendered again
20
+ // result.assets Record<string, Uint8Array> every picture the deck uses, by asset id
21
+ // result.warnings Issue[] limits the final draft still exceeds
22
+ // result.outline OutlineEntry[] [{ pattern, purpose, items? }] for each slide
23
+ ```
24
+
25
+ It runs `planDeck`, then `resolveAssets`, then `renderDeck`.
26
+
27
+ ### `Brief`
28
+
29
+ | Field | Type | Default | Meaning |
30
+ | --- | --- | --- | --- |
31
+ | `prompt` | string | required | What the deck is about, in the person's own words. Include every fact, figure and name that should appear: the model is told to use only what is stated. |
32
+ | `slideCount` | number, or `{ min, max }` | 6 to 10 | A fixed number of slides, or a range. |
33
+ | `language` | string | the template's | BCP 47 tag of the language to write in. |
34
+ | `direction` | `"ltr"` or `"rtl"` | the language's | Reading direction. |
35
+ | `imageDensity` | `"none"`, `"low"`, `"medium"`, `"high"` | `medium` | How many slides carry a photo: none, at most a quarter, about half, or wherever it suits. Forced to `none` by `generateDeck` when no image source and no supplied photos were given. |
36
+ | `instructions` | string | none | Anything else to respect: audience, tone, points that must be covered. |
37
+ | `fields` | object of strings | none | Deck-level values the template needs (see "Deck fields" in [deck-format.md](deck-format.md)). A pattern that needs a field that is not given is not offered. |
38
+ | `textSize` | `"normal"`, `"large"`, `"x-large"` | `normal` | Size of body text. Larger text gets smaller budgets, so less is written. |
39
+ | `textOnly` | boolean | `false` | Plan every slide as plain text. Only the template's plain text patterns are offered. See "Asking for a kind of slide" below. |
40
+
41
+ ### Options (`GenerateOptions`)
42
+
43
+ | Option | Type | Default | Meaning |
44
+ | --- | --- | --- | --- |
45
+ | `llm` | `LlmProvider` | required | The text model. |
46
+ | `images` | `ImageProvider` | none | Makes photos from descriptions. Without it, decks are planned with no generated photos. |
47
+ | `icons` | `IconDef[]` | none | Your icon set: `{ id, description, data }`. Patterns with icon slots are offered only when this is given. The model picks icons by `description`, so describe what each shows and what it stands for ("a clock: time, schedule"). |
48
+ | `assets` | `AssetDef[]` | none | Pictures you already have: `{ id, kind: 'photo' | 'graphic', description, data }`. The model decides where each fits from its description. |
49
+ | `layout` | `"strict"` or `"adaptive"` | `strict` | Whether optional slots may be left out. |
50
+ | `patterns` | string[] | all | Restrict the deck to these pattern ids. |
51
+ | `numberPatterns` | `"from-brief"`, `"always"`, `"never"` | `from-brief` | When patterns marked `figures` may be used. `from-brief` offers them only if the brief contains digits. |
52
+ | `maxRepairs` | number | 2 | How many times a draft that breaks the template's limits is sent back to the model. |
53
+ | `cache` | `Map<string, Uint8Array>` | none | Generated pictures by asset id. Pass the same map on later calls and pictures whose description did not change are reused. |
54
+ | `imageConcurrency` | number | 3 | How many pictures to generate at once. |
55
+ | `onProgress` | function | none | Called with `{ stage: 'outline' }`, `{ stage: 'write', slides }`, `{ stage: 'repair', attempt, issues }`, `{ stage: 'images', done, total }`, `{ stage: 'render' }`. |
56
+ | `signal` | `AbortSignal` | none | Cancels the work. |
57
+
58
+ ### Example
59
+
60
+ ```ts
61
+ const { pptx, deck, warnings } = await generateDeck(
62
+ template,
63
+ {
64
+ prompt: 'A deck for new adopters: why adopt a rescue dog, the first month at home, what it costs. We rehomed 286 dogs in 2023, 351 in 2024 and 412 in 2025.',
65
+ language: 'en',
66
+ slideCount: 9,
67
+ imageDensity: 'medium',
68
+ fields: { organizationDepartment: 'Harbor Street Shelter | Adoption team' },
69
+ },
70
+ {
71
+ llm,
72
+ images,
73
+ icons: [
74
+ { id: 'clock', description: 'a clock: time, schedule, duration', data: clockPng },
75
+ { id: 'heart', description: 'a heart: care, health, loyalty', data: heartPng },
76
+ ],
77
+ assets: [{ id: 'flow', kind: 'graphic', description: 'diagram of the adoption process in four steps', data: flowPng }],
78
+ layout: 'adaptive',
79
+ onProgress: (event) => console.log(event.stage),
80
+ },
81
+ );
82
+ ```
83
+
84
+ ## How planning works
85
+
86
+ 1. **Outline** (one model call). The model sees every usable pattern with its description and slot limits, and
87
+ answers with one pattern per slide and the point each slide makes.
88
+ 2. **Write** (one model call). The model fills a JSON schema built for exactly that sequence of slides, so it
89
+ cannot invent patterns or slots.
90
+ 3. **Repair** (up to `maxRepairs` more calls). The draft is validated against the manifest. Errors and
91
+ over-budget text go back to the model with the list of problems.
92
+
93
+ Which patterns are offered depends on what you supplied:
94
+
95
+ | A pattern with | Is offered only when |
96
+ | --- | --- |
97
+ | a required photo slot | `imageDensity` is not `none`. `generateDeck` and the chat tools set it to `none` when there is no image source and no supplied photo. If you call `planDeck` yourself without a way to make photos, set `imageDensity: 'none'`. |
98
+ | a required icon slot | `icons` is given |
99
+ | a required graphic slot | `assets` has a picture of kind `graphic` |
100
+ | a required deck field | `brief.fields` has it |
101
+ | `figures: true` | the brief contains digits (or `numberPatterns: 'always'`) |
102
+
103
+ "Required" follows the layout: in `strict` layout an optional slot (`empty: "remove"`) counts as required,
104
+ because nothing may be removed from a slide. Slots with `empty` of `keep` or `drop` are never shown to the
105
+ model.
106
+
107
+ If nothing can be offered, `PlanError` is thrown with the reasons.
108
+
109
+ ### Asking for a kind of slide
110
+
111
+ By default the planner varies the patterns across a deck. A wish about the kind of slides overrides that, and
112
+ there are two ways to state one.
113
+
114
+ **In words.** A wish in `prompt` or `instructions`, such as "text only", "no tables" or "only cards", decides
115
+ which patterns may be used and comes before variety. The planner is told so, and the pattern list it reads marks
116
+ each plain text pattern with the words "Plain text.".
117
+
118
+ **As a flag.** `textOnly: true` is the reliable way to get a deck of text and nothing else. The planner is then
119
+ offered only the plain text patterns, so no other kind can be chosen, and it is told that using the same pattern
120
+ for several slides in a row is expected.
121
+
122
+ ```ts
123
+ await generateDeck(template, { prompt: 'Our move to hybrid work: why, the rules, what changed', textOnly: true }, { llm });
124
+ // for example: cover, text-only, text-only, text-only, text-only, closing
125
+ ```
126
+
127
+ A pattern counts as **plain text** when a slide made from it holds running text and nothing else: all its
128
+ slots are text (a picture that the pattern always drops does not count), it has no row or grid of items, and it
129
+ is not marked `figures`. A cover, a closing slide, a quotation and a title-with-paragraphs slide are plain text;
130
+ cards, tables, charts and anything with a picture or icon are not.
131
+
132
+ Two things to know:
133
+
134
+ - "No pictures" is a different wish. `imageDensity: 'none'` removes photos but still allows cards, tables and
135
+ charts.
136
+ - A text-only deck is only as varied as the template's plain text patterns. A template without one cannot make
137
+ such a deck: `PlanError: Template "x" has no plain text pattern, so a text-only deck cannot be planned from it`.
138
+ Add one as a derived pattern (see [layout-freedom.md](layout-freedom.md)).
139
+
140
+ ### Why numbers are guarded
141
+
142
+ A model given a slide built around a statistic and no data tends to invent one. So patterns marked `figures` are
143
+ held back unless the brief itself contains figures, and chart values that the brief does not state come back as
144
+ warnings such as `chart values not stated in the brief: 89`. Values worked out from the brief, such as a sum, are
145
+ flagged too; treat the warning as a prompt to check.
146
+
147
+ Contact details and names are not guarded. A model asked for a closing slide may invent an email address. State
148
+ such details in the brief, or review the deck.
149
+
150
+ ## The steps on their own
151
+
152
+ Use these when a person should see and approve a plan before the content is written.
153
+
154
+ ```ts
155
+ // 1. Decide the slides. One call, quick, small result: the thing to show a person.
156
+ const plan = await planOutline(template, brief, { llm, icons, layout: 'adaptive' });
157
+ // plan = { version: 1, title, brief, slides: [{ pattern, purpose, items? }] }
158
+
159
+ // 2. Change the plan by instruction. One call. Returns a new plan and one sentence on what changed.
160
+ const { plan: revised, summary } = await editPlan(template, plan, 'Add a slide about pricing before the end', { llm, icons, layout: 'adaptive' });
161
+
162
+ // 3. Write the content. One call plus repairs.
163
+ const { deck, warnings, outline } = await writeDeck(template, revised, { llm, icons, layout: 'adaptive' });
164
+
165
+ // 4. Pictures and file.
166
+ const assets = await resolveAssets(template, deck, { images, icons, assets: supplied });
167
+ const { pptx } = renderDeck(template, deck, { assets, layout: 'adaptive' });
168
+ ```
169
+
170
+ `planDeck(template, brief, options)` is steps 1 and 3 in one call and returns `{ deck, warnings, outline }`.
171
+
172
+ Pass the same `icons`, `assets` and `layout` to every step: the plan was made on the assumption that they are
173
+ available.
174
+
175
+ ### `resolveAssets(template, deck, options?)`
176
+
177
+ Collects the bytes of every picture a deck refers to: supplied icons and assets as they are, described pictures
178
+ from `cache` or from `images`. Returns a map from asset id to bytes, ready for `renderDeck`.
179
+
180
+ | Option | Meaning |
181
+ | --- | --- |
182
+ | `images` | The `ImageProvider` that makes described pictures. |
183
+ | `icons`, `assets` | The same sets the deck was planned with. |
184
+ | `cache` | A `Map` of pictures already made. New ones are added to it. |
185
+ | `concurrency` | How many to generate at once (default 3). |
186
+ | `onProgress`, `signal` | As above. |
187
+
188
+ The image provider is asked for a picture with the aspect ratio of the frame it goes into.
189
+
190
+ ## Editing a deck
191
+
192
+ ```ts
193
+ const edited = await editDeck(template, deck, 'Shorten slide 3 to two bullets and translate the deck to Hebrew', {
194
+ llm,
195
+ images,
196
+ icons,
197
+ assets,
198
+ cache, // pictures of the earlier version; see extractAssets in rendering.md
199
+ layout: 'adaptive',
200
+ });
201
+ // edited.deck the new deck
202
+ // edited.pptx the new file
203
+ // edited.assets its pictures
204
+ // edited.summary one sentence from the model on what it changed
205
+ // edited.rewritten numbers of the slides in the new deck that were written or rewritten
206
+ // edited.warnings
207
+ ```
208
+
209
+ An edit is two model calls. The first decides what the deck looks like after the change: which slides stay,
210
+ move, go, get rewritten or are new. The second writes only the slides that change. Reordering and deleting need
211
+ no second call.
212
+
213
+ - Slides the instruction does not touch are copied by code, so their text cannot drift.
214
+ - A rewritten slide keeps its pictures unless the instruction is about them.
215
+ - A slide given a different pattern takes its photo along.
216
+ - A change of language rewrites every slide, sets the reading direction and translates deck-level fields.
217
+ - "Make the text larger" sets `textScale` and rewrites only the slides whose text no longer fits.
218
+ - "Use three cards instead of four" changes `items` on that slide.
219
+
220
+ `planEdit(template, deck, instruction, options)` does the same without pictures or rendering and returns
221
+ `{ deck, summary, rewritten, warnings }`.
222
+
223
+ Extra options for edits (`EditOptions`):
224
+
225
+ | Option | Meaning |
226
+ | --- | --- |
227
+ | `fields` | Replaces the deck-level values, for example a translated header text. Defaults to the deck's own. |
228
+ | `direction` | Reading direction after the edit. Defaults to the direction of the deck's language. |
229
+ | `imageDensity` | Whether slides added by the edit may use photo patterns. Default `medium`. |
230
+
231
+ Changes made by hand in PowerPoint are not read back. The deck JSON is the source of truth.
232
+
233
+ ## Errors
234
+
235
+ | Error | When | What to do |
236
+ | --- | --- | --- |
237
+ | `PlanError` | No pattern can be used with what was supplied; or the model's outline or edit plan is unusable; or the plan uses patterns that cannot be filled | Read the message: it names the patterns and reasons. Supply the missing icons, fields or images, or retry. |
238
+ | `DeckValidationError` | After all repairs the draft still has error-level issues | `error.issues` lists them. Retry, raise `maxRepairs`, or simplify the brief. |
239
+ | Anything your provider throws | The model call failed | Handled by you: slotdeck does not retry model calls. |
240
+
241
+ Warnings (text a little over a budget, for instance) do not throw. They are returned with the result.
242
+
243
+ ## Cost and time
244
+
245
+ For one deck: two text calls, more if repairs are needed, and one image call per generated photo. Measured on
246
+ the demo template with `gemini-3.8-flash`: a plan takes about 5 seconds, an eight-slide deck about 30 seconds
247
+ from brief to file (with drawn stand-in pictures), and an edit that rewrites one slide about 8 seconds.
248
+
249
+ Planning calls (the outline, a change to a plan, the first step of an edit) ask the model for light reasoning,
250
+ because they choose and arrange rather than write. This made them several times faster and removed the runaway
251
+ answers described in [providers.md](providers.md). Writing the slides keeps the model's default.
252
+
253
+ To keep cost down: reuse `cache` across calls, set `imageDensity: 'low'` or `'none'`, and use `planOutline`
254
+ first so a person can correct the plan before content and pictures are produced.
@@ -0,0 +1,139 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install slotdeck
7
+ ```
8
+
9
+ - Node 22.18 or later.
10
+ - ESM only. Your `package.json` needs `"type": "module"`, or your files need the `.mjs` extension (or you use
11
+ TypeScript with ESM output).
12
+ - Two runtime dependencies come with it: `fflate` (zip) and `@xmldom/xmldom` (XML). There is no native code
13
+ and no office program involved.
14
+
15
+ ## What you need
16
+
17
+ | Thing | What it is | Where it comes from |
18
+ | --- | --- | --- |
19
+ | Template | A `.pptx` file with sample slides | Your designer, or your organization's standard deck |
20
+ | Manifest | JSON describing the template | Written once per template: by `draftManifest` (see [template-studio.md](template-studio.md)) or by hand (see [manifest-format.md](manifest-format.md)) |
21
+ | Deck | JSON describing one new presentation | Written by your code, or by a model through `generateDeck` |
22
+ | Assets | Picture bytes by id | Your icon set, your own pictures, or an image source you plug in |
23
+
24
+ ## 1. Render a deck from JSON (no model)
25
+
26
+ ```ts
27
+ import { readFileSync, writeFileSync } from 'node:fs';
28
+ import { loadTemplate, renderDeck } from 'slotdeck';
29
+ import type { DeckSpec, TemplateManifest } from 'slotdeck';
30
+
31
+ const manifest = JSON.parse(readFileSync('template.manifest.json', 'utf8')) as TemplateManifest;
32
+ const template = loadTemplate(readFileSync('template.pptx'), manifest); // do this once and keep it
33
+
34
+ const deck: DeckSpec = {
35
+ version: 1,
36
+ title: 'Quarterly review',
37
+ fields: { organizationDepartment: 'Acme | Operations' },
38
+ slides: [
39
+ { pattern: 'cover', slots: { title: 'Quarterly review', subtitle: 'What changed since March' } },
40
+ {
41
+ pattern: 'text-and-photo',
42
+ slots: {
43
+ title: 'Deliveries are faster',
44
+ body: ['Median delivery time fell from four days to three.', 'The new depot explains most of it.'],
45
+ photo: { asset: 'depot', alt: 'The new depot' },
46
+ },
47
+ },
48
+ { pattern: 'closing', slots: { title: 'Thank you' } },
49
+ ],
50
+ };
51
+
52
+ const { pptx, warnings } = renderDeck(template, deck, {
53
+ assets: { depot: readFileSync('depot.jpg') },
54
+ layout: 'adaptive', // optional slots that the deck leaves out are removed from the slide
55
+ });
56
+ for (const warning of warnings) console.log(`slide ${warning.slide} ${warning.slot ?? ''}: ${warning.message}`);
57
+ writeFileSync('review.pptx', pptx);
58
+ ```
59
+
60
+ The pattern ids (`cover`, `text-and-photo`, `closing`) and slot names (`title`, `body`, `photo`) are whatever
61
+ the manifest defines. To list them:
62
+
63
+ ```ts
64
+ for (const pattern of manifest.patterns) {
65
+ console.log(pattern.id, '-', pattern.description);
66
+ for (const slot of pattern.slots) console.log(' ', slot.name, slot.kind, slot.empty ?? 'required');
67
+ }
68
+ ```
69
+
70
+ If the deck does not fit the manifest (unknown pattern, a required slot without a value, a picture whose
71
+ asset was not passed), `renderDeck` throws a `DeckValidationError` whose `issues` array says what is wrong. Call
72
+ `validateDeck` first if you want the list without an exception. See [rendering.md](rendering.md).
73
+
74
+ ## 2. Generate a deck with a language model
75
+
76
+ ```ts
77
+ import { generateDeck, loadTemplate } from 'slotdeck';
78
+ import type { LlmProvider } from 'slotdeck';
79
+
80
+ // Any model works: the only requirement is this one method. See providers.md.
81
+ const llm: LlmProvider = {
82
+ async generateJson({ system, prompt, schema }) {
83
+ return await myModel.completeAsJson({ system, prompt, schema }); // must resolve with a parsed JSON value
84
+ },
85
+ };
86
+
87
+ const { pptx, deck, warnings, outline } = await generateDeck(
88
+ template,
89
+ {
90
+ prompt: 'Quarterly review for the operations team: deliveries got faster, returns fell, two depots opened.',
91
+ slideCount: 6,
92
+ imageDensity: 'none', // no image source was given, so plan without photos
93
+ fields: { organizationDepartment: 'Acme | Operations' },
94
+ },
95
+ { llm, layout: 'adaptive' },
96
+ );
97
+ ```
98
+
99
+ Keep the returned `deck`: it is what you store, show, edit and render again. See [generating.md](generating.md).
100
+
101
+ With the included Gemini adapter instead of your own provider:
102
+
103
+ ```ts
104
+ import { gemini, geminiImages } from 'slotdeck';
105
+
106
+ const llm = gemini({ model: 'gemini-3.8-flash', apiKey: process.env.GEMINI_API_KEY });
107
+ const images = geminiImages({ model: 'gemini-3.1-flash-image', apiKey: process.env.GEMINI_API_KEY });
108
+ ```
109
+
110
+ ## 3. Change a deck by instruction
111
+
112
+ ```ts
113
+ import { editDeck } from 'slotdeck';
114
+
115
+ const edited = await editDeck(template, deck, 'Shorten slide 2 and add a slide about returns before the end', { llm });
116
+ // edited.deck, edited.pptx, edited.summary, edited.rewritten
117
+ ```
118
+
119
+ ## 4. Describe a new template
120
+
121
+ ```ts
122
+ import { checkManifest, draftManifest, loadTemplate, renderDeck, sampleDeck } from 'slotdeck';
123
+
124
+ const { manifest, skipped, issues } = await draftManifest(readFileSync('new-template.pptx'), { llm, name: 'new-template' });
125
+ const draft = loadTemplate(readFileSync('new-template.pptx'), manifest);
126
+ console.log(checkManifest(draft)); // [] when the manifest matches the file
127
+
128
+ // A deck that uses every pattern once with every slot full: render it and look at it.
129
+ const { deck: sample, assets } = sampleDeck(draft);
130
+ writeFileSync('sample.pptx', renderDeck(draft, sample, { assets }).pptx);
131
+ ```
132
+
133
+ A drafted manifest is a starting point that a person reviews. See [template-studio.md](template-studio.md).
134
+
135
+ ## Where to go next
136
+
137
+ - The two JSON formats: [deck-format.md](deck-format.md), [manifest-format.md](manifest-format.md)
138
+ - Using it inside a chat assistant: [chat-tools.md](chat-tools.md)
139
+ - Connecting an internal model gateway: [providers.md](providers.md)