slotdeck 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +16 -1
  2. package/dist/deck/validate.js +12 -5
  3. package/dist/deck/validate.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/llm/gemini.d.ts +4 -0
  8. package/dist/llm/gemini.js +43 -7
  9. package/dist/llm/gemini.js.map +1 -1
  10. package/dist/llm/provider.d.ts +12 -0
  11. package/dist/plan/catalog.d.ts +11 -1
  12. package/dist/plan/catalog.js +20 -7
  13. package/dist/plan/catalog.js.map +1 -1
  14. package/dist/plan/edit.js +9 -5
  15. package/dist/plan/edit.js.map +1 -1
  16. package/dist/plan/plan.d.ts +16 -2
  17. package/dist/plan/plan.js +50 -17
  18. package/dist/plan/plan.js.map +1 -1
  19. package/dist/plan/write.d.ts +8 -1
  20. package/dist/plan/write.js +29 -3
  21. package/dist/plan/write.js.map +1 -1
  22. package/dist/render/render.js +4 -4
  23. package/dist/render/render.js.map +1 -1
  24. package/dist/template/change.d.ts +1 -0
  25. package/dist/template/change.js +12 -0
  26. package/dist/template/change.js.map +1 -1
  27. package/dist/template/instruct.js +4 -2
  28. package/dist/template/instruct.js.map +1 -1
  29. package/dist/template/manifest.d.ts +16 -0
  30. package/dist/template/manifest.js +26 -0
  31. package/dist/template/manifest.js.map +1 -1
  32. package/dist/template/sample.js +4 -3
  33. package/dist/template/sample.js.map +1 -1
  34. package/dist/tools.js +6 -0
  35. package/dist/tools.js.map +1 -1
  36. package/docs/README.md +77 -0
  37. package/docs/api-reference.md +680 -0
  38. package/docs/changelog.md +107 -0
  39. package/docs/chat-tools.md +257 -0
  40. package/docs/concepts.md +126 -0
  41. package/docs/deck-format.md +225 -0
  42. package/docs/generating.md +254 -0
  43. package/docs/getting-started.md +139 -0
  44. package/docs/languages.md +83 -0
  45. package/docs/layout-freedom.md +136 -0
  46. package/docs/manifest-format.md +208 -0
  47. package/docs/providers.md +281 -0
  48. package/docs/rendering.md +172 -0
  49. package/docs/template-studio.md +379 -0
  50. package/docs/troubleshooting.md +174 -0
  51. package/package.json +3 -2
