slotdeck 0.1.1 → 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 (46) hide show
  1. package/README.md +8 -2
  2. package/dist/deck/validate.js +7 -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 +1 -0
  37. package/docs/api-reference.md +13 -1
  38. package/docs/changelog.md +107 -0
  39. package/docs/chat-tools.md +4 -0
  40. package/docs/generating.md +39 -3
  41. package/docs/layout-freedom.md +23 -0
  42. package/docs/manifest-format.md +1 -0
  43. package/docs/providers.md +48 -3
  44. package/docs/template-studio.md +11 -1
  45. package/docs/troubleshooting.md +8 -0
  46. package/package.json +1 -1
@@ -86,6 +86,7 @@ Arguments:
86
86
  | `language` | string | no | BCP 47 tag, such as `he` or `en`. Defaults to the template's language. |
87
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
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. |
89
90
  | `instructions` | string | no | Audience, tone and anything else to respect. |
90
91
 
91
92
  Output:
@@ -247,6 +248,9 @@ async function runTurn(messages) {
247
248
  The tool descriptions already tell the model the order to work in. It helps to add, in your own system prompt:
248
249
 
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.
250
254
  - Show the plan and wait for approval before calling `create_presentation`.
251
255
  - For a change to a finished presentation, call `edit_presentation` with the person's words; do not plan again
252
256
  from scratch.
@@ -36,6 +36,7 @@ It runs `planDeck`, then `resolveAssets`, then `renderDeck`.
36
36
  | `instructions` | string | none | Anything else to respect: audience, tone, points that must be covered. |
37
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
38
  | `textSize` | `"normal"`, `"large"`, `"x-large"` | `normal` | Size of body text. Larger text gets smaller budgets, so less is written. |
39
+ | `textOnly` | boolean | `false` | Plan every slide as plain text. Only the template's plain text patterns are offered. See "Asking for a kind of slide" below. |
39
40
 
40
41
  ### Options (`GenerateOptions`)
41
42
 
@@ -105,6 +106,37 @@ model.
105
106
 
106
107
  If nothing can be offered, `PlanError` is thrown with the reasons.
107
108
 
109
+ ### Asking for a kind of slide
110
+
111
+ By default the planner varies the patterns across a deck. A wish about the kind of slides overrides that, and
112
+ there are two ways to state one.
113
+
114
+ **In words.** A wish in `prompt` or `instructions`, such as "text only", "no tables" or "only cards", decides
115
+ which patterns may be used and comes before variety. The planner is told so, and the pattern list it reads marks
116
+ each plain text pattern with the words "Plain text.".
117
+
118
+ **As a flag.** `textOnly: true` is the reliable way to get a deck of text and nothing else. The planner is then
119
+ offered only the plain text patterns, so no other kind can be chosen, and it is told that using the same pattern
120
+ for several slides in a row is expected.
121
+
122
+ ```ts
123
+ await generateDeck(template, { prompt: 'Our move to hybrid work: why, the rules, what changed', textOnly: true }, { llm });
124
+ // for example: cover, text-only, text-only, text-only, text-only, closing
125
+ ```
126
+
127
+ A pattern counts as **plain text** when a slide made from it holds running text and nothing else: all its
128
+ slots are text (a picture that the pattern always drops does not count), it has no row or grid of items, and it
129
+ is not marked `figures`. A cover, a closing slide, a quotation and a title-with-paragraphs slide are plain text;
130
+ cards, tables, charts and anything with a picture or icon are not.
131
+
132
+ Two things to know:
133
+
134
+ - "No pictures" is a different wish. `imageDensity: 'none'` removes photos but still allows cards, tables and
135
+ charts.
136
+ - A text-only deck is only as varied as the template's plain text patterns. A template without one cannot make
137
+ such a deck: `PlanError: Template "x" has no plain text pattern, so a text-only deck cannot be planned from it`.
138
+ Add one as a derived pattern (see [layout-freedom.md](layout-freedom.md)).
139
+
108
140
  ### Why numbers are guarded
109
141
 
110
142
  A model given a slide built around a statistic and no data tends to invent one. So patterns marked `figures` are
@@ -210,9 +242,13 @@ Warnings (text a little over a budget, for instance) do not throw. They are retu
210
242
 
211
243
  ## Cost and time
212
244
 
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.
245
+ For one deck: two text calls, more if repairs are needed, and one image call per generated photo. Measured on
246
+ the demo template with `gemini-3.8-flash`: a plan takes about 5 seconds, an eight-slide deck about 30 seconds
247
+ from brief to file (with drawn stand-in pictures), and an edit that rewrites one slide about 8 seconds.
248
+
249
+ Planning calls (the outline, a change to a plan, the first step of an edit) ask the model for light reasoning,
250
+ because they choose and arrange rather than write. This made them several times faster and removed the runaway
251
+ answers described in [providers.md](providers.md). Writing the slides keeps the model's default.
216
252
 
217
253
  To keep cost down: reuse `cache` across calls, set `imageDensity: 'low'` or `'none'`, and use `planOutline`
218
254
  first so a person can correct the plan before content and pictures are produced.
@@ -72,6 +72,29 @@ or, on a brief for the planner, `textSize: 'large'` (1.25) or `'x-large'` (1.5).
72
72
  shows the model the reduced budgets, so it writes less.
73
73
  - Through `editDeck`, "make the text larger" sets the scale and rewrites only the slides whose text no longer
74
74
  fits.
75
+ - Table text and chart labels follow the scale where the manifest states their size (`bodyText.size`,
76
+ `headerText.size`, `text.size`), and a table cell then holds less.
77
+
78
+ ### Larger text for a whole template
79
+
80
+ The engine keeps the template's own font sizes. A template designed with small body text, such as 9 pt on a
81
+ 10-inch-wide slide, therefore gives decks with small body text. To change that once, for every deck, set
82
+ `textScale` on the manifest:
83
+
84
+ ```json
85
+ { "version": 1, "name": "corporate", "direction": "rtl", "textScale": 1.3, "patterns": [ ... ] }
86
+ ```
87
+
88
+ With `changeManifest`: `{ op: 'setManifest', textScale: 1.3 }`.
89
+
90
+ - It works exactly like a deck's `textScale` and applies to the same slots (body text: 12 pt and under by
91
+ default), but for every deck made from the template.
92
+ - Budgets shrink to match, in validation, in what the planner shows the model, and in `sampleDeck`: a slot
93
+ that held 240 characters holds 142 at 1.3. Render the sample deck after setting it to see that everything
94
+ still fits.
95
+ - A deck's own text size goes on top. With a template at 1.3, a deck at `large` (1.25) shows body text at about
96
+ 1.6 times the designed size.
97
+ - A slot that should keep its size can opt out with `"scales": false`.
75
98
 
