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.
- package/README.md +16 -1
- package/dist/deck/validate.js +12 -5
- package/dist/deck/validate.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/gemini.d.ts +4 -0
- package/dist/llm/gemini.js +43 -7
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/provider.d.ts +12 -0
- package/dist/plan/catalog.d.ts +11 -1
- package/dist/plan/catalog.js +20 -7
- package/dist/plan/catalog.js.map +1 -1
- package/dist/plan/edit.js +9 -5
- package/dist/plan/edit.js.map +1 -1
- package/dist/plan/plan.d.ts +16 -2
- package/dist/plan/plan.js +50 -17
- package/dist/plan/plan.js.map +1 -1
- package/dist/plan/write.d.ts +8 -1
- package/dist/plan/write.js +29 -3
- package/dist/plan/write.js.map +1 -1
- package/dist/render/render.js +4 -4
- package/dist/render/render.js.map +1 -1
- package/dist/template/change.d.ts +1 -0
- package/dist/template/change.js +12 -0
- package/dist/template/change.js.map +1 -1
- package/dist/template/instruct.js +4 -2
- package/dist/template/instruct.js.map +1 -1
- package/dist/template/manifest.d.ts +16 -0
- package/dist/template/manifest.js +26 -0
- package/dist/template/manifest.js.map +1 -1
- package/dist/template/sample.js +4 -3
- package/dist/template/sample.js.map +1 -1
- package/dist/tools.js +6 -0
- package/dist/tools.js.map +1 -1
- package/docs/README.md +77 -0
- package/docs/api-reference.md +680 -0
- package/docs/changelog.md +107 -0
- package/docs/chat-tools.md +257 -0
- package/docs/concepts.md +126 -0
- package/docs/deck-format.md +225 -0
- package/docs/generating.md +254 -0
- package/docs/getting-started.md +139 -0
- package/docs/languages.md +83 -0
- package/docs/layout-freedom.md +136 -0
- package/docs/manifest-format.md +208 -0
- package/docs/providers.md +281 -0
- package/docs/rendering.md +172 -0
- package/docs/template-studio.md +379 -0
- package/docs/troubleshooting.md +174 -0
- 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)
|