slotdeck 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +486 -2
- package/dist/deck/numbers.d.ts +14 -0
- package/dist/deck/numbers.js +48 -0
- package/dist/deck/numbers.js.map +1 -0
- package/dist/deck/spec.d.ts +73 -0
- package/dist/deck/spec.js +21 -0
- package/dist/deck/spec.js.map +1 -0
- package/dist/deck/validate.d.ts +49 -0
- package/dist/deck/validate.js +189 -0
- package/dist/deck/validate.js.map +1 -0
- package/dist/generate.d.ts +53 -0
- package/dist/generate.js +44 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/gemini.d.ts +39 -0
- package/dist/llm/gemini.js +147 -0
- package/dist/llm/gemini.js.map +1 -0
- package/dist/llm/provider.d.ts +38 -0
- package/dist/llm/provider.js +2 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.js +31 -0
- package/dist/node.js.map +1 -0
- package/dist/plan/assets.d.ts +28 -0
- package/dist/plan/assets.js +80 -0
- package/dist/plan/assets.js.map +1 -0
- package/dist/plan/catalog.d.ts +106 -0
- package/dist/plan/catalog.js +302 -0
- package/dist/plan/catalog.js.map +1 -0
- package/dist/plan/edit.d.ts +31 -0
- package/dist/plan/edit.js +364 -0
- package/dist/plan/edit.js.map +1 -0
- package/dist/plan/plan.d.ts +106 -0
- package/dist/plan/plan.js +243 -0
- package/dist/plan/plan.js.map +1 -0
- package/dist/plan/write.d.ts +39 -0
- package/dist/plan/write.js +179 -0
- package/dist/plan/write.js.map +1 -0
- package/dist/pptx/package.d.ts +48 -0
- package/dist/pptx/package.js +202 -0
- package/dist/pptx/package.js.map +1 -0
- package/dist/pptx/presentation.d.ts +20 -0
- package/dist/pptx/presentation.js +31 -0
- package/dist/pptx/presentation.js.map +1 -0
- package/dist/pptx/xml.d.ts +23 -0
- package/dist/pptx/xml.js +67 -0
- package/dist/pptx/xml.js.map +1 -0
- package/dist/render/chart.d.ts +18 -0
- package/dist/render/chart.js +158 -0
- package/dist/render/chart.js.map +1 -0
- package/dist/render/extract.d.ts +11 -0
- package/dist/render/extract.js +65 -0
- package/dist/render/extract.js.map +1 -0
- package/dist/render/picture.d.ts +20 -0
- package/dist/render/picture.js +114 -0
- package/dist/render/picture.js.map +1 -0
- package/dist/render/render.d.ts +32 -0
- package/dist/render/render.js +247 -0
- package/dist/render/render.js.map +1 -0
- package/dist/render/repeat.d.ts +15 -0
- package/dist/render/repeat.js +118 -0
- package/dist/render/repeat.js.map +1 -0
- package/dist/render/table.d.ts +12 -0
- package/dist/render/table.js +84 -0
- package/dist/render/table.js.map +1 -0
- package/dist/render/text.d.ts +31 -0
- package/dist/render/text.js +210 -0
- package/dist/render/text.js.map +1 -0
- package/dist/store.d.ts +18 -0
- package/dist/store.js +23 -0
- package/dist/store.js.map +1 -0
- package/dist/template/change.d.ts +138 -0
- package/dist/template/change.js +406 -0
- package/dist/template/change.js.map +1 -0
- package/dist/template/check.d.ts +15 -0
- package/dist/template/check.js +97 -0
- package/dist/template/check.js.map +1 -0
- package/dist/template/draft.d.ts +47 -0
- package/dist/template/draft.js +276 -0
- package/dist/template/draft.js.map +1 -0
- package/dist/template/inspect.d.ts +53 -0
- package/dist/template/inspect.js +157 -0
- package/dist/template/inspect.js.map +1 -0
- package/dist/template/instruct.d.ts +26 -0
- package/dist/template/instruct.js +278 -0
- package/dist/template/instruct.js.map +1 -0
- package/dist/template/manifest.d.ts +226 -0
- package/dist/template/manifest.js +116 -0
- package/dist/template/manifest.js.map +1 -0
- package/dist/template/measure.d.ts +80 -0
- package/dist/template/measure.js +212 -0
- package/dist/template/measure.js.map +1 -0
- package/dist/template/sample.d.ts +17 -0
- package/dist/template/sample.js +96 -0
- package/dist/template/sample.js.map +1 -0
- package/dist/template/view.d.ts +46 -0
- package/dist/template/view.js +38 -0
- package/dist/template/view.js.map +1 -0
- package/dist/tools.d.ts +68 -0
- package/dist/tools.js +266 -0
- package/dist/tools.js.map +1 -0
- package/dist/util/image.d.ts +11 -0
- package/dist/util/image.js +58 -0
- package/dist/util/image.js.map +1 -0
- package/dist/util/png.d.ts +2 -0
- package/dist/util/png.js +50 -0
- package/dist/util/png.js.map +1 -0
- package/package.json +63 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 renanbazinin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,487 @@
|
|
|
1
|
-
#
|
|
1
|
+
# slotdeck
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turn a PowerPoint template into new decks. A language model decides what goes on each slide; the code clones
|
|
4
|
+
the template's own slides and fills them, so the design is never redrawn.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
*Planned, written and illustrated from a one-paragraph brief, on the demo template in this repository
|
|
9
|
+
([deck JSON](examples/showcase/rescue-dog/deck.json)). The shelter and its numbers are invented.*
|
|
10
|
+
|
|
11
|
+
- **Template in, decks out.** Each sample slide of a pptx becomes a reusable **pattern**. A deck picks a
|
|
12
|
+
pattern per slide and supplies text and pictures for its **slots**. Everything that is not a slot (logo,
|
|
13
|
+
backgrounds, fonts, decorations) is copied untouched.
|
|
14
|
+
- **Two layers.** A renderer that needs no model (`template + deck JSON -> pptx`), and a planner that turns a
|
|
15
|
+
brief into that JSON with any model you plug in. Gemini adapters are included.
|
|
16
|
+
- **Editable results.** Decks are plain JSON without binary data, so they can be stored, changed by instruction
|
|
17
|
+
("shorten slide 3") and rendered again. Charts are native PowerPoint charts with their data inside.
|
|
18
|
+
- **Any language, either direction.** A Hebrew or Arabic deck on a left-to-right template mirrors its text and
|
|
19
|
+
the order of its cards, and the other way round.
|
|
20
|
+
- **A studio for templates.** A model drafts the manifest that describes a template; you correct it by hand or
|
|
21
|
+
by instruction; the code checks every change against the pptx.
|
|
22
|
+
|
|
23
|
+
It has two runtime dependencies (`fflate`, `@xmldom/xmldom`), no native code and no headless office.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install slotdeck
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Node 22.18 or later. The package is ESM.
|
|
32
|
+
|
|
33
|
+
## How it works
|
|
34
|
+
|
|
35
|
+
A template is an ordinary deck of sample slides, the kind a designer hands over:
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
A **manifest** (JSON) says which slides are patterns and which shapes on them are slots, with a character
|
|
40
|
+
budget for each text slot. A **deck** (JSON) lists slides, each naming a pattern and giving values for its
|
|
41
|
+
slots:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"version": 1,
|
|
46
|
+
"fields": { "organizationDepartment": "Riverbend Bike Share | Season review" },
|
|
47
|
+
"slides": [
|
|
48
|
+
{ "pattern": "cover", "slots": { "title": "A year of Riverbend Bike Share", "subtitle": "What the 2025 season taught us" } },
|
|
49
|
+
{
|
|
50
|
+
"pattern": "icon-cards",
|
|
51
|
+
"items": 3,
|
|
52
|
+
"slots": {
|
|
53
|
+
"title": "Three changes for next season",
|
|
54
|
+
"card1Icon": { "asset": "clock" }, "card1Heading": "Longer hours", "card1Text": "Bikes unlock from five in the morning.",
|
|
55
|
+
"card2Icon": { "asset": "shield" }, "card2Heading": "Safer docks", "card2Text": "Every station gets lighting.",
|
|
56
|
+
"card3Icon": { "asset": "people" }, "card3Heading": "Group passes", "card3Text": "One pass for up to five riders."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { loadTemplate, renderDeck } from 'slotdeck';
|
|
65
|
+
|
|
66
|
+
const template = loadTemplate(pptxBytes, manifest); // unzip once, reuse for every deck
|
|
67
|
+
const { pptx, warnings } = renderDeck(template, deck, { assets: { clock, shield, people } });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That is the whole renderer: no model, no network, about 100 ms for a 15-slide deck.
|
|
71
|
+
|
|
72
|
+
## Generate a deck with a model
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { gemini, geminiImages, generateDeck, loadTemplate } from 'slotdeck';
|
|
76
|
+
|
|
77
|
+
const template = loadTemplate(pptxBytes, manifest);
|
|
78
|
+
|
|
79
|
+
const { pptx, deck, warnings } = await generateDeck(
|
|
80
|
+
template,
|
|
81
|
+
{
|
|
82
|
+
prompt: 'A deck for new adopters: why adopt a rescue dog, the first month at home, what it costs',
|
|
83
|
+
language: 'en',
|
|
84
|
+
slideCount: 9,
|
|
85
|
+
imageDensity: 'medium', // none | low | medium | high
|
|
86
|
+
fields: { organizationDepartment: 'Harbor Street Shelter | Adoption team' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
llm: gemini({ model: textModel, apiKey }),
|
|
90
|
+
images: geminiImages({ model: imageModel, apiKey }),
|
|
91
|
+
icons: [{ id: 'clock', description: 'a clock: time, schedule', data: clockPng }],
|
|
92
|
+
assets: [{ id: 'flow', kind: 'graphic', description: 'adoption process diagram', data: mermaidPng }],
|
|
93
|
+
layout: 'adaptive', // or 'strict'
|
|
94
|
+
onProgress: (event) => console.log(event.stage),
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`generateDeck` is three steps you can also call yourself:
|
|
100
|
+
|
|
101
|
+
| Step | Function | Model calls |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| Brief to deck JSON | `planDeck` | 2, plus up to 2 repair rounds |
|
|
104
|
+
| Deck JSON and instruction to deck JSON | `planEdit` | 1 or 2, plus up to 2 repair rounds |
|
|
105
|
+
| Deck JSON to picture bytes | `resolveAssets` | one per generated photo |
|
|
106
|
+
| Deck JSON and pictures to pptx | `renderDeck` | none |
|
|
107
|
+
|
|
108
|
+
Keep the returned `deck`. It is plain JSON with no binary data, so it can be stored, changed and rendered again.
|
|
109
|
+
Pass the same `cache` map to later calls and photos whose description did not change are not generated twice.
|
|
110
|
+
|
|
111
|
+
**Icons** come from a set your app registers, and the model picks among them by description. **Photos** come
|
|
112
|
+
from an image source you plug in. **Supplied pictures** (`assets`) are for things your app already has, such as
|
|
113
|
+
a rendered Mermaid diagram.
|
|
114
|
+
|
|
115
|
+
### How planning works
|
|
116
|
+
|
|
117
|
+
1. **Outline.** The model sees the template's patterns with their slot limits and picks one per slide.
|
|
118
|
+
2. **Content.** The model fills a JSON schema built for exactly that sequence of slides, so it cannot invent
|
|
119
|
+
patterns or slots.
|
|
120
|
+
3. **Repair.** The draft is checked against the manifest. Text over a slot's budget or a missing slot goes back
|
|
121
|
+
to the model with the list of problems.
|
|
122
|
+
|
|
123
|
+
Patterns the caller cannot fill are never offered: photo patterns when images are off, icon patterns without an
|
|
124
|
+
icon set, patterns that need a deck field that was not given.
|
|
125
|
+
|
|
126
|
+
Patterns built around numbers (`"figures": true` in the manifest) are offered only when the brief itself
|
|
127
|
+
contains figures. In a live run without this guard the model filled such a slide with an invented statistic. Set
|
|
128
|
+
`numberPatterns: 'always'` to offer them regardless, or `'never'` to rule them out. Chart values the brief does
|
|
129
|
+
not state come back as warnings (`chart values not stated in the brief: 89`).
|
|
130
|
+
|
|
131
|
+
### Editing a deck
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { editDeck, extractAssets } from 'slotdeck';
|
|
135
|
+
|
|
136
|
+
// The deck JSON and the pptx rendered from it are all that has to be stored between turns:
|
|
137
|
+
// the pictures are read back from the file instead of being generated again.
|
|
138
|
+
const cache = new Map(Object.entries(extractAssets(template, deck, previousPptx)));
|
|
139
|
+
|
|
140
|
+
const { pptx, deck: next, summary, rewritten } = await editDeck(template, deck, 'Shorten slide 3 to two bullets', {
|
|
141
|
+
llm,
|
|
142
|
+
images,
|
|
143
|
+
icons, // the same icon set and assets the deck was made with
|
|
144
|
+
cache,
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
An edit is two model calls. The first decides what the deck looks like after the change: which slides stay,
|
|
149
|
+
move, go, get rewritten or are new. The second writes only the slides that change. Reordering and deleting need
|
|
150
|
+
no second call.
|
|
151
|
+
|
|
152
|
+
- Slides the instruction does not touch are copied by code, so their text cannot drift.
|
|
153
|
+
- A rewritten slide keeps its pictures unless the instruction is about them.
|
|
154
|
+
- A slide given a different layout takes its photo along.
|
|
155
|
+
- A change of language rewrites every slide, sets the reading direction and translates deck-level texts.
|
|
156
|
+
- `summary` is one sentence from the model on what it did, to show the person who asked.
|
|
157
|
+
|
|
158
|
+
Changes made by hand in PowerPoint are not read back: the deck JSON is the source of truth.
|
|
159
|
+
|
|
160
|
+
### Chat tools
|
|
161
|
+
|
|
162
|
+
For a chat app, the package provides the four tools a model needs to make a deck with a person in the loop:
|
|
163
|
+
|
|
164
|
+
| Tool | What it does | Model calls |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| `plan_presentation` | Decides the slides, the layout of each and the point each makes | 1 |
|
|
167
|
+
| `edit_presentation_plan` | Changes a plan by instruction | 1 |
|
|
168
|
+
| `create_presentation` | Writes the approved plan's content, makes its pictures, renders the file | 1, plus repairs and pictures |
|
|
169
|
+
| `edit_presentation` | Changes a created deck by instruction | 1 or 2, plus repairs |
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { presentationTools } from 'slotdeck';
|
|
173
|
+
import { directoryStore } from 'slotdeck/node';
|
|
174
|
+
|
|
175
|
+
const tools = presentationTools({
|
|
176
|
+
template,
|
|
177
|
+
llm,
|
|
178
|
+
images, // optional: without it decks are planned with no photos
|
|
179
|
+
icons,
|
|
180
|
+
store: directoryStore('/mnt/presentations'), // a volume, or any object with get(key) and set(key, bytes)
|
|
181
|
+
fields: { organizationDepartment: 'Harbor Street Shelter | Adoption team' },
|
|
182
|
+
thumbnail: (pattern) => `/static/thumbnails/${pattern}.png`,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Declare the tools to the chat model (Interactions format shown):
|
|
186
|
+
const declarations = tools.definitions.map((tool) => ({ type: 'function', ...tool }));
|
|
187
|
+
|
|
188
|
+
// Run a tool the model called:
|
|
189
|
+
const { output, plan, deck, pptx } = await tools.call(call.name, call.arguments);
|
|
190
|
+
// `output` goes back to the model; `plan`, `deck` and `pptx` are for the app's own UI.
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The model only ever handles ids and short summaries. Everything else is kept in the store:
|
|
194
|
+
|
|
195
|
+
| Key | Content | Typical size |
|
|
196
|
+
| --- | --- | --- |
|
|
197
|
+
| `plan/<id>.json` | A plan: the brief and the slide list | 1 KB |
|
|
198
|
+
| `deck/<id>.json` | A deck: all text, and for each picture its id and description | 4 KB |
|
|
199
|
+
| `asset/<id>` | One picture | tens of KB to 1 MB |
|
|
200
|
+
| `pptx/<id>.pptx` | The rendered file | 1 MB and up |
|
|
201
|
+
|
|
202
|
+
Pictures are stored once, apart from the JSON and the file. A deck's JSON never holds image bytes, so no heavy
|
|
203
|
+
data reaches a model, and a pptx can be rebuilt from the JSON and the pictures at any time. Every change makes a
|
|
204
|
+
new plan or deck under a new id; earlier versions stay.
|
|
205
|
+
|
|
206
|
+
To show a plan as layout thumbnails, render the template once and name the pictures after the patterns
|
|
207
|
+
(`scripts/pattern-thumbnails.ts`). `examples/plan-preview.ts` turns a plan into a self-contained page.
|
|
208
|
+
|
|
209
|
+
### Other models
|
|
210
|
+
|
|
211
|
+
A text model is anything with this method:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const llm: LlmProvider = {
|
|
215
|
+
async generateJson({ system, prompt, schema, images, signal }) {
|
|
216
|
+
return myModel.json({ system, prompt, schema }); // resolve with the parsed JSON
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
An image source is anything with `generateImage({ prompt, kind, aspectRatio })` that resolves with PNG, JPEG or
|
|
222
|
+
GIF bytes. It can be an image model, a stock library or a search.
|
|
223
|
+
|
|
224
|
+
The Gemini adapters call the Interactions API with plain `fetch`. For Vertex AI, pass
|
|
225
|
+
`endpoint: vertexEndpoint(project)` and a `headers` function that returns a bearer token. A request that has
|
|
226
|
+
not answered after three minutes is abandoned with an error; `timeoutMs` changes the limit.
|
|
227
|
+
|
|
228
|
+
## Languages and direction
|
|
229
|
+
|
|
230
|
+
A deck states its language and reading direction; the template has its own. When they differ, text flips and
|
|
231
|
+
the layout stays:
|
|
232
|
+
|
|
233
|
+

|
|
234
|
+
|
|
235
|
+
- Every paragraph gets the direction of its own text, so a Hebrew deck may hold an English line.
|
|
236
|
+
- Alignment mirrors (`alignOnDirectionChange: 'flip'`, for the whole template or per slot). Templates whose text
|
|
237
|
+
boxes share one edge but not the other can `keep` it.
|
|
238
|
+
- Rows and grids of items are filled mirror-wise, so the deck's first card is where its readers start.
|
|
239
|
+
- Tables and charts run in the deck's direction.
|
|
240
|
+
- Right-to-left runs are marked with their language, because PowerPoint places punctuation by the language of a
|
|
241
|
+
run: without it, the colon in "contact: name@example.org" lands on the wrong side of the address.
|
|
242
|
+
- Shapes do not move. A photo on the right stays on the right.
|
|
243
|
+
|
|
244
|
+
## Templates
|
|
245
|
+
|
|
246
|
+
- **Manifest** (`TemplateManifest`): which slides are patterns and which shapes are slots. See
|
|
247
|
+
[`templates/demo.manifest.json`](templates/demo.manifest.json).
|
|
248
|
+
- **Layout**: `strict` requires every slot to be filled. `adaptive` lets optional slots be left out, and removes
|
|
249
|
+
their shapes.
|
|
250
|
+
- **Empty slots**: each slot says what happens without a value. `error` makes it required, `remove` makes it
|
|
251
|
+
optional, `keep` leaves the template's content, and `drop` always removes it (for a sticker drawn onto a
|
|
252
|
+
particular sample photo).
|
|
253
|
+
- **Deck fields**: a text that is the same on every slide, such as the organization line beside the logo, is
|
|
254
|
+
one value for the whole deck (`fields`).
|
|
255
|
+
- **Tables**: a table slot takes `{ header, rows }`. The sample table's rows supply the styles (header, then the
|
|
256
|
+
body rows in turn), columns share its width, and it runs in the deck's reading direction.
|
|
257
|
+
- **Supplied pictures**: a `graphic` slot is filled from the `assets` the app passes. The picture keeps its
|
|
258
|
+
proportions inside the frame; `box` gives it a larger area than the sample picture's.
|
|
259
|
+
- **Charts**: a chart slot takes `{ type, categories, series, format }` and becomes a native PowerPoint chart
|
|
260
|
+
(`column`, `bar`, `line` or `pie`) with its data in an embedded workbook, so it can be changed with Edit Data.
|
|
261
|
+
The slot points at the sample picture of a chart, which the real chart replaces. Colours, font and line colour
|
|
262
|
+
come from the manifest. Bars start at zero.
|
|
263
|
+
- **Number patterns**: `figures: true` marks a pattern built around numbers.
|
|
264
|
+
|
|
265
|
+
### Layout freedom
|
|
266
|
+
|
|
267
|
+
A deck can depart from the template slide in bounded ways. In each, the model chooses among options the
|
|
268
|
+
manifest allows and the code moves the shapes; the model never positions anything.
|
|
269
|
+
|
|
270
|
+
- **A different number of items.** A pattern with `repeat` names the shapes that make one card and the range it
|
|
271
|
+
accepts (in the demo template `icon-cards` takes 2 to 4 and `stat-cards` 2 to 6). A slide says how many with
|
|
272
|
+
`items`. Surplus cards are removed, extra ones are copies of the last, and the row is spaced out again around
|
|
273
|
+
its centre. A copied card has its own slots (`card5Icon`, `card5Heading`, ...).
|
|
274
|
+
- **Grids.** With `columns`, the items form a grid. They take as few rows as they need and spread evenly: four
|
|
275
|
+
figures in a three-column grid make two rows of two, five make three and two, three make one row.
|
|
276
|
+
- **Larger text.** `textScale` on a deck (or `textSize: 'large' | 'x-large'` on a brief) enlarges body text:
|
|
277
|
+
slots of 12 pt and under by default, not titles. Because the boxes stay the same size, character budgets fall
|
|
278
|
+
with the square of the scale, so the model writes less.
|
|
279
|
+
- **Derived patterns.** Several patterns can come from one slide. A text slot can be moved and resized (`box`),
|
|
280
|
+
given a font size (`fontSize`) and paragraph spacing, and any shape can be dropped. In the demo template
|
|
281
|
+
`text-only` is the photo slide with its photo dropped and its text widened, and `chart-native` is the chart
|
|
282
|
+
slide with a native chart in place of the picture.
|
|
283
|
+
|
|
284
|
+
## Template studio
|
|
285
|
+
|
|
286
|
+
Writing a manifest by hand means reading shape ids out of a pptx. The studio functions do that work with a
|
|
287
|
+
model, and keep the model from breaking anything.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { checkManifest, draftManifest, gemini, loadTemplate, renderDeck, sampleDeck } from 'slotdeck';
|
|
291
|
+
|
|
292
|
+
// 1. Draft: one model call per slide, plus one for the deck.
|
|
293
|
+
const { manifest, skipped, issues } = await draftManifest(pptx, {
|
|
294
|
+
llm: gemini({ model: textModel, apiKey }),
|
|
295
|
+
name: 'marketing',
|
|
296
|
+
slideImage: (slide) => thumbnails.get(slide), // optional PNG or JPEG of each slide; may be async
|
|
297
|
+
onProgress: ({ done, total }) => report(done, total),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// 2. Check: after every change to the manifest, and whenever a template is loaded.
|
|
301
|
+
const template = loadTemplate(pptx, manifest);
|
|
302
|
+
const problems = checkManifest(template);
|
|
303
|
+
|
|
304
|
+
// 3. Look: every pattern once, every slot filled close to its budget.
|
|
305
|
+
const { deck, assets } = sampleDeck(template);
|
|
306
|
+
const sample = renderDeck(template, deck, { assets }).pptx;
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**`draftManifest`** splits the work so that the model cannot break the manifest. The code measures: every
|
|
310
|
+
shape's id, kind, position, font size and sample text; what sits in the same place on many slides (logo, page
|
|
311
|
+
number: left alone); texts that repeat across slides (deck fields); the reading direction; a character budget
|
|
312
|
+
for each text box from its size and font; a table's columns, rows and cell size; and rows or grids of like
|
|
313
|
+
items, from numbered slot names. The model answers two things for each slide: whether it is a reusable layout
|
|
314
|
+
at all, and what role each shape plays (`title`, `text`, `list`, `number`, `photo`, `icon`, `graphic`, `table`,
|
|
315
|
+
`decoration`, or `fixed`). A label for a shape that is not on the slide is discarded, and the result is run
|
|
316
|
+
through `checkManifest` before it is returned.
|
|
317
|
+
|
|
318
|
+
**`checkManifest`** reports slots that point at a missing shape or the wrong kind of shape, duplicate names,
|
|
319
|
+
rows whose items do not line up with their slots, and text slots with no budget. It reads only the pptx and the
|
|
320
|
+
manifest, so it is safe to run on every load.
|
|
321
|
+
|
|
322
|
+
**`sampleDeck`** uses the template's own filler words, so the result reads like the template with every box
|
|
323
|
+
full. Text that overflows in the sample will overflow in real decks. `items: 'max'` fills rows to their largest
|
|
324
|
+
count, `fill` sets how close to the budget each slot goes.
|
|
325
|
+
|
|
326
|
+
### Changing a manifest
|
|
327
|
+
|
|
328
|
+
A manifest is changed through small operations. A studio screen sends them from its own controls, and
|
|
329
|
+
`editManifest` has a model turn an instruction into the same operations. Either way the code checks each
|
|
330
|
+
operation against the pptx, measures what follows from it, and refuses what cannot work.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import { changeManifest, editManifest, studioView } from 'slotdeck';
|
|
334
|
+
|
|
335
|
+
// By hand: one operation or many.
|
|
336
|
+
const result = changeManifest(template, [
|
|
337
|
+
{ op: 'copyPattern', pattern: 'chart-with-takeaway', id: 'chart-native' },
|
|
338
|
+
{ op: 'addSlot', pattern: 'chart-native', shape: '6', role: 'chart' }, // the sample picture becomes a native chart
|
|
339
|
+
{ op: 'setRepeat', pattern: 'stat-cards', min: 2, max: 6 },
|
|
340
|
+
{ op: 'setSlot', pattern: 'table', slot: 'table', maxColumns: 5, bodyText: { size: 12 } },
|
|
341
|
+
]);
|
|
342
|
+
result.manifest; // the new manifest; the template that was passed in is not touched
|
|
343
|
+
result.applied; // [{ index, change, message }]
|
|
344
|
+
result.rejected; // [{ index, change, reason }] "5 cards do not fit side by side: ... The most is 4"
|
|
345
|
+
result.issues; // checkManifest on the result
|
|
346
|
+
|
|
347
|
+
// By instruction, in any language.
|
|
348
|
+
const edited = await editManifest(template, 'Rename four-icon-cards to icon-cards and make the card icons required.', { llm });
|
|
349
|
+
edited.note; // what the model could not do, in the person's language; usually empty
|
|
350
|
+
|
|
351
|
+
// For a screen: every slide's shapes with their place on the slide, their slots, and the roles they can take.
|
|
352
|
+
const view = studioView(template);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
| Operation | What it changes |
|
|
356
|
+
| --- | --- |
|
|
357
|
+
| `setSlot` | A slot's budget, whether it is required, optional, kept or always removed (`empty`), font size, area (`box`), name, deck field, picture kind, and the settings of a table or chart. |
|
|
358
|
+
| `addSlot` | Makes a shape a slot in a role (`title`, `text`, `list`, `number`, `caption`, `photo`, `icon`, `graphic`, `chart`, `table`, `decoration`), with limits measured from the shape. On a shape that is already a slot it changes the role. |
|
|
359
|
+
| `removeSlot` | The shape goes back to being part of the design. |
|
|
360
|
+
| `setRepeat`, `removeRepeat` | How many items a row or grid takes. Finds the row from numbered slot names when the pattern has none. |
|
|
361
|
+
| `addPattern`, `copyPattern`, `setPattern`, `removePattern` | Which slides are patterns, their ids and descriptions, and variants of a pattern from the same slide. |
|
|
362
|
+
| `setManifest` | The template's name, language, direction and alignment policy. |
|
|
363
|
+
|
|
364
|
+
What the code adds to each change:
|
|
365
|
+
|
|
366
|
+
- **Budgets follow.** A new font size, text area, table text size or column count changes the character budget,
|
|
367
|
+
unless the change states one.
|
|
368
|
+
- **Rows are checked for room.** Asking for more items than the row draws widens it as far as the slide allows,
|
|
369
|
+
up to the margin or the next shape beside the row. Asking for more than fit is refused with the most that do.
|
|
370
|
+
- **Row items stay alike.** A change to `card1Icon` is made to that slot of every card.
|
|
371
|
+
- **Nothing is left broken.** A change that would make the manifest point at something the pptx does not have
|
|
372
|
+
is refused, and the rest of the list is still applied.
|
|
373
|
+
|
|
374
|
+
`editManifest` makes one model call, and a second when some of its operations were refused, so the model can
|
|
375
|
+
correct them. The model never sets a position: `box` and `span` exist only for changes made by hand.
|
|
376
|
+
`describeManifest(template)` returns the text the model reads, for an app that wants to show the manifest to its
|
|
377
|
+
own chat model.
|
|
378
|
+
|
|
379
|
+
### How the demo manifest was made
|
|
380
|
+
|
|
381
|
+
`templates/demo.manifest.json` was made with these functions, not written by hand:
|
|
382
|
+
|
|
383
|
+
1. `draftManifest` on the 13 slides, with a 480-pixel picture of each: 14 model calls, 29 seconds. All 13
|
|
384
|
+
slides became patterns, with the row of cards, the grid of figures, the two columns and the organization
|
|
385
|
+
line found, and no checker issue.
|
|
386
|
+
2. One instruction to `editManifest` renamed five patterns, made the card icons and texts required and added the
|
|
387
|
+
native-chart variant. It also asked for five cards in a row; that was refused, because four fit.
|
|
388
|
+
3. A few changes by hand with `changeManifest`: the text-only variant, chart colours, mirrored alignment for
|
|
389
|
+
right-to-left decks, and tighter budgets for figures and one-line captions.
|
|
390
|
+
|
|
391
|
+
A draft is a starting point. What it does not contain, and a person adds while reviewing: budgets tuned by eye,
|
|
392
|
+
derived patterns, wider rows, picture areas and chart colours. On a 36-slide corporate template (not included
|
|
393
|
+
here) the draft took 37 calls and 80 seconds, skipped the 12 slides that were not layouts, and matched a
|
|
394
|
+
hand-written manifest on 114 of 115 slots.
|
|
395
|
+
|
|
396
|
+
## Try the examples
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
git clone https://github.com/renanbazinin/slotdeck.git
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm install
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
npm test
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Render the example decks (no model, no key). They are written to `out/`:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
npm run demo
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Generate a deck with Gemini. Put `GEMINI_API_KEY=...` in a `.env` file first:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
node --env-file=.env examples/generate-demo.ts "A deck about adopting a rescue dog" --slides 6
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Photos are free drawn stand-ins unless you add `--real-images`, which calls the paid image model. `--lang he`
|
|
423
|
+
writes in another language, `--charts` supplies two sample charts the way an app would supply its own diagrams,
|
|
424
|
+
and `--dry-run` prints the prompts without calling anything.
|
|
425
|
+
|
|
426
|
+
Change a deck that was generated earlier:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
node --env-file=.env examples/edit-demo.ts out/a-deck-about-adopting-a-rescue-dog.deck.json "Shorten slide 3 to two bullets"
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Play a chat app that calls the four tools in turn:
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
node --env-file=.env examples/tools-demo.ts "A deck about adopting a rescue dog"
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Draft a manifest for a template of your own, then change it by instruction:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
node --env-file=.env examples/studio-demo.ts draft path/to/template.pptx
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
node --env-file=.env examples/studio-demo.ts edit "make the subtitle optional on every slide"
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
List the shapes of a template's slides, as the manifest addresses them:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
node scripts/inspect.ts templates/demo.pptx --slides 2,5
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
On Windows with PowerPoint installed, this exports every slide of a deck to PNG, which is how the pictures on
|
|
455
|
+
this page were made:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
powershell -File scripts/render-pptx.ps1 -Path out/patterns-en.pptx -OutDir out/png/patterns-en
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## What to know before relying on it
|
|
462
|
+
|
|
463
|
+
- **Shapes need their own position.** The engine measures the shapes on a slide. A shape that takes its position
|
|
464
|
+
and font size from a layout placeholder, without stating them on the slide, is filled correctly but measured
|
|
465
|
+
as empty, so its budget is only as long as its sample text. Decks exported from Google Slides and decks built
|
|
466
|
+
from free-standing text boxes work as they are.
|
|
467
|
+
- **Budgets are estimates.** A character budget cannot see lines lost to wrapping. Budgets for slots with several
|
|
468
|
+
paragraphs should be set with room to spare, and a sample deck is the way to see them.
|
|
469
|
+
- **Models invent details.** In the showcase deck the model made up an email address and a phone number for the
|
|
470
|
+
closing slide, which were replaced by hand. Figures are guarded (see How planning works); contact details and
|
|
471
|
+
names are not.
|
|
472
|
+
- **What has been run.** The whole path was run against the Gemini Developer API with `gemini-3.8-flash` for
|
|
473
|
+
text and `gemini-3.1-flash-image` for photos, in English and Hebrew. Vertex AI is supported by the adapter but
|
|
474
|
+
has not been run live. Output was checked by rendering in PowerPoint on Windows; other viewers were not tested.
|
|
475
|
+
- **Not built:** swapping the photo side, stacked or combined charts, speaker notes, and reading back changes
|
|
476
|
+
made in PowerPoint.
|
|
477
|
+
|
|
478
|
+
## The demo template and pictures
|
|
479
|
+
|
|
480
|
+
`templates/demo.pptx` and the icons in `examples/icons` are drawn by
|
|
481
|
+
[`scripts/build-demo-template.py`](scripts/build-demo-template.py): no stock photo, logo or icon from anywhere
|
|
482
|
+
else is in them. The four photos of the showcase deck were generated once with an image model from the
|
|
483
|
+
descriptions in its JSON. All of it is covered by the license below and free to reuse.
|
|
484
|
+
|
|
485
|
+
## License
|
|
486
|
+
|
|
487
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TemplateManifest } from '../template/manifest.ts';
|
|
2
|
+
import type { DeckSpec } from './spec.ts';
|
|
3
|
+
import type { Issue } from './validate.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The numbers a text states. "3K" counts as both 3 and 3000, "1,200" as 1200, "45%" as 45.
|
|
6
|
+
* Only Western digits are read.
|
|
7
|
+
*/
|
|
8
|
+
export declare function statedNumbers(text: string): Set<number>;
|
|
9
|
+
/**
|
|
10
|
+
* Chart values that the deck's brief does not state. A model asked for a chart can fill it with
|
|
11
|
+
* plausible numbers; this flags them so a person can check. Values worked out from the brief
|
|
12
|
+
* (a sum, a growth rate applied) are flagged too: the check cannot tell them from invented ones.
|
|
13
|
+
*/
|
|
14
|
+
export declare function unstatedChartValues(manifest: TemplateManifest, deck: DeckSpec): Issue[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isChartValue } from './spec.js';
|
|
2
|
+
/**
|
|
3
|
+
* The numbers a text states. "3K" counts as both 3 and 3000, "1,200" as 1200, "45%" as 45.
|
|
4
|
+
* Only Western digits are read.
|
|
5
|
+
*/
|
|
6
|
+
export function statedNumbers(text) {
|
|
7
|
+
const numbers = new Set();
|
|
8
|
+
for (const match of text.matchAll(/(\d[\d,]*(?:\.\d+)?)\s*([KkMm])?/g)) {
|
|
9
|
+
const value = Number(match[1].replace(/,/g, ''));
|
|
10
|
+
if (!Number.isFinite(value))
|
|
11
|
+
continue;
|
|
12
|
+
numbers.add(value);
|
|
13
|
+
const suffix = match[2]?.toLowerCase();
|
|
14
|
+
if (suffix === 'k')
|
|
15
|
+
numbers.add(value * 1000);
|
|
16
|
+
if (suffix === 'm')
|
|
17
|
+
numbers.add(value * 1_000_000);
|
|
18
|
+
}
|
|
19
|
+
return numbers;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Chart values that the deck's brief does not state. A model asked for a chart can fill it with
|
|
23
|
+
* plausible numbers; this flags them so a person can check. Values worked out from the brief
|
|
24
|
+
* (a sum, a growth rate applied) are flagged too: the check cannot tell them from invented ones.
|
|
25
|
+
*/
|
|
26
|
+
export function unstatedChartValues(manifest, deck) {
|
|
27
|
+
const stated = statedNumbers(deck.brief ?? '');
|
|
28
|
+
const issues = [];
|
|
29
|
+
deck.slides.forEach((slide, index) => {
|
|
30
|
+
const pattern = manifest.patterns.find((candidate) => candidate.id === slide.pattern);
|
|
31
|
+
for (const slot of pattern?.slots ?? []) {
|
|
32
|
+
const value = slide.slots[slot.name];
|
|
33
|
+
if (slot.kind !== 'chart' || !isChartValue(value))
|
|
34
|
+
continue;
|
|
35
|
+
const unknown = [...new Set(value.series.flatMap((series) => series.values))].filter((number) => !stated.has(number));
|
|
36
|
+
if (unknown.length > 0) {
|
|
37
|
+
issues.push({
|
|
38
|
+
severity: 'warning',
|
|
39
|
+
slide: index + 1,
|
|
40
|
+
slot: slot.name,
|
|
41
|
+
message: `chart values not stated in the brief: ${unknown.join(', ')}. Check them before using the deck.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return issues;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=numbers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numbers.js","sourceRoot":"","sources":["../../src/deck/numbers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAIzC;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,mCAAmC,CAAC,EAAE,CAAC;QACvE,MAAM,KAAK,GAAG,MAAM,CAAE,KAAK,CAAC,CAAC,CAAY,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAS;QACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACvC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAA0B,EAAE,IAAc;IAC5E,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;QACtF,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC5D,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YACtH,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC;oBACV,QAAQ,EAAE,SAAS;oBACnB,KAAK,EAAE,KAAK,GAAG,CAAC;oBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,OAAO,EAAE,yCAAyC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,qCAAqC;iBAC1G,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Direction } from '../template/manifest.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A deck as plain JSON: the source of truth that is rendered, stored and edited.
|
|
4
|
+
* It holds no binary data; images are referenced by asset id.
|
|
5
|
+
*/
|
|
6
|
+
export interface DeckSpec {
|
|
7
|
+
version: 1;
|
|
8
|
+
title?: string;
|
|
9
|
+
/** The request the deck was first written from. Later edits use it as background. */
|
|
10
|
+
brief?: string;
|
|
11
|
+
/** BCP 47 tag of the deck's text. */
|
|
12
|
+
language?: string;
|
|
13
|
+
/** Reading direction of the deck. Defaults to the template's. */
|
|
14
|
+
direction?: Direction;
|
|
15
|
+
/** Values shared by every slide, e.g. the header text. */
|
|
16
|
+
fields?: Record<string, string>;
|
|
17
|
+
/** Size of body text as a factor on the template's: 1 as designed, 1.25 large, 1.5 extra large. */
|
|
18
|
+
textScale?: number;
|
|
19
|
+
slides: SlideSpec[];
|
|
20
|
+
}
|
|
21
|
+
export interface SlideSpec {
|
|
22
|
+
/** Id of a pattern in the template manifest. */
|
|
23
|
+
pattern: string;
|
|
24
|
+
/** For a pattern with a row of items: how many items this slide has. Defaults to the template's number. */
|
|
25
|
+
items?: number;
|
|
26
|
+
slots: Record<string, SlotValue>;
|
|
27
|
+
}
|
|
28
|
+
/** Reference to an image passed to the renderer alongside the deck. */
|
|
29
|
+
export interface ImageRef {
|
|
30
|
+
asset: string;
|
|
31
|
+
/** Alternative text for screen readers. */
|
|
32
|
+
alt?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Description the image can be generated from when the asset is not supplied.
|
|
35
|
+
* Kept in the deck so a later re-render can recreate the picture.
|
|
36
|
+
*/
|
|
37
|
+
prompt?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Content of a table slot. */
|
|
40
|
+
export interface TableValue {
|
|
41
|
+
/** Column headings. Their number sets the number of columns. */
|
|
42
|
+
header: string[];
|
|
43
|
+
/** Body rows, each with one cell per column. */
|
|
44
|
+
rows: string[][];
|
|
45
|
+
}
|
|
46
|
+
export type ChartType = 'column' | 'bar' | 'line' | 'pie';
|
|
47
|
+
/** Content of a chart slot. */
|
|
48
|
+
export interface ChartValue {
|
|
49
|
+
/** `column` has vertical bars, `bar` horizontal ones. */
|
|
50
|
+
type: ChartType;
|
|
51
|
+
/** Labels along the axis, or the names of the pie slices. */
|
|
52
|
+
categories: string[];
|
|
53
|
+
/** One or more series, each with one value per category. A pie takes exactly one. */
|
|
54
|
+
series: Array<{
|
|
55
|
+
name: string;
|
|
56
|
+
values: number[];
|
|
57
|
+
}>;
|
|
58
|
+
/** `percent` shows values with a percent sign (45 is shown as 45%). */
|
|
59
|
+
format?: 'number' | 'percent';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* - text slot: a string (newlines split paragraphs) or one string per paragraph
|
|
63
|
+
* - image slot: an {@link ImageRef}
|
|
64
|
+
* - table slot: a {@link TableValue}
|
|
65
|
+
* - chart slot: a {@link ChartValue}
|
|
66
|
+
* - `null`: explicitly leave the slot out (the shape is removed)
|
|
67
|
+
*/
|
|
68
|
+
export type SlotValue = string | string[] | ImageRef | TableValue | ChartValue | null;
|
|
69
|
+
export declare function isImageRef(value: unknown): value is ImageRef;
|
|
70
|
+
export declare function isTableValue(value: unknown): value is TableValue;
|
|
71
|
+
export declare function isChartValue(value: unknown): value is ChartValue;
|
|
72
|
+
/** Normalises a text value to paragraphs. */
|
|
73
|
+
export declare function toParagraphs(value: string | string[]): string[];
|