76
99
  ## 4. Derived patterns
77
100
 
@@ -66,6 +66,7 @@ operations (`changeManifest`) or by instruction (`editManifest`), and verify it
66
66
  | `patterns` | `PatternDef[]` | yes | The reusable layouts. |
67
67
  | `language` | string | no | BCP 47 tag of the template's sample text. Decks default to it. |
68
68
  | `alignOnDirectionChange` | `"flip"` or `"keep"` | no | What happens to left and right paragraph alignment in a deck that reads the other way. `flip` (default) mirrors it. `keep` leaves it as designed. A text slot can override this. |
69
+ | `textScale` | number | no | Enlarges the body text of every deck made from this template, as a factor on the designed size: `1.3` turns 9 pt into about 12 pt. For a template whose body text is too small to read comfortably. Budgets shrink to match. See "Larger text" in [layout-freedom.md](layout-freedom.md). |
69
70
 
70
71
  ## `PatternDef`
71
72
 
package/docs/providers.md CHANGED
@@ -20,6 +20,8 @@ interface JsonRequest {
20
20
  system: string; // the system prompt: the role and the rules
21
21
  prompt: string; // the user message: the brief, the manifest's patterns, what to answer
22
22
  schema: JsonSchema; // the JSON Schema the answer must follow
23
+ maxOutputTokens?: number; // a ceiling on the answer's size in tokens, reasoning not counted; see "Runaway answers"
24
+ effort?: 'low'; // set on short structural requests: use light reasoning if your model has a setting for it
23
25
  images?: Array<{ mimeType: string; data: Uint8Array }>; // pictures to look at; only draftManifest sends them
24
26
  signal?: AbortSignal; // pass it to your HTTP call so work can be cancelled
25
27
  }
