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,218 @@
|
|
|
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
|
+
|
|
40
|
+
### Options (`GenerateOptions`)
|
|
41
|
+
|
|
42
|
+
| Option | Type | Default | Meaning |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `llm` | `LlmProvider` | required | The text model. |
|
|
45
|
+
| `images` | `ImageProvider` | none | Makes photos from descriptions. Without it, decks are planned with no generated photos. |
|
|
46
|
+
| `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"). |
|
|
47
|
+
| `assets` | `AssetDef[]` | none | Pictures you already have: `{ id, kind: 'photo' | 'graphic', description, data }`. The model decides where each fits from its description. |
|
|
48
|
+
| `layout` | `"strict"` or `"adaptive"` | `strict` | Whether optional slots may be left out. |
|
|
49
|
+
| `patterns` | string[] | all | Restrict the deck to these pattern ids. |
|
|
50
|
+
| `numberPatterns` | `"from-brief"`, `"always"`, `"never"` | `from-brief` | When patterns marked `figures` may be used. `from-brief` offers them only if the brief contains digits. |
|
|
51
|
+
| `maxRepairs` | number | 2 | How many times a draft that breaks the template's limits is sent back to the model. |
|
|
52
|
+
| `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. |
|
|
53
|
+
| `imageConcurrency` | number | 3 | How many pictures to generate at once. |
|
|
54
|
+
| `onProgress` | function | none | Called with `{ stage: 'outline' }`, `{ stage: 'write', slides }`, `{ stage: 'repair', attempt, issues }`, `{ stage: 'images', done, total }`, `{ stage: 'render' }`. |
|
|
55
|
+
| `signal` | `AbortSignal` | none | Cancels the work. |
|
|
56
|
+
|
|
57
|
+
### Example
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const { pptx, deck, warnings } = await generateDeck(
|
|
61
|
+
template,
|
|
62
|
+
{
|
|
63
|
+
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.',
|
|
64
|
+
language: 'en',
|
|
65
|
+
slideCount: 9,
|
|
66
|
+
imageDensity: 'medium',
|
|
67
|
+
fields: { organizationDepartment: 'Harbor Street Shelter | Adoption team' },
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
llm,
|
|
71
|
+
images,
|
|
72
|
+
icons: [
|
|
73
|
+
{ id: 'clock', description: 'a clock: time, schedule, duration', data: clockPng },
|
|
74
|
+
{ id: 'heart', description: 'a heart: care, health, loyalty', data: heartPng },
|
|
75
|
+
],
|
|
76
|
+
assets: [{ id: 'flow', kind: 'graphic', description: 'diagram of the adoption process in four steps', data: flowPng }],
|
|
77
|
+
layout: 'adaptive',
|
|
78
|
+
onProgress: (event) => console.log(event.stage),
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How planning works
|
|
84
|
+
|
|
85
|
+
1. **Outline** (one model call). The model sees every usable pattern with its description and slot limits, and
|
|
86
|
+
answers with one pattern per slide and the point each slide makes.
|
|
87
|
+
2. **Write** (one model call). The model fills a JSON schema built for exactly that sequence of slides, so it
|
|
88
|
+
cannot invent patterns or slots.
|
|
89
|
+
3. **Repair** (up to `maxRepairs` more calls). The draft is validated against the manifest. Errors and
|
|
90
|
+
over-budget text go back to the model with the list of problems.
|
|
91
|
+
|
|
92
|
+
Which patterns are offered depends on what you supplied:
|
|
93
|
+
|
|
94
|
+
| A pattern with | Is offered only when |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| 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'`. |
|
|
97
|
+
| a required icon slot | `icons` is given |
|
|
98
|
+
| a required graphic slot | `assets` has a picture of kind `graphic` |
|
|
99
|
+
| a required deck field | `brief.fields` has it |
|
|
100
|
+
| `figures: true` | the brief contains digits (or `numberPatterns: 'always'`) |
|
|
101
|
+
|
|
102
|
+
"Required" follows the layout: in `strict` layout an optional slot (`empty: "remove"`) counts as required,
|
|
103
|
+
because nothing may be removed from a slide. Slots with `empty` of `keep` or `drop` are never shown to the
|
|
104
|
+
model.
|
|
105
|
+
|
|
106
|
+
If nothing can be offered, `PlanError` is thrown with the reasons.
|
|
107
|
+
|
|
108
|
+
### Why numbers are guarded
|
|
109
|
+
|
|
110
|
+
A model given a slide built around a statistic and no data tends to invent one. So patterns marked `figures` are
|
|
111
|
+
held back unless the brief itself contains figures, and chart values that the brief does not state come back as
|
|
112
|
+
warnings such as `chart values not stated in the brief: 89`. Values worked out from the brief, such as a sum, are
|
|
113
|
+
flagged too; treat the warning as a prompt to check.
|
|
114
|
+
|
|
115
|
+
Contact details and names are not guarded. A model asked for a closing slide may invent an email address. State
|
|
116
|
+
such details in the brief, or review the deck.
|
|
117
|
+
|
|
118
|
+
## The steps on their own
|
|
119
|
+
|
|
120
|
+
Use these when a person should see and approve a plan before the content is written.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// 1. Decide the slides. One call, quick, small result: the thing to show a person.
|
|
124
|
+
const plan = await planOutline(template, brief, { llm, icons, layout: 'adaptive' });
|
|
125
|
+
// plan = { version: 1, title, brief, slides: [{ pattern, purpose, items? }] }
|
|
126
|
+
|
|
127
|
+
// 2. Change the plan by instruction. One call. Returns a new plan and one sentence on what changed.
|
|
128
|
+
const { plan: revised, summary } = await editPlan(template, plan, 'Add a slide about pricing before the end', { llm, icons, layout: 'adaptive' });
|
|
129
|
+
|
|
130
|
+
// 3. Write the content. One call plus repairs.
|
|
131
|
+
const { deck, warnings, outline } = await writeDeck(template, revised, { llm, icons, layout: 'adaptive' });
|
|
132
|
+
|
|
133
|
+
// 4. Pictures and file.
|
|
134
|
+
const assets = await resolveAssets(template, deck, { images, icons, assets: supplied });
|
|
135
|
+
const { pptx } = renderDeck(template, deck, { assets, layout: 'adaptive' });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`planDeck(template, brief, options)` is steps 1 and 3 in one call and returns `{ deck, warnings, outline }`.
|
|
139
|
+
|
|
140
|
+
Pass the same `icons`, `assets` and `layout` to every step: the plan was made on the assumption that they are
|
|
141
|
+
available.
|
|
142
|
+
|
|
143
|
+
### `resolveAssets(template, deck, options?)`
|
|
144
|
+
|
|
145
|
+
Collects the bytes of every picture a deck refers to: supplied icons and assets as they are, described pictures
|
|
146
|
+
from `cache` or from `images`. Returns a map from asset id to bytes, ready for `renderDeck`.
|
|
147
|
+
|
|
148
|
+
| Option | Meaning |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `images` | The `ImageProvider` that makes described pictures. |
|
|
151
|
+
| `icons`, `assets` | The same sets the deck was planned with. |
|
|
152
|
+
| `cache` | A `Map` of pictures already made. New ones are added to it. |
|
|
153
|
+
| `concurrency` | How many to generate at once (default 3). |
|
|
154
|
+
| `onProgress`, `signal` | As above. |
|
|
155
|
+
|
|
156
|
+
The image provider is asked for a picture with the aspect ratio of the frame it goes into.
|
|
157
|
+
|
|
158
|
+
## Editing a deck
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const edited = await editDeck(template, deck, 'Shorten slide 3 to two bullets and translate the deck to Hebrew', {
|
|
162
|
+
llm,
|
|
163
|
+
images,
|
|
164
|
+
icons,
|
|
165
|
+
assets,
|
|
166
|
+
cache, // pictures of the earlier version; see extractAssets in rendering.md
|
|
167
|
+
layout: 'adaptive',
|
|
168
|
+
});
|
|
169
|
+
// edited.deck the new deck
|
|
170
|
+
// edited.pptx the new file
|
|
171
|
+
// edited.assets its pictures
|
|
172
|
+
// edited.summary one sentence from the model on what it changed
|
|
173
|
+
// edited.rewritten numbers of the slides in the new deck that were written or rewritten
|
|
174
|
+
// edited.warnings
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
An edit is two model calls. The first decides what the deck looks like after the change: which slides stay,
|
|
178
|
+
move, go, get rewritten or are new. The second writes only the slides that change. Reordering and deleting need
|
|
179
|
+
no second call.
|
|
180
|
+
|
|
181
|
+
- Slides the instruction does not touch are copied by code, so their text cannot drift.
|
|
182
|
+
- A rewritten slide keeps its pictures unless the instruction is about them.
|
|
183
|
+
- A slide given a different pattern takes its photo along.
|
|
184
|
+
- A change of language rewrites every slide, sets the reading direction and translates deck-level fields.
|
|
185
|
+
- "Make the text larger" sets `textScale` and rewrites only the slides whose text no longer fits.
|
|
186
|
+
- "Use three cards instead of four" changes `items` on that slide.
|
|
187
|
+
|
|
188
|
+
`planEdit(template, deck, instruction, options)` does the same without pictures or rendering and returns
|
|
189
|
+
`{ deck, summary, rewritten, warnings }`.
|
|
190
|
+
|
|
191
|
+
Extra options for edits (`EditOptions`):
|
|
192
|
+
|
|
193
|
+
| Option | Meaning |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `fields` | Replaces the deck-level values, for example a translated header text. Defaults to the deck's own. |
|
|
196
|
+
| `direction` | Reading direction after the edit. Defaults to the direction of the deck's language. |
|
|
197
|
+
| `imageDensity` | Whether slides added by the edit may use photo patterns. Default `medium`. |
|
|
198
|
+
|
|
199
|
+
Changes made by hand in PowerPoint are not read back. The deck JSON is the source of truth.
|
|
200
|
+
|
|
201
|
+
## Errors
|
|
202
|
+
|
|
203
|
+
| Error | When | What to do |
|
|
204
|
+
| --- | --- | --- |
|
|
205
|
+
| `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. |
|
|
206
|
+
| `DeckValidationError` | After all repairs the draft still has error-level issues | `error.issues` lists them. Retry, raise `maxRepairs`, or simplify the brief. |
|
|
207
|
+
| Anything your provider throws | The model call failed | Handled by you: slotdeck does not retry model calls. |
|
|
208
|
+
|
|
209
|
+
Warnings (text a little over a budget, for instance) do not throw. They are returned with the result.
|
|
210
|
+
|
|
211
|
+
## Cost and time
|
|
212
|
+
|
|
213
|
+
For one deck: two text calls, more if repairs are needed, and one image call per generated photo. On the demo
|
|
214
|
+
template with `gemini-3.8-flash`, a nine-slide deck took about 50 seconds including four pictures. An edit that
|
|
215
|
+
rewrites one slide took about 15 seconds.
|
|
216
|
+
|
|
217
|
+
To keep cost down: reuse `cache` across calls, set `imageDensity: 'low'` or `'none'`, and use `planOutline`
|
|
218
|
+
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)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Languages and reading direction
|
|
2
|
+
|
|
3
|
+
A template is designed for one reading direction (`direction` in the manifest: `ltr` or `rtl`). A deck states
|
|
4
|
+
its own (`direction` in the deck, defaulting to the template's). When they differ, the text flips and the layout
|
|
5
|
+
stays.
|
|
6
|
+
|
|
7
|
+
This works both ways: a Hebrew or Arabic deck on a left-to-right template, and an English deck on a
|
|
8
|
+
right-to-left template.
|
|
9
|
+
|
|
10
|
+
## Setting language and direction
|
|
11
|
+
|
|
12
|
+
In a deck:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{ "version": 1, "language": "he", "direction": "rtl", "slides": [ ... ] }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
In a brief for the planner, `language` is enough: the direction follows from it.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
await generateDeck(template, { prompt: '...', language: 'he' }, options);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`directionOf('he')` returns `'rtl'`; `directionOf('en')` returns `'ltr'`.
|
|
25
|
+
|
|
26
|
+
## What changes in a deck that reads the other way
|
|
27
|
+
|
|
28
|
+
| Thing | What happens |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| Paragraph direction | Every paragraph gets the direction of its own text, from its first letter. A Hebrew deck may hold an English line, and that line is left to right. Text with no letters ("78%") keeps the template paragraph's direction. |
|
|
31
|
+
| Alignment | Left and right alignment are swapped (`alignOnDirectionChange: "flip"`, the default). A paragraph that states no alignment counts as left-aligned. Centred text stays centred. |
|
|
32
|
+
| Rows and grids of items | Filled mirror-wise within each line, so the deck's first item is where its readers start: at the right in a right-to-left deck. Shapes do not move; the values go to the mirrored positions. |
|
|
33
|
+
| Tables | Run in the deck's direction, with text aligned to its start. |
|
|
34
|
+
| Charts | Labels are set right to left. |
|
|
35
|
+
| Run language | Right-to-left text is marked with a right-to-left language. |
|
|
36
|
+
| Shapes | Do not move. A photo on the right stays on the right. Bullets and their indent follow the paragraph direction automatically. |
|
|
37
|
+
|
|
38
|
+
## Keeping alignment
|
|
39
|
+
|
|
40
|
+
Mirroring alignment suits templates whose text boxes are as wide as the area they belong to. It does not suit a
|
|
41
|
+
template whose text boxes share one edge but not the other: flipped text would then start at uneven positions or
|
|
42
|
+
run into a neighbouring shape.
|
|
43
|
+
|
|
44
|
+
- For the whole template: `"alignOnDirectionChange": "keep"` in the manifest.
|
|
45
|
+
- For one slot: `"alignOnDirectionChange": "keep"` (or `"flip"`) on that text slot.
|
|
46
|
+
|
|
47
|
+
A common case for a per-slot `keep` is a line next to a logo: it should stay beside the logo in either
|
|
48
|
+
direction.
|
|
49
|
+
|
|
50
|
+
## Why runs are marked with a language
|
|
51
|
+
|
|
52
|
+
PowerPoint decides where punctuation and spaces go by the language of the run they are in, not only by the
|
|
53
|
+
paragraph's direction. In a run marked as English, the text
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
שאלות: name@example.org
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
is displayed with the colon on the far side of the address. slotdeck therefore marks right-to-left runs with a
|
|
60
|
+
right-to-left language (`he-IL` for Hebrew, `ar-SA` for Arabic) unless the template already gave the run one.
|
|
61
|
+
You do not need to do anything for this.
|
|
62
|
+
|
|
63
|
+
## Fonts
|
|
64
|
+
|
|
65
|
+
Text keeps the template's fonts. If the template's font has no glyphs for the deck's script, PowerPoint
|
|
66
|
+
substitutes another font. For a template that will be used in two scripts, set a complex-script font on its text
|
|
67
|
+
(in PowerPoint: the font for "complex scripts"), or choose fonts that cover both.
|
|
68
|
+
|
|
69
|
+
## Mirrored item order in the deck JSON
|
|
70
|
+
|
|
71
|
+
The deck always numbers items in its own reading order: `card1` is the first card for the reader. You do not
|
|
72
|
+
reverse anything yourself. Validation messages name the slot as the deck wrote it.
|
|
73
|
+
|
|
74
|
+
## Language of generated text
|
|
75
|
+
|
|
76
|
+
The planner writes in `brief.language`. Slot names and pattern ids stay as the manifest defines them (they are
|
|
77
|
+
identifiers, not content). Translating an existing deck is an edit:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await editDeck(template, deck, 'Translate the deck to English', { llm });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This rewrites every slide, sets the direction, and translates deck-level fields.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Layout freedom
|
|
2
|
+
|
|
3
|
+
A deck can depart from the template slide in four bounded ways. In each, the choice is a small value in the deck
|
|
4
|
+
JSON, the manifest says what is allowed, and the code moves the shapes. Nothing is ever positioned by a model.
|
|
5
|
+
|
|
6
|
+
## 1. A different number of items
|
|
7
|
+
|
|
8
|
+
A pattern with a `repeat` in the manifest has a row or grid of like items: cards, figures, steps. The deck says
|
|
9
|
+
how many with `items`.
|
|
10
|
+
|
|
11
|
+
Manifest:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
"repeat": {
|
|
15
|
+
"item": "card",
|
|
16
|
+
"slots": ["card{n}Icon", "card{n}Heading", "card{n}Text"],
|
|
17
|
+
"shapes": [["6"], ["12"], ["18"], ["24"]],
|
|
18
|
+
"min": 2,
|
|
19
|
+
"max": 4
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Deck:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{ "pattern": "icon-cards", "items": 3, "slots": { "title": "...", "card1Icon": {"asset": "clock"}, "card1Heading": "...", "card1Text": "...", "card2Icon": {"asset": "shield"}, "card2Heading": "...", "card2Text": "...", "card3Icon": {"asset": "people"}, "card3Heading": "...", "card3Text": "..." } }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
What the renderer does:
|
|
30
|
+
|
|
31
|
+
- **Fewer items than drawn:** the surplus items' shapes are removed.
|
|
32
|
+
- **More items than drawn:** the last item is copied. A copy's slots have the next numbers (`card5Icon`,
|
|
33
|
+
`card5Heading`, ...) and take the last drawn item's limits.
|
|
34
|
+
- **Spacing:** the items are laid out again around the centre of the area the row occupied. They keep the
|
|
35
|
+
template's spacing when there is room and move closer together when there is not. With a `span`, the row may
|
|
36
|
+
spread out to those limits.
|
|
37
|
+
- Without `items`, the slide has the number of items drawn on the template slide.
|
|
38
|
+
|
|
39
|
+
An `items` value outside `min` to `max` is an error:
|
|
40
|
+
`pattern "icon-cards" takes 2 to 4 cards, not 5`.
|
|
41
|
+
|
|
42
|
+
How many items fit is a property of the template: `changeManifest` refuses a `max` that does not fit side by
|
|
43
|
+
side and says the most that do (see [template-studio.md](template-studio.md)).
|
|
44
|
+
|
|
45
|
+
## 2. Grids
|
|
46
|
+
|
|
47
|
+
With `columns` in the `repeat`, the items form a grid. `columns` is how many items one line holds on the
|
|
48
|
+
template slide.
|
|
49
|
+
|
|
50
|
+
- Items fill lines in reading order and use as few lines as the count needs.
|
|
51
|
+
- They are spread evenly over those lines: in a three-column grid, four items make two lines of two (not three
|
|
52
|
+
and one), five make three and two, three make one line.
|
|
53
|
+
- Each line is centred, and a single line moves to the middle of the grid's area.
|
|
54
|
+
- A grid cannot hold more items than it draws.
|
|
55
|
+
|
|
56
|
+
## 3. Larger text
|
|
57
|
+
|
|
58
|
+
A deck can ask for larger body text:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{ "version": 1, "textScale": 1.25, "slides": [ ... ] }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
or, on a brief for the planner, `textSize: 'large'` (1.25) or `'x-large'` (1.5). The named sizes are in
|
|
65
|
+
`TEXT_SIZES`: `{ normal: 1, large: 1.25, 'x-large': 1.5 }`.
|
|
66
|
+
|
|
67
|
+
- It applies to **body text**: by default, text slots whose template size is 12 pt or under and that are not
|
|
68
|
+
deck fields. Titles and large figures keep their size. A slot can opt in or out with `scales` in the
|
|
69
|
+
manifest.
|
|
70
|
+
- Boxes do not grow, so larger text fits less. The character budget of a scaled slot is divided by the square
|
|
71
|
+
of the scale (a 300-character slot holds 192 at 1.25), and the paragraph budget by the scale. The planner
|
|
72
|
+
shows the model the reduced budgets, so it writes less.
|
|
73
|
+
- Through `editDeck`, "make the text larger" sets the scale and rewrites only the slides whose text no longer
|
|
74
|
+
fits.
|
|
75
|
+
|
|
76
|
+
## 4. Derived patterns
|
|
77
|
+
|
|
78
|
+
Several patterns may point at the same template slide and use it differently. A derived pattern is made by
|
|
79
|
+
copying a pattern and changing some of its slots:
|
|
80
|
+
|
|
81
|
+
| Change | Manifest property | Example |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| Remove a shape always | `"empty": "drop"` on a slot that points at it | A text-only slide from a slide with a photo |
|
|
84
|
+
| Move or resize a text box | `box` on the text slot | Widen the text over the area the photo left free |
|
|
85
|
+
| Set a font size | `fontSize` | Larger body text for a slide with little on it |
|
|
86
|
+
| Space paragraphs | `paragraphGap` | Air between two or three short paragraphs |
|
|
87
|
+
| Give a picture another area | `box` on the image slot | A wide diagram in place of a small sample chart |
|
|
88
|
+
| A native chart in place of a picture | a `chart` slot on the picture's shape | The chart slide with editable data |
|
|
89
|
+
|
|
90
|
+
Example: a text-only pattern derived from a photo pattern.
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "text-only",
|
|
95
|
+
"sourceSlide": 2,
|
|
96
|
+
"description": "A title, an optional supporting line and two or three short paragraphs, with no picture.",
|
|
97
|
+
"slots": [
|
|
98
|
+
{ "name": "photo", "kind": "image", "imageKind": "graphic", "shapeId": "2", "empty": "drop" },
|
|
99
|
+
{ "name": "title", "kind": "text", "shapeId": "5", "size": 34, "maxChars": 83, "box": { "x": 0.0375, "y": 0.2, "w": 0.925, "h": 0.187 } },
|
|
100
|
+
{ "name": "body", "kind": "text", "shapeId": "7", "size": 18, "maxChars": 331, "maxParagraphs": 3, "fontSize": 20, "paragraphGap": 12, "box": { "x": 0.0375, "y": 0.4933, "w": 0.72, "h": 0.3867 } }
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
With `changeManifest` this is three operations (`copyPattern`, `addSlot` with role `decoration`, `setSlot` with
|
|
106
|
+
a `box`), and the budgets are worked out for you: see [template-studio.md](template-studio.md).
|
|
107
|
+
|
|
108
|
+
## What is not possible
|
|
109
|
+
|
|
110
|
+
- Moving a shape to an arbitrary place from a deck. Positions come only from the template and the manifest.
|
|
111
|
+
- Swapping the side of a photo.
|
|
112
|
+
- A grid with more items than the template slide draws.
|
|
113
|
+
- New kinds of slides that the template has no sample for.
|