@@ -0,0 +1,107 @@
1
+ # Changelog
2
+
3
+ What changed in each version, newest first, with what to do when upgrading. The version you have installed is
4
+ in `node_modules/slotdeck/package.json`.
5
+
6
+ ## 0.1.2
7
+
8
+ ### Added
9
+
10
+ - **Text-only decks.** `textOnly: true` on a brief, or `text_only: true` in the `plan_presentation` tool, plans
11
+ every slide as plain text: a title with paragraphs or a list, and no pictures, icons, cards, figures, tables
12
+ or charts. The planner is then offered only the template's plain text patterns, so no other kind can be
13
+ chosen. See "Asking for a kind of slide" in [generating.md](generating.md).
14
+ - **Wishes about the kind of slides are honoured.** A wish stated in words in the brief or in an edit
15
+ instruction ("text only", "no tables", "only cards") now decides which patterns may be used and comes before
16
+ variety. It works when planning, when changing a plan, and when changing a finished deck. The pattern list
17
+ the model reads marks each plain text pattern with "Plain text.".
18
+ - **`isPlainText(pattern)`** tells whether a pattern is plain text, for an app that wants to show or check
19
+ which layouts a text-only deck can use.
20
+ - **Larger body text for a whole template.** `textScale` on the manifest enlarges the body text of every deck
21
+ made from the template (`1.3` turns 9 pt into about 12 pt). Budgets shrink to match in validation, in what
22
+ the planner shows the model, and in `sampleDeck`. Set it by hand or with
23
+ `changeManifest(template, [{ op: 'setManifest', textScale: 1.3 }])`; `editManifest` understands requests such
24
+ as "make body text 20 percent larger". See "Larger text for a whole template" in
25
+ [layout-freedom.md](layout-freedom.md).
26
+ - **`maxOutputTokens` on requests to the model.** The planner passes a ceiling for each answer with every
27
+ request it makes. See "Runaway answers" in [providers.md](providers.md).
28
+ - **`effort: 'low'` on planning requests.** Calls that choose and arrange rather than write (the outline, a
29
+ change to a plan, the first step of an edit) ask for light reasoning.
30
+
31
+ ### Changed
32
+
33
+ - **Table text and chart labels follow the text scale**, where the manifest states their size
34
+ (`bodyText.size`, `headerText.size`, `text.size`). This applies to a deck's own text size (`textScale`,
35
+ `textSize`, `text_size`) as well as to the new template scale, and a table cell then holds fewer characters.
36
+ Before, tables and charts kept their size when a deck asked for larger text.
37
+ - **The Gemini adapter limits the output of every request.** The limit is the request's estimate plus 8,192
38
+ tokens for reasoning, or 24,576 when the request has none, or the adapter's `maxOutputTokens` when set.
39
+ Before, a request had no limit unless `maxOutputTokens` was set.
40
+ - **The Gemini adapter sends a request once more** when the answer was cut off at the limit, after a server
41
+ error (500 to 504), and when the service's recitation filter blocks an ordinary answer. A bad key, a quota or
42
+ a bad request is not repeated.
43
+ - **The Gemini adapter uses thinking level `low` for requests with `effort: 'low'`**, unless `thinkingLevel`
44
+ is set on the adapter. Planning calls became several times faster: a plan takes about 5 seconds.
45
+ - The per-slide sentence of a plan is asked for as "a single sentence of at most 25 words".
46
+
47
+ ### Fixed
48
+
49
+ - **A request for a text-only presentation gave cards and tables.** The planner was told to vary the layouts
50
+ and nothing said that a wish about the kind of slides comes first; and "no pictures" was the most that
51
+ reached it. Fixed by the two additions above.
52
+ - **A model call could hang for minutes.** With its default reasoning the model sometimes ran away inside one
53
+ text field of a planning answer and wrote tens of thousands of tokens. Measured on a text-only plan: 6 of 24
54
+ calls with default reasoning, none of 18 with light reasoning. Planning calls now use light reasoning, every
55
+ call has an output limit, and a cut-off answer is asked for once more.
56
+
57
+ ### Upgrading from 0.1.1
58
+
59
+ No code has to change. To benefit:
60
+
61
+ - **Chat assistants:** `plan_presentation` has a new optional argument, `text_only`. It is in
62
+ `tools.definitions`, so it reaches the chat model when the definitions are passed as before. If your system
63
+ prompt lists the tool's arguments, add it, and tell the model that "no pictures" (`image_density: "none"`)
64
+ still allows cards, tables and charts.
65
+ - **Small body text:** if decks from your template have body text that is too small, add `"textScale": 1.3`
66
+ (or another factor) to the manifest, render `sampleDeck` once to see that everything still fits, and keep the
67
+ manifest. A manifest with `textScale` is ignored by 0.1.1 and earlier, which show the designed size.
68
+ - **Your own `LlmProvider`:** requests now carry two optional fields, `maxOutputTokens` and `effort`. A provider
69
+ that ignores them keeps working. To get the protection and the speed, pass `maxOutputTokens` on as your
70
+ model's output limit (adding room for reasoning tokens if your model counts them in the same limit), use
71
+ light reasoning when `effort` is `'low'`, and retry once when an answer is cut off. See "Runaway answers" in
72
+ [providers.md](providers.md).
73
+ - **Tables with larger text:** a deck that asks for larger text now enlarges table text too, where the
74
+ manifest states its size, and gives cells a smaller budget. A deck stored with `textScale` renders with
75
+ larger table text than before.
76
+
77
+ ## 0.1.1
78
+
79
+ ### Added
80
+
81
+ - **Documentation inside the package.** The `docs/` folder ships with the npm package
82
+ (`node_modules/slotdeck/docs/`), in plain Markdown with no links to the internet, so it can be read where
83
+ there is no network access. Start at [README.md](README.md).
84
+
85
+ ### Changed
86
+
87
+ - **Unknown chart format.** `validateDeck` warns about a chart `format` other than `"number"` or `"percent"`
88
+ (`chart format "x" is not known and is ignored`). Before, it was ignored silently.
89
+
90
+ ### Upgrading from 0.1.0
91
+
92
+ Nothing to change.
93
+
94
+ ## 0.1.0
95
+
96
+ First release.
97
+
98
+ - **Renderer:** `loadTemplate`, `renderDeck`, `validateDeck`, `extractAssets`. Text, pictures, tables and
99
+ native charts; rows and grids with a variable number of items; larger text on request; derived patterns.
100
+ - **Planner and editor:** `generateDeck`, `planDeck`, `planOutline`, `editPlan`, `writeDeck`, `editDeck`,
101
+ `planEdit`, `resolveAssets`, with any model through `LlmProvider` and `ImageProvider`.
102
+ - **Chat tools:** `presentationTools` with `plan_presentation`, `edit_presentation_plan`,
103
+ `create_presentation` and `edit_presentation`, and stores (`memoryStore`, `directoryStore`).
104
+ - **Gemini adapters:** `gemini`, `geminiImages`, `vertexEndpoint`.
105
+ - **Reading direction:** right-to-left decks on left-to-right templates and the reverse.
106
+ - **Template studio:** `draftManifest`, `checkManifest`, `sampleDeck`, `changeManifest`, `editManifest`,
107
+ `studioView`, `describeManifest`, `inspectTemplate`.
@@ -0,0 +1,257 @@
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
+ | `text_only` | boolean | no | `true` when the person asks for a presentation of text only. Every slide is then plain text: a title with paragraphs or a list, with no pictures, icons, cards, figures, tables or charts. Only the template's plain text layouts are used. |
90
+ | `instructions` | string | no | Audience, tone and anything else to respect. |
91
+
92
+ Output:
93
+
94
+ ```json
95
+ {
96
+ "plan_id": "plan_dc7cfffab9",
97
+ "title": "Adopting a Rescue Dog",
98
+ "language": "en",
99
+ "slides": [
100
+ {
101
+ "number": 1,
102
+ "layout": "cover",
103
+ "layout_description": "Opening slide with the presentation's title and a subtitle.",
104
+ "purpose": "Introduce the guide.",
105
+ "thumbnail": "/static/thumbnails/cover.png"
106
+ }
107
+ ],
108
+ "next": "Show this plan to the user. Use edit_presentation_plan for changes and create_presentation once they approve."
109
+ }
110
+ ```
111
+
112
+ `thumbnail` is present only when the `thumbnail` option returns an address.
113
+
114
+ ### `edit_presentation_plan`
115
+
116
+ Arguments: `plan_id` (string, required), `instruction` (string, required: what to change, in the person's words,
117
+ with any new facts they gave).
118
+
119
+ Output: the same shape as `plan_presentation` with a new `plan_id`, plus:
120
+
121
+ ```json
122
+ { "summary": "Added a table slide comparing three options before the closing slide.", "previous_plan_id": "plan_dc7cfffab9" }
123
+ ```
124
+
125
+ ### `create_presentation`
126
+
127
+ Arguments: `plan_id` (string, required: the approved plan).
128
+
129
+ Output:
130
+
131
+ ```json
132
+ {
133
+ "presentation_id": "deck_851fbb9677",
134
+ "title": "Adopting a Rescue Dog",
135
+ "file": "pptx/deck_851fbb9677.pptx",
136
+ "slides": [{ "number": 1, "layout": "cover", "title": "Welcome Home Guide" }],
137
+ "plan_id": "plan_7e890671a8",
138
+ "warnings": ["slide 4 body: text is 230 characters; the slot fits about 211"]
139
+ }
140
+ ```
141
+
142
+ `file` is the store key of the rendered pptx. `warnings` is present only when there are any.
143
+
144
+ ### `edit_presentation`
145
+
146
+ Arguments: `presentation_id` (string, required), `instruction` (string, required).
147
+
148
+ Output: the same shape as `create_presentation` with a new `presentation_id`, plus:
149
+
150
+ ```json
151
+ { "summary": "Shortened the body text on slide 2 and made the title punchier.", "rewritten_slides": [2], "previous_presentation_id": "deck_851fbb9677" }
152
+ ```
153
+
154
+ ### Errors
155
+
156
+ ```json
157
+ { "error": "There is no plan with id \"plan_x\"" }
158
+ ```
159
+
160
+ Other messages: `"topic" is required`, `There is no presentation with id "..."`, `Unknown tool "..."`, and the
161
+ messages of `PlanError` and `DeckValidationError` (see [generating.md](generating.md)).
162
+
163
+ ## The store
164
+
165
+ The tools keep everything in a `BlobStore`, so the chat model only ever handles ids and short summaries.
166
+
167
+ ```ts
168
+ interface BlobStore {
169
+ get(key: string): Promise<Uint8Array | undefined>;
170
+ set(key: string, data: Uint8Array): Promise<void>;
171
+ }
172
+ ```
173
+
174
+ | Key | Content | Typical size |
175
+ | --- | --- | --- |
176
+ | `plan/<id>.json` | A plan: the brief and the slide list | 1 KB |
177
+ | `deck/<id>.json` | A deck: all text, and for each picture its id and description | 4 KB |
178
+ | `asset/<id>` | One generated picture | tens of KB to 1 MB |
179
+ | `pptx/<id>.pptx` | The rendered file | hundreds of KB and up |
180
+
181
+ `storeKeys.plan(id)`, `storeKeys.deck(id)`, `storeKeys.asset(id)` and `storeKeys.pptx(id)` build these keys, so
182
+ your app can read what the tools saved:
183
+
184
+ ```ts
185
+ const pptx = await store.get(storeKeys.pptx(presentationId)); // to serve a download later
186
+ ```
187
+
188
+ Why pictures are kept apart: the deck JSON stays a few kilobytes of text, which is all a model ever sees, and a
189
+ pptx can be rebuilt from the JSON and the pictures at any time. Icons and supplied assets are not stored, since
190
+ your app already has them.
191
+
192
+ Every change makes a new plan or deck under a new id. Earlier versions stay, so "undo" is just using the previous
193
+ id.
194
+
195
+ ### Stores that come with the package
196
+
197
+ ```ts
198
+ const store = memoryStore(); // in memory: tests and short-lived processes; store.keys() lists keys
199
+ const store = directoryStore('/mnt/presentations'); // a folder, such as a mounted volume; keys become file paths under it
200
+ ```
201
+
202
+ ### Writing your own
203
+
204
+ Any object with `get` and `set` works: a bucket, a database table, a key-value service.
205
+
206
+ ```ts
207
+ const store: BlobStore = {
208
+ async get(key) {
209
+ const row = await db.query('select data from blobs where key = $1', [key]);
210
+ return row ? new Uint8Array(row.data) : undefined;
211
+ },
212
+ async set(key, data) {
213
+ await db.query('insert into blobs (key, data) values ($1, $2) on conflict (key) do update set data = $2', [key, Buffer.from(data)]);
214
+ },
215
+ };
216
+ ```
217
+
218
+ `get` must resolve with `undefined` for a key that does not exist, not throw.
219
+
220
+ ## Showing a plan visually
221
+
222
+ A plan lists a layout per slide. To show layouts as pictures, render the template's slides to images once (with
223
+ any tool you have), name each picture after the pattern made from that slide, and return its address from the
224
+ `thumbnail` option. The address is passed through to the chat model's output and is not fetched by slotdeck.
225
+
226
+ ## A complete loop
227
+
228
+ ```ts
229
+ async function runTurn(messages) {
230
+ let reply = await chatModel.send({ messages, tools: declarations });
231
+ while (reply.toolCalls?.length) {
232
+ for (const call of reply.toolCalls) {
233
+ const result = await tools.call(call.name, call.arguments);
234
+ if (result.pptx) offerDownload(result.output.presentation_id, result.pptx);
235
+ messages.push({ role: 'tool', toolCallId: call.id, content: JSON.stringify(result.output) });
236
+ }
237
+ reply = await chatModel.send({ messages, tools: declarations });
238
+ }
239
+ return reply.text;
240
+ }
241
+ ```
242
+
243
+ `chatModel`, `offerDownload` and the message shape are your application's; only `tools.definitions` and
244
+ `tools.call` come from slotdeck.
245
+
246
+ ## Guidance for the chat model's system prompt
247
+
248
+ The tool descriptions already tell the model the order to work in. It helps to add, in your own system prompt:
249
+
250
+ - Put every fact, figure and name the person gave into `topic`. Numbers that are not in `topic` will not be used.
251
+ - Pass wishes about the kind of slides on, in the person's words ("no tables", "only cards"), in `topic` or
252
+ `instructions`. For a presentation of text only, set `text_only: true`: "no pictures" alone
253
+ (`image_density: "none"`) still allows cards, tables and charts.
254
+ - Show the plan and wait for approval before calling `create_presentation`.
255
+ - For a change to a finished presentation, call `edit_presentation` with the person's words; do not plan again
256
+ from scratch.
257
+ - Refer to presentations and plans by id only. Never ask for or produce deck JSON.
@@ -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).