slotdeck 0.1.0 → 0.1.1
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 +9 -0
- package/dist/deck/validate.js +5 -0
- package/dist/deck/validate.js.map +1 -1
- package/docs/README.md +76 -0
- package/docs/api-reference.md +668 -0
- package/docs/chat-tools.md +253 -0
- package/docs/concepts.md +126 -0
- package/docs/deck-format.md +225 -0
- package/docs/generating.md +218 -0
- package/docs/getting-started.md +139 -0
- package/docs/languages.md +83 -0
- package/docs/layout-freedom.md +113 -0
- package/docs/manifest-format.md +207 -0
- package/docs/providers.md +236 -0
- package/docs/rendering.md +172 -0
- package/docs/template-studio.md +369 -0
- package/docs/troubleshooting.md +166 -0
- package/package.json +3 -2
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Chat tools
|
|
2
|
+
|
|
3
|
+
`presentationTools` packages the planner as four tools for a chat model ("function calling"). The chat model
|
|
4
|
+
talks to the person; the tools do the work; the person approves a plan before anything is written.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { presentationTools, TOOL_NAMES, storeKeys, memoryStore } from 'slotdeck';
|
|
8
|
+
import { directoryStore } from 'slotdeck/node';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## The four tools
|
|
12
|
+
|
|
13
|
+
| Tool name | What it does | Model calls inside |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `plan_presentation` | Decides the slides, the layout of each and the point each makes. Writes nothing yet. | 1 |
|
|
16
|
+
| `edit_presentation_plan` | Changes a plan by instruction. Returns a new plan with a new id. | 1 |
|
|
17
|
+
| `create_presentation` | Writes the approved plan's content, makes its pictures, renders the file. | 1, plus repairs, plus one per photo |
|
|
18
|
+
| `edit_presentation` | Changes a created presentation by instruction. Returns a new presentation with a new id. | 1 or 2, plus repairs |
|
|
19
|
+
|
|
20
|
+
The intended conversation:
|
|
21
|
+
|
|
22
|
+
1. The person asks for a deck. The chat model calls `plan_presentation`.
|
|
23
|
+
2. The chat model shows the plan. The person asks for changes; the chat model calls `edit_presentation_plan`
|
|
24
|
+
as often as needed.
|
|
25
|
+
3. The person approves. The chat model calls `create_presentation`.
|
|
26
|
+
4. The person asks for changes to the result. The chat model calls `edit_presentation`.
|
|
27
|
+
|
|
28
|
+
## Setting up
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const tools = presentationTools({
|
|
32
|
+
template, // from loadTemplate
|
|
33
|
+
llm, // LlmProvider used inside the tools
|
|
34
|
+
store: directoryStore('/mnt/presentations'), // where plans, decks, pictures and files are kept
|
|
35
|
+
images, // optional ImageProvider; without it decks have no generated photos
|
|
36
|
+
icons, // optional IconDef[]
|
|
37
|
+
assets, // optional AssetDef[]
|
|
38
|
+
layout: 'adaptive', // optional; default 'strict'
|
|
39
|
+
fields: { organizationDepartment: 'Acme | Operations' }, // optional deck-level values every deck gets
|
|
40
|
+
numberPatterns: 'from-brief', // optional
|
|
41
|
+
thumbnail: (patternId) => `/static/thumbnails/${patternId}.png`, // optional address of a picture of each layout
|
|
42
|
+
newId: (kind) => `${kind}_${crypto.randomUUID()}`, // optional; default is a short random id
|
|
43
|
+
onProgress: (event, tool) => console.log(tool, event.stage), // optional
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `llm` here is the model the tools use to plan and write. It can be the same model as the chat model or a
|
|
48
|
+
different one.
|
|
49
|
+
|
|
50
|
+
## Giving the tools to the chat model
|
|
51
|
+
|
|
52
|
+
`tools.definitions` is an array of `{ name, description, parameters }`, where `parameters` is a JSON Schema
|
|
53
|
+
object. Wrap it in whatever shape your chat model's API expects:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// One common shape:
|
|
57
|
+
const declarations = tools.definitions.map((tool) => ({ type: 'function', ...tool }));
|
|
58
|
+
|
|
59
|
+
// Another:
|
|
60
|
+
const declarations = tools.definitions.map((tool) => ({ type: 'function', function: tool }));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When the chat model calls a tool, pass the name and the parsed arguments to `tools.call`:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const result = await tools.call(call.name, call.arguments, { signal });
|
|
67
|
+
// result.output -> send this back to the chat model as the tool's result (JSON, small, no binary data)
|
|
68
|
+
// result.plan -> the DeckPlan, when the tool made or changed one (for your own UI)
|
|
69
|
+
// result.deck -> the DeckSpec, when the tool made or changed one
|
|
70
|
+
// result.pptx -> the file bytes, when one was rendered (offer it as a download)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`call` does not throw for expected failures. A missing id, an unknown tool or a model failure comes back as
|
|
74
|
+
`{ output: { error: "..." } }`, in words the chat model can act on. It throws only when `signal` was aborted.
|
|
75
|
+
|
|
76
|
+
## Arguments and results of each tool
|
|
77
|
+
|
|
78
|
+
### `plan_presentation`
|
|
79
|
+
|
|
80
|
+
Arguments:
|
|
81
|
+
|
|
82
|
+
| Name | Type | Required | Meaning |
|
|
83
|
+
| --- | --- | --- | --- |
|
|
84
|
+
| `topic` | string | yes | What the presentation is about, with every fact, figure and name the person gave. Numbers not stated here will not appear on slides. |
|
|
85
|
+
| `slide_count` | integer 1 to 30 | no | Number of slides, when the person asked for one. |
|
|
86
|
+
| `language` | string | no | BCP 47 tag, such as `he` or `en`. Defaults to the template's language. |
|
|
87
|
+
| `image_density` | `none`, `low`, `medium`, `high` | no | How many slides carry a photo. Default `medium`. Forced to `none` when no photos can be made. |
|
|
88
|
+
| `text_size` | `normal`, `large`, `x-large` | no | Size of body text. Default `normal`. |
|
|
89
|
+
| `instructions` | string | no | Audience, tone and anything else to respect. |
|
|
90
|
+
|
|
91
|
+
Output:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"plan_id": "plan_dc7cfffab9",
|
|
96
|
+
"title": "Adopting a Rescue Dog",
|
|
97
|
+
"language": "en",
|
|
98
|
+
"slides": [
|
|
99
|
+
{
|
|
100
|
+
"number": 1,
|
|
101
|
+
"layout": "cover",
|
|
102
|
+
"layout_description": "Opening slide with the presentation's title and a subtitle.",
|
|
103
|
+
"purpose": "Introduce the guide.",
|
|
104
|
+
"thumbnail": "/static/thumbnails/cover.png"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"next": "Show this plan to the user. Use edit_presentation_plan for changes and create_presentation once they approve."
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`thumbnail` is present only when the `thumbnail` option returns an address.
|
|
112
|
+
|
|
113
|
+
### `edit_presentation_plan`
|
|
114
|
+
|
|
115
|
+
Arguments: `plan_id` (string, required), `instruction` (string, required: what to change, in the person's words,
|
|
116
|
+
with any new facts they gave).
|
|
117
|
+
|
|
118
|
+
Output: the same shape as `plan_presentation` with a new `plan_id`, plus:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{ "summary": "Added a table slide comparing three options before the closing slide.", "previous_plan_id": "plan_dc7cfffab9" }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `create_presentation`
|
|
125
|
+
|
|
126
|
+
Arguments: `plan_id` (string, required: the approved plan).
|
|
127
|
+
|
|
128
|
+
Output:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"presentation_id": "deck_851fbb9677",
|
|
133
|
+
"title": "Adopting a Rescue Dog",
|
|
134
|
+
"file": "pptx/deck_851fbb9677.pptx",
|
|
135
|
+
"slides": [{ "number": 1, "layout": "cover", "title": "Welcome Home Guide" }],
|
|
136
|
+
"plan_id": "plan_7e890671a8",
|
|
137
|
+
"warnings": ["slide 4 body: text is 230 characters; the slot fits about 211"]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`file` is the store key of the rendered pptx. `warnings` is present only when there are any.
|
|
142
|
+
|
|
143
|
+
### `edit_presentation`
|
|
144
|
+
|
|
145
|
+
Arguments: `presentation_id` (string, required), `instruction` (string, required).
|
|
146
|
+
|
|
147
|
+
Output: the same shape as `create_presentation` with a new `presentation_id`, plus:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{ "summary": "Shortened the body text on slide 2 and made the title punchier.", "rewritten_slides": [2], "previous_presentation_id": "deck_851fbb9677" }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Errors
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{ "error": "There is no plan with id \"plan_x\"" }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Other messages: `"topic" is required`, `There is no presentation with id "..."`, `Unknown tool "..."`, and the
|
|
160
|
+
messages of `PlanError` and `DeckValidationError` (see [generating.md](generating.md)).
|
|
161
|
+
|
|
162
|
+
## The store
|
|
163
|
+
|
|
164
|
+
The tools keep everything in a `BlobStore`, so the chat model only ever handles ids and short summaries.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
interface BlobStore {
|
|
168
|
+
get(key: string): Promise<Uint8Array | undefined>;
|
|
169
|
+
set(key: string, data: Uint8Array): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Key | Content | Typical size |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| `plan/<id>.json` | A plan: the brief and the slide list | 1 KB |
|
|
176
|
+
| `deck/<id>.json` | A deck: all text, and for each picture its id and description | 4 KB |
|
|
177
|
+
| `asset/<id>` | One generated picture | tens of KB to 1 MB |
|
|
178
|
+
| `pptx/<id>.pptx` | The rendered file | hundreds of KB and up |
|
|
179
|
+
|
|
180
|
+
`storeKeys.plan(id)`, `storeKeys.deck(id)`, `storeKeys.asset(id)` and `storeKeys.pptx(id)` build these keys, so
|
|
181
|
+
your app can read what the tools saved:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const pptx = await store.get(storeKeys.pptx(presentationId)); // to serve a download later
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Why pictures are kept apart: the deck JSON stays a few kilobytes of text, which is all a model ever sees, and a
|
|
188
|
+
pptx can be rebuilt from the JSON and the pictures at any time. Icons and supplied assets are not stored, since
|
|
189
|
+
your app already has them.
|
|
190
|
+
|
|
191
|
+
Every change makes a new plan or deck under a new id. Earlier versions stay, so "undo" is just using the previous
|
|
192
|
+
id.
|
|
193
|
+
|
|
194
|
+
### Stores that come with the package
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const store = memoryStore(); // in memory: tests and short-lived processes; store.keys() lists keys
|
|
198
|
+
const store = directoryStore('/mnt/presentations'); // a folder, such as a mounted volume; keys become file paths under it
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Writing your own
|
|
202
|
+
|
|
203
|
+
Any object with `get` and `set` works: a bucket, a database table, a key-value service.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const store: BlobStore = {
|
|
207
|
+
async get(key) {
|
|
208
|
+
const row = await db.query('select data from blobs where key = $1', [key]);
|
|
209
|
+
return row ? new Uint8Array(row.data) : undefined;
|
|
210
|
+
},
|
|
211
|
+
async set(key, data) {
|
|
212
|
+
await db.query('insert into blobs (key, data) values ($1, $2) on conflict (key) do update set data = $2', [key, Buffer.from(data)]);
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`get` must resolve with `undefined` for a key that does not exist, not throw.
|
|
218
|
+
|
|
219
|
+
## Showing a plan visually
|
|
220
|
+
|
|
221
|
+
A plan lists a layout per slide. To show layouts as pictures, render the template's slides to images once (with
|
|
222
|
+
any tool you have), name each picture after the pattern made from that slide, and return its address from the
|
|
223
|
+
`thumbnail` option. The address is passed through to the chat model's output and is not fetched by slotdeck.
|
|
224
|
+
|
|
225
|
+
## A complete loop
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
async function runTurn(messages) {
|
|
229
|
+
let reply = await chatModel.send({ messages, tools: declarations });
|
|
230
|
+
while (reply.toolCalls?.length) {
|
|
231
|
+
for (const call of reply.toolCalls) {
|
|
232
|
+
const result = await tools.call(call.name, call.arguments);
|
|
233
|
+
if (result.pptx) offerDownload(result.output.presentation_id, result.pptx);
|
|
234
|
+
messages.push({ role: 'tool', toolCallId: call.id, content: JSON.stringify(result.output) });
|
|
235
|
+
}
|
|
236
|
+
reply = await chatModel.send({ messages, tools: declarations });
|
|
237
|
+
}
|
|
238
|
+
return reply.text;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`chatModel`, `offerDownload` and the message shape are your application's; only `tools.definitions` and
|
|
243
|
+
`tools.call` come from slotdeck.
|
|
244
|
+
|
|
245
|
+
## Guidance for the chat model's system prompt
|
|
246
|
+
|
|
247
|
+
The tool descriptions already tell the model the order to work in. It helps to add, in your own system prompt:
|
|
248
|
+
|
|
249
|
+
- Put every fact, figure and name the person gave into `topic`. Numbers that are not in `topic` will not be used.
|
|
250
|
+
- Show the plan and wait for approval before calling `create_presentation`.
|
|
251
|
+
- For a change to a finished presentation, call `edit_presentation` with the person's words; do not plan again
|
|
252
|
+
from scratch.
|
|
253
|
+
- Refer to presentations and plans by id only. Never ask for or produce deck JSON.
|
package/docs/concepts.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Concepts
|
|
2
|
+
|
|
3
|
+
## Template
|
|
4
|
+
|
|
5
|
+
An ordinary `.pptx` file: a deck of sample slides with filler text and sample pictures, as a designer hands it
|
|
6
|
+
over. slotdeck never draws anything. It copies slides of the template and replaces the content of some of their
|
|
7
|
+
shapes, so fonts, colours, backgrounds, logos and decorations are exactly the template's.
|
|
8
|
+
|
|
9
|
+
A loaded template is `{ pkg, manifest }` (type `Template`), made by `loadTemplate(pptxBytes, manifest)`. Loading
|
|
10
|
+
unzips the file once; keep the object and reuse it for every render.
|
|
11
|
+
|
|
12
|
+
## Pattern
|
|
13
|
+
|
|
14
|
+
One reusable slide layout. A pattern points at one slide of the template (`sourceSlide`, counted from 1) and has
|
|
15
|
+
an `id` that decks use to ask for it. Several patterns may point at the same slide (see "derived patterns" in
|
|
16
|
+
[layout-freedom.md](layout-freedom.md)).
|
|
17
|
+
|
|
18
|
+
A slide of the template that is not named by any pattern is simply never used.
|
|
19
|
+
|
|
20
|
+
## Slot
|
|
21
|
+
|
|
22
|
+
One shape on a pattern's slide whose content a deck replaces. A slot has a `name` (what decks call it), the id of
|
|
23
|
+
its shape, and a `kind`:
|
|
24
|
+
|
|
25
|
+
| Kind | The shape on the template | A deck gives it |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `text` | A text box or a shape with text | A string, or an array with one string per paragraph |
|
|
28
|
+
| `image` | A picture | `{ "asset": "<id>" }` naming picture bytes passed at render time |
|
|
29
|
+
| `table` | A table | `{ "header": [...], "rows": [[...], ...] }` |
|
|
30
|
+
| `chart` | A picture that stands where a chart goes | `{ "type", "categories", "series" }`, which becomes a native PowerPoint chart |
|
|
31
|
+
|
|
32
|
+
Shapes that are not slots are copied untouched.
|
|
33
|
+
|
|
34
|
+
## Manifest
|
|
35
|
+
|
|
36
|
+
The JSON that lists a template's patterns and slots, with limits for each slot (how many characters fit, how
|
|
37
|
+
many table columns). It is written once per template. Full reference: [manifest-format.md](manifest-format.md).
|
|
38
|
+
|
|
39
|
+
The manifest is the contract between the template and everything else:
|
|
40
|
+
|
|
41
|
+
- The renderer uses it to find shapes.
|
|
42
|
+
- The validator uses it to check a deck.
|
|
43
|
+
- The planner shows it to the language model, so the model can only choose patterns and slots that exist and
|
|
44
|
+
is told how much text each slot holds.
|
|
45
|
+
|
|
46
|
+
## Deck
|
|
47
|
+
|
|
48
|
+
The JSON that describes one presentation: an ordered list of slides, each naming a pattern and giving values for
|
|
49
|
+
its slots. Full reference: [deck-format.md](deck-format.md).
|
|
50
|
+
|
|
51
|
+
A deck is the source of truth. It holds no binary data, so it is small (a few kilobytes), safe to send to a
|
|
52
|
+
model, and can be stored, compared, edited and rendered again at any time.
|
|
53
|
+
|
|
54
|
+
## Deck fields
|
|
55
|
+
|
|
56
|
+
Some text is the same on every slide, such as an organization or department line next to the logo. In the
|
|
57
|
+
manifest such a slot has `deckField: "<field name>"`, and a deck gives the value once:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "fields": { "organizationDepartment": "Acme | Operations" } }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A slide may still set that slot itself, which overrides the field for that slide.
|
|
64
|
+
|
|
65
|
+
## Assets
|
|
66
|
+
|
|
67
|
+
Pictures are passed to the renderer as a map from **asset id** to bytes (PNG, JPEG or GIF). A deck refers to a
|
|
68
|
+
picture only by its id. There are three sources:
|
|
69
|
+
|
|
70
|
+
| Source | Who provides it | Typical use |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| Icons (`IconDef`) | Your app registers a set; the model picks by description | Pictograms on cards |
|
|
73
|
+
| Supplied pictures (`AssetDef`) | Your app passes pictures it already has | A rendered diagram, a product photo, a chart image |
|
|
74
|
+
| Generated pictures | An `ImageProvider` you plug in | Photos described by the model |
|
|
75
|
+
|
|
76
|
+
A generated picture's reference keeps its description (`prompt`) in the deck, so the picture can be made again
|
|
77
|
+
later, and its id is derived from that description, so the same description always maps to the same id.
|
|
78
|
+
|
|
79
|
+
## Layout modes
|
|
80
|
+
|
|
81
|
+
| Mode | Meaning |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `strict` (default) | Every slot must have a value unless its policy says otherwise. Nothing on a slide disappears. |
|
|
84
|
+
| `adaptive` | A slot marked optional (`"empty": "remove"`) may be left out, and its shape is then removed from the slide. |
|
|
85
|
+
|
|
86
|
+
## What happens to a slot without a value
|
|
87
|
+
|
|
88
|
+
Each slot has an `empty` policy in the manifest:
|
|
89
|
+
|
|
90
|
+
| `empty` | Without a value |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `error` (default) | The deck is invalid: the slot is required. |
|
|
93
|
+
| `remove` | In `adaptive` layout the shape is removed. In `strict` layout the slot is required. |
|
|
94
|
+
| `keep` | The template's own content stays. Use it for decoration that suits any content. |
|
|
95
|
+
| `drop` | The shape is always removed, in any layout. Use it for decoration drawn for the sample content, such as a sticker on a sample photo. |
|
|
96
|
+
|
|
97
|
+
A deck can also give `null` for a slot to leave it out explicitly; the shape is removed (allowed where the
|
|
98
|
+
policy is `remove`, in `adaptive` layout).
|
|
99
|
+
|
|
100
|
+
## Character budgets
|
|
101
|
+
|
|
102
|
+
A text slot has `maxChars` (and optionally `maxParagraphs`). These are soft limits: text over the budget is
|
|
103
|
+
reported as a **warning**, not an error, and is still rendered. The planner shows budgets to the model and sends
|
|
104
|
+
a draft that exceeds them back for repair.
|
|
105
|
+
|
|
106
|
+
A budget is an estimate of what fits the box. It cannot see line wrapping, so slots with several paragraphs
|
|
107
|
+
should have budgets with room to spare.
|
|
108
|
+
|
|
109
|
+
## Rows and grids of items
|
|
110
|
+
|
|
111
|
+
A pattern can declare that some of its shapes form a row or a grid of like items (cards, figures, steps) and
|
|
112
|
+
that a deck may choose how many there are. See [layout-freedom.md](layout-freedom.md).
|
|
113
|
+
|
|
114
|
+
## Reading direction
|
|
115
|
+
|
|
116
|
+
A template has a direction (`ltr` or `rtl`), and so has a deck. When they differ, text direction and alignment
|
|
117
|
+
flip while shapes stay where they are. See [languages.md](languages.md).
|
|
118
|
+
|
|
119
|
+
## Models are plugged in
|
|
120
|
+
|
|
121
|
+
slotdeck defines two small interfaces and calls nothing else:
|
|
122
|
+
|
|
123
|
+
- `LlmProvider.generateJson(request)` answers a prompt with JSON.
|
|
124
|
+
- `ImageProvider.generateImage(request)` returns picture bytes.
|
|
125
|
+
|
|
126
|
+
Adapters for Gemini are included. Anything else is a few lines: see [providers.md](providers.md).
|
|
@@ -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).
|