@@ -30,8 +32,8 @@ The contract:
30
32
  - Resolve with the **parsed** value (an object), not a string of JSON.
31
33
  - The value should follow `schema`. slotdeck checks what it gets back against the template's manifest and asks
32
34
  again when content breaks a limit, but it expects the overall shape the schema describes.
33
- - Reject (throw) when the call fails. slotdeck does not retry failed calls; add retries in your provider if you
34
- want them.
35
+ - Reject (throw) when the call fails. The planner does not retry failed calls; retries belong in the provider,
36
+ which knows which failures pass by themselves (see "Runaway answers" and the Gemini adapter below).
35
37
  - One call is one request and one answer. There is no conversation state: every request carries everything it
36
38
  needs.
37
39
 
@@ -117,6 +119,47 @@ Every top-level answer slotdeck asks for is a JSON object, so looking for the ou
117
119
  - `draftManifest` can send a picture of each slide (`images`). A provider for a model that cannot see may ignore
118
120
  them; the draft then relies on measurements alone.
119
121
 
122
+ ### Runaway answers: always set an output limit
123
+
124
+ Now and then a model runs away inside one text field of its answer: it keeps adding sentence fragments to a
125
+ single string until something stops it. This was seen with a planning request whose proper answer is about 200
126
+ tokens: the model wrote more than 27,000 tokens into one field. Without a limit such a call takes minutes, costs
127
+ tens of thousands of output tokens, and ends in a timeout. It is a matter of chance: the same request usually
128
+ works the next time.
129
+
130
+ It was tied to reasoning. With the model's default reasoning, a request for a text-only plan ran away in 6 of
131
+ 24 calls. With light reasoning it did not run away in 18 calls, and each took 4 seconds instead of about 35.
132
+ That is why planning requests carry `effort: 'low'`.
133
+
134
+ So a provider should do three things:
135
+
136
+ 1. **Use light reasoning when a request says `effort: 'low'`**, if your model has such a setting.
137
+ 2. **Limit the output of every call.** The planner passes `maxOutputTokens` with its requests: a generous
138
+ ceiling for that answer (for an outline of six slides, 1,360 tokens). Pass it to your model as its output
139
+ limit. If your model counts reasoning tokens against the same limit, add room for them. When a request
140
+ carries no estimate, use a default of your own rather than no limit.
141
+ 3. **Ask once more when an answer is cut off at the limit.** A cut-off answer is not valid JSON. Retry the same
142
+ request once, and throw if it is cut off again.
143
+
144
+ ```ts
145
+ async generateJson({ system, prompt, schema, maxOutputTokens, effort, signal }) {
146
+ const limit = (maxOutputTokens ?? 16_000) + 8_000; // room for reasoning, if your model counts it here
147
+ const reasoning = effort === 'low' ? 'low' : undefined; // whatever your model calls its light setting
148
+ for (let attempt = 1; attempt <= 2; attempt++) {
149
+ const answer = await callModel({ system, prompt, schema, limit, reasoning, signal });
150
+ if (!answer.cutOffAtLimit) return JSON.parse(answer.text);
151
+ }
152
+ throw new Error(`The model's answer was cut off at ${limit} tokens twice`);
153
+ }
154
+ ```
155
+
156
+ The included Gemini adapter does all three. Its limit is the request's estimate plus 8,192 tokens for reasoning
157
+ (24,576 when there is no estimate, or its own `maxOutputTokens` option when set). It sends a cut-off request
158
+ once more. It uses thinking level `low` for requests with `effort: 'low'`, unless `thinkingLevel` is set on the
159
+ adapter, which then applies to every request. It also sends a request once more after a server error (500 to
160
+ 504) or when the service's recitation filter blocks an ordinary answer, which happens at random; a bad key, a
161
+ quota or a bad request is not repeated.
162
+
120
163
  ### Timeouts and cancellation
121
164
 
122
165
  slotdeck passes `signal` through when the caller gave one. Your provider should pass it to its HTTP call, and
@@ -203,7 +246,9 @@ Options of both:
203
246
  | `fetch` | A custom `fetch`, for proxies and tests. |
204
247
  | `timeoutMs` | Gives up on a request after this long. Default 180000 (3 minutes). `0` waits for ever. |
205
248
 
206
- Only for `gemini`: `thinkingLevel` (`minimal`, `low`, `medium`, `high`) and `maxOutputTokens`.
249
+ Only for `gemini`: `thinkingLevel` (`minimal`, `low`, `medium`, `high`) and `maxOutputTokens`, the output limit
250
+ of a request with reasoning included. By default the limit is the request's own estimate plus 8,192 tokens, or
251
+ 24,576 when the request has none.
207
252
 
208
253
  Only for `geminiImages`: `imageSize` (`512`, `1K`, `2K`, `4K`; default `1K`) and `style`, a sentence appended to
209
254
  every prompt, such as a house photo style.
@@ -145,9 +145,12 @@ Every change is an object with `op`. `pattern` is a pattern id, `slot` a slot na
145
145
  #### `setManifest`
146
146
 
147
147
  ```ts
148
- { op: 'setManifest', name?: string, language?: string, direction?: 'ltr' | 'rtl', alignOnDirectionChange?: 'flip' | 'keep' }
148
+ { op: 'setManifest', name?: string, language?: string, direction?: 'ltr' | 'rtl', alignOnDirectionChange?: 'flip' | 'keep', textScale?: number | null }
149
149
  ```
150
150
 
151
+ `textScale` enlarges the body text of every deck made from the template (`1.3` for 30% larger); `null` or `1`
152
+ returns to the designed size.
153
+
151
154
  #### `addPattern`
152
155
 
153
156
  ```ts
@@ -296,6 +299,13 @@ Smaller table text and fewer columns (the cell budget is measured for you):
296
299
  { op: 'setSlot', pattern: 'table', slot: 'table', maxColumns: 5, bodyText: { size: 9 }, headerText: { size: 10, bold: true } }
297
300
  ```
298
301
 
302
+ Larger body text in every deck, for a template whose own body text is small (render the sample deck afterwards
303
+ to see that everything still fits):
304
+
305
+ ```ts
306
+ { op: 'setManifest', textScale: 1.3 }
307
+ ```
308
+
299
309
  Mirror alignment for right-to-left decks, but keep the line beside the logo where it is:
300
310
 
301
311
  ```ts
@@ -58,6 +58,7 @@ These do not stop rendering. `renderDeck` and the planner return them.
58
58
  | --- | --- | --- |
59
59
  | `No pattern of template "x" can be used: ...` | Nothing in the manifest can be filled with what was supplied. The reasons follow. | See the reasons below. |
60
60
  | `The model did not produce a usable outline: ...` | The model's answer did not have the expected shape. | Retry. If it repeats, check that your provider returns parsed JSON that follows the schema. |
61
+ | `Template "x" has no plain text pattern, so a text-only deck cannot be planned from it` | `textOnly` was asked for, and no pattern of the manifest is plain text. | Add a plain text pattern to the manifest (see [layout-freedom.md](layout-freedom.md)), or plan without `textOnly`. |
61
62
  | `The plan uses patterns that cannot be filled with what was supplied: a, b` | A plan was made with icons, assets or fields that are not passed now. | Pass the same `icons`, `assets`, `fields` and `layout` to every step. |
62
63
  | `Slide 3: pattern "x" needs icons, and no icon set was supplied` | The same, for an edit. | As above. |
63
64
 
@@ -129,6 +130,10 @@ Plain `Error`s from `renderDeck`. They mean the manifest and the pptx disagree,
129
130
  | `Gemini did not answer within 180 seconds` | The request hung. Retry, or raise `timeoutMs`. |
130
131
  | `Gemini did not return valid JSON: ...` | The model's text was not JSON. Retry. |
131
132
  | `Gemini interaction ended with status "..."` | The service did not complete the request; the reason follows when it gives one. |
133
+ | `Gemini's answer was cut off at the limit of 9552 tokens, twice. ...` | The model wrote until the output limit, twice in a row. It is nearly always a model running away inside one text field (see "Runaway answers" in [providers.md](providers.md)). Try again. If it keeps happening, set `thinkingLevel: 'low'`; if the limit is too small for a legitimately large answer, set `maxOutputTokens`. |
134
+ | `Gemini request failed (400): Request blocked due to copyright/recitation content` | The service's recitation filter tripped on an ordinary answer. It happens at random. The adapter already sent the request twice; try again. |
135
+ | `Gemini request failed (400): Thinking level ... is not supported for this model` | The `thinkingLevel` set on the adapter is not one the model accepts (`minimal` is refused by some models). Use `low`, or leave it unset. |
136
+ | `Gemini request failed (429): Your project has exceeded its monthly spending cap` | The key's project reached the spending cap set for it. Nothing works until the cap is raised in the provider's console. |
132
137
  | `Gemini returned no image` | The image model answered without a picture, often because the prompt was refused. |
133
138
 
134
139
  ## The result looks wrong
@@ -141,7 +146,10 @@ Plain `Error`s from `renderDeck`. They mean the manifest and the pptx disagree,
141
146
  | Cards are in reverse order in a right-to-left deck | The pattern's items are not declared as a row. | Add a `repeat` (`setRepeat`) or `sequences`. |
142
147
  | Hebrew or Arabic text is left-aligned | The manifest keeps alignment. | Set `alignOnDirectionChange: 'flip'` on the manifest or the slot. |
143
148
  | A font changes for another script | The template's font lacks that script. | Set a complex-script font in the template. |
149
+ | Body text looks small | The engine keeps the template's own font sizes, and the template was designed with small body text (9 pt on a 10-inch slide is common in decks exported from Google Slides). | For every deck: set `textScale` on the manifest, such as `1.3`. For one deck: `textSize: 'large'` on the brief, `text_size` in the chat tool, or `textScale` on the deck. See "Larger text" in [layout-freedom.md](layout-freedom.md). |
144
150
  | The planner never uses a pattern | It is not offered. | See "Reasons a pattern is not offered". Also check the pattern's `description`: the model chooses by it. |
151
+ | A deck that was asked to be "text only" has cards, tables or charts | The wish did not reach the planner as a wish about slides: only "no pictures" was passed (`imageDensity: 'none'`, or `image_density` in the chat tool), which still allows cards, tables and charts. | Set `textOnly: true` on the brief, or `text_only: true` in `plan_presentation`. See "Asking for a kind of slide" in [generating.md](generating.md). |
152
+ | A text-only deck uses one layout for every content slide | The template has one plain text pattern besides its opening and closing. | Add more plain text patterns to the manifest as derived patterns: a list without its photo, a quotation, two paragraphs side by side. |
145
153
  | A slide states a number or contact detail that nobody gave | The model invented it. | Figures are guarded only for patterns marked `figures` and for charts. State details in the brief and review the deck. |
146
154
 
147
155
  ## Known limits
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slotdeck",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Turn a PowerPoint template into new decks: a model plans the slides, the code clones the template's own slides and fills them.",
5
5
  "keywords": [
6
6
  "pptx",