@bendyline/docblocks-react 2.7.1 → 2.7.3

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 (38) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.txt +290 -48
  3. package/dist/{AiDiagrams-LFV4YU6W.js → AiDiagrams-QE3LVYFK.js} +2 -2
  4. package/dist/{AiEditorAssistant-GN2KXKZD.js → AiEditorAssistant-YLJ5OZK2.js} +203 -164
  5. package/dist/AiStudyDialog-4TQZZGU3.js +1150 -0
  6. package/dist/{ExportDialog-IMBVGRYG.js → ExportDialog-JRVZCG3J.js} +2 -2
  7. package/dist/{ExportToolbarControls-MUM6HM3K.js → ExportToolbarControls-A2YBKUUD.js} +46 -9
  8. package/dist/KeyboardShortcutsDialog-G5OBDTNR.js +145 -0
  9. package/dist/{PresentationMaker-W2JIBHRN.js → PresentationMaker-P2JP6UUX.js} +2 -2
  10. package/dist/{Settings-DuXk6baH.d.ts → Settings-ByNImjk5.d.ts} +13 -13
  11. package/dist/{SpeechToolbarControl-OSQS7NVY.js → SpeechToolbarControl-CLU7RB2G.js} +1 -1
  12. package/dist/{TextFromNarration-XYRAB6O2.js → TextFromNarration-NITJQ4IB.js} +1 -1
  13. package/dist/{WorkspaceSearchPanel-BKZJRPMY.js → WorkspaceSearchPanel-J6BKEZQJ.js} +1 -1
  14. package/dist/{chunk-X3DDTJKR.js → chunk-3CE5RH2B.js} +255 -17
  15. package/dist/{chunk-UG4G7PJ4.js → chunk-5O5YTOXG.js} +1 -1
  16. package/dist/{chunk-VO2JYYQZ.js → chunk-6JJOGV6M.js} +5 -13
  17. package/dist/{chunk-X437UAF7.js → chunk-6MSHXQNB.js} +7 -2
  18. package/dist/{chunk-BCQZTWE5.js → chunk-6PA7QBZT.js} +107 -24
  19. package/dist/chunk-ACQF63SB.js +136 -0
  20. package/dist/chunk-DICE7VBJ.js +77 -0
  21. package/dist/{chunk-EPOLZNFD.js → chunk-EOU2JWAQ.js} +186 -71
  22. package/dist/{chunk-G4MFB3IO.js → chunk-VG434562.js} +2 -70
  23. package/dist/chunk-XNT66JC2.js +168 -0
  24. package/dist/{chunk-BD54Z5XP.js → chunk-Y5OE3HJC.js} +4 -3
  25. package/dist/{chunk-NAH52736.js → chunk-YPIVCCTB.js} +969 -98
  26. package/dist/{chunk-W55E6TE2.js → chunk-ZMCQO57Q.js} +28 -23
  27. package/dist/editor.js +7 -5
  28. package/dist/export/index.d.ts +27 -8
  29. package/dist/export/index.js +4 -4
  30. package/dist/index.d.ts +36 -8
  31. package/dist/index.js +629 -446
  32. package/dist/run-export-OHRNT2TP.js +13 -0
  33. package/dist/settings/index.d.ts +1 -1
  34. package/dist/settings/index.js +1 -1
  35. package/package.json +9 -8
  36. package/src/styles/docblocks.css +968 -329
  37. package/dist/chunk-EA4BO6E4.js +0 -73
  38. package/dist/run-export-47X5FTYZ.js +0 -11
@@ -0,0 +1,136 @@
1
+ // src/DocBlocksShell/keyboard-shortcuts.ts
2
+ var SHELL_SHORTCUTS = Object.freeze([
3
+ // Must work mid-sentence; that is the entire point of a save shortcut.
4
+ { id: "save", key: "s", accel: true, label: "Ctrl+S", whileTyping: true, action: "Save now" },
5
+ // Find is mostly invoked from inside the document being searched.
6
+ {
7
+ id: "find",
8
+ key: "f",
9
+ accel: true,
10
+ label: "Ctrl+F",
11
+ whileTyping: true,
12
+ action: "Find in document"
13
+ },
14
+ // Jumping elsewhere is as likely mid-sentence as from the tree.
15
+ {
16
+ id: "quick-open",
17
+ key: "p",
18
+ accel: true,
19
+ label: "Ctrl+P",
20
+ whileTyping: true,
21
+ action: "Go to document"
22
+ },
23
+ {
24
+ id: "workspace-search",
25
+ key: "f",
26
+ accel: true,
27
+ shift: true,
28
+ label: "Ctrl+Shift+F",
29
+ whileTyping: true,
30
+ action: "Search the workspace"
31
+ },
32
+ // Deliberately NOT Cmd+B — Squisq owns that for Bold. Cmd+\ is the VS Code
33
+ // convention for toggling the side bar.
34
+ {
35
+ id: "toggle-sidebar",
36
+ key: "\\",
37
+ accel: true,
38
+ label: "Ctrl+\\",
39
+ whileTyping: true,
40
+ action: "Show or hide the file pane"
41
+ },
42
+ // Leaving a text field for the files is the point of this key — the search
43
+ // box included, which Ctrl+Shift+F has just focused. It only moves focus.
44
+ {
45
+ id: "focus-files",
46
+ key: "e",
47
+ accel: true,
48
+ shift: true,
49
+ label: "Ctrl+Shift+E",
50
+ whileTyping: true,
51
+ action: "Move focus to the files"
52
+ },
53
+ // Starting another document is most often done from inside one. A dialog's
54
+ // own name box keeps these keys, so a second prompt can't stack.
55
+ {
56
+ id: "new-document",
57
+ key: "n",
58
+ accel: true,
59
+ label: "Ctrl+N",
60
+ whileEditing: true,
61
+ action: "New document",
62
+ browserReserved: true
63
+ },
64
+ {
65
+ id: "new-folder",
66
+ key: "n",
67
+ accel: true,
68
+ shift: true,
69
+ label: "Ctrl+Shift+N",
70
+ whileEditing: true,
71
+ action: "New folder",
72
+ browserReserved: true
73
+ },
74
+ // F1 is the help key and works mid-sentence; `?` is the web convention but
75
+ // is ordinary text while typing. Both open the same sheet.
76
+ {
77
+ id: "shortcuts",
78
+ key: "F1",
79
+ accel: false,
80
+ label: "F1",
81
+ whileTyping: true,
82
+ action: "Show keyboard shortcuts"
83
+ },
84
+ { id: "shortcuts", key: "?", accel: false, shift: true, label: "?" },
85
+ // Ctrl+, (settings) is deliberately absent: AppMenu owns that dialog's state
86
+ // internally and exposes no way to open it. Binding the key without first
87
+ // giving AppMenu a controlled `settingsOpen` prop would ship a dead shortcut.
88
+ { id: "close-overlay", key: "Escape", accel: false, label: "Esc", whileTyping: true }
89
+ ]);
90
+ function isTypingTarget(target) {
91
+ if (!(target instanceof HTMLElement)) return false;
92
+ if (isDocumentEditorTarget(target)) return true;
93
+ const tag = target.tagName;
94
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
95
+ }
96
+ function isDocumentEditorTarget(target) {
97
+ return target.isContentEditable || target.closest(".monaco-editor") !== null;
98
+ }
99
+ function matchShortcut(event, _layoutMode) {
100
+ return SHELL_SHORTCUTS.find((binding) => bindingMatches(binding, event))?.id ?? null;
101
+ }
102
+ function bindingMatches(binding, event) {
103
+ if (event.altKey) return false;
104
+ const accel = event.ctrlKey || event.metaKey;
105
+ const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
106
+ if (binding.accel !== accel) return false;
107
+ if ((binding.shift ?? false) !== event.shiftKey) return false;
108
+ if (binding.key !== key) return false;
109
+ if (binding.whileTyping === true || !isTypingTarget(event.target)) return true;
110
+ return binding.whileEditing === true && event.target instanceof HTMLElement && isDocumentEditorTarget(event.target);
111
+ }
112
+ function isFindShortcut(event) {
113
+ const binding = SHELL_SHORTCUTS.find((candidate) => candidate.id === "find");
114
+ return binding !== void 0 && bindingMatches(binding, event);
115
+ }
116
+ function findShortcutLabel() {
117
+ return shortcutLabel("find", isApplePlatform());
118
+ }
119
+ function isApplePlatform() {
120
+ if (typeof navigator === "undefined") return false;
121
+ return /Mac|iPhone|iPad|iPod/u.test(navigator.platform || navigator.userAgent);
122
+ }
123
+ function shortcutLabel(id, isApple) {
124
+ const binding = SHELL_SHORTCUTS.find((candidate) => candidate.id === id);
125
+ if (!binding) return "";
126
+ return isApple ? binding.label.replace("Ctrl+", "\u2318") : binding.label;
127
+ }
128
+
129
+ export {
130
+ SHELL_SHORTCUTS,
131
+ matchShortcut,
132
+ isFindShortcut,
133
+ findShortcutLabel,
134
+ isApplePlatform,
135
+ shortcutLabel
136
+ };
@@ -0,0 +1,77 @@
1
+ // src/icons.tsx
2
+ import {
3
+ ArrowDownAZ,
4
+ Clock,
5
+ Download,
6
+ Ellipsis,
7
+ FilePlus,
8
+ Folder,
9
+ FolderOpen,
10
+ FolderPlus,
11
+ FolderTree,
12
+ PanelLeft,
13
+ Pin,
14
+ Search,
15
+ Settings2
16
+ } from "lucide-react";
17
+ import { jsx } from "react/jsx-runtime";
18
+ var ICON_SIZE = "1.15em";
19
+ var STROKE_WIDTH = 1.75;
20
+ function ShellIcon({ icon: Icon, size = ICON_SIZE }) {
21
+ return /* @__PURE__ */ jsx(Icon, { size, strokeWidth: STROKE_WIDTH, "aria-hidden": "true", focusable: "false" });
22
+ }
23
+ function NewFileIcon() {
24
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: FilePlus });
25
+ }
26
+ function NewFolderIcon() {
27
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: FolderPlus });
28
+ }
29
+ function OpenFolderIcon() {
30
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: FolderOpen });
31
+ }
32
+ function SortByNameIcon() {
33
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: ArrowDownAZ });
34
+ }
35
+ function SortByLastModifiedIcon() {
36
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Clock });
37
+ }
38
+ function FolderIcon() {
39
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Folder });
40
+ }
41
+ function MoreIcon() {
42
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Ellipsis });
43
+ }
44
+ function PinIcon() {
45
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Pin });
46
+ }
47
+ function WorkspaceIcon() {
48
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Settings2 });
49
+ }
50
+ function SplitViewIcon() {
51
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: PanelLeft, size: 16 });
52
+ }
53
+ function SearchIcon() {
54
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Search });
55
+ }
56
+ function FilesIcon() {
57
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: FolderTree });
58
+ }
59
+ function BackupIcon() {
60
+ return /* @__PURE__ */ jsx(ShellIcon, { icon: Download });
61
+ }
62
+
63
+ export {
64
+ NewFileIcon,
65
+ NewFolderIcon,
66
+ OpenFolderIcon,
67
+ SortByNameIcon,
68
+ SortByLastModifiedIcon,
69
+ FolderIcon,
70
+ MoreIcon,
71
+ PinIcon,
72
+ WorkspaceIcon,
73
+ SplitViewIcon,
74
+ SearchIcon,
75
+ FilesIcon,
76
+ BackupIcon
77
+ };
@@ -1,6 +1,7 @@
1
1
  import {
2
+ AiReportButton,
2
3
  chatOnce
3
- } from "./chunk-NAH52736.js";
4
+ } from "./chunk-YPIVCCTB.js";
4
5
  import {
5
6
  unavailableSentence,
6
7
  useAiStatus
@@ -78,6 +79,7 @@ async function refinePresentationPlan(ai, plan, options = {}) {
78
79
  const passage = plan.sourceText.slice(beat.sourceStart, beat.sourceEnd);
79
80
  if (passage.length > 2400) {
80
81
  retained++;
82
+ options.onBeat?.(index, null);
81
83
  continue;
82
84
  }
83
85
  let replacement = null;
@@ -117,6 +119,7 @@ async function refinePresentationPlan(ai, plan, options = {}) {
117
119
  beats[index] = replacement;
118
120
  refined.push(index);
119
121
  } else retained++;
122
+ options.onBeat?.(index, replacement);
120
123
  }
121
124
  options.onProgress?.(plan.beats.length, plan.beats.length);
122
125
  return {
@@ -136,15 +139,49 @@ var LABELS = {
136
139
  steps: "Steps diagram",
137
140
  image: "Image"
138
141
  };
142
+ function sectionAt(sections, offset) {
143
+ return sections.find((item) => offset >= item.sourceStart && offset < item.sourceEnd);
144
+ }
145
+ function withSuggestion(current, sections, sourceText, item) {
146
+ const parent = sectionAt(sections, item.sourceStart);
147
+ if (!parent) return current;
148
+ const source = sourceText.slice(item.sourceStart, item.sourceEnd);
149
+ const existing = current.blocks.find((block) => block.blockId === parent.blockId);
150
+ const target = {
151
+ ...existing ?? { blockId: parent.blockId },
152
+ ai: [
153
+ ...(existing?.ai ?? []).filter((entry) => entry.source !== source),
154
+ { source, layout: item.layout, headline: item.headline, points: [...item.points] }
155
+ ].slice(-80)
156
+ };
157
+ return {
158
+ ...current,
159
+ blocks: [...current.blocks.filter((block) => block.blockId !== parent.blockId), target]
160
+ };
161
+ }
139
162
  function PresentationMaker({
140
163
  ai,
141
164
  onClose
142
165
  }) {
143
166
  return createPortal(/* @__PURE__ */ jsx(PresentationDialog, { ai, onClose }), document.body);
144
167
  }
168
+ function AiSuggestionReport({ ai, hints }) {
169
+ const status = useAiStatus(ai);
170
+ const suggestions = hints.blocks.flatMap((block) => block.ai ?? []);
171
+ if (suggestions.length === 0) return null;
172
+ return /* @__PURE__ */ jsx(
173
+ AiReportButton,
174
+ {
175
+ feature: "Suggest slides",
176
+ getOutput: () => suggestions.map((entry) => [entry.headline, ...entry.points.map((point) => `- ${point}`)].join("\n")).join("\n\n"),
177
+ model: status?.kind === "ready" ? status.model?.label ?? null : null
178
+ }
179
+ );
180
+ }
145
181
  function AiRefineButton({
146
182
  ai,
147
183
  disabled,
184
+ buttonRef,
148
185
  onRefine
149
186
  }) {
150
187
  const status = useAiStatus(ai);
@@ -152,6 +189,7 @@ function AiRefineButton({
152
189
  return /* @__PURE__ */ jsx(
153
190
  "button",
154
191
  {
192
+ ref: buttonRef,
155
193
  type: "button",
156
194
  disabled: disabled || !ready,
157
195
  title: ready ? "Suggest layouts and shorter slide wording from the current text." : unavailableSentence(status),
@@ -173,6 +211,9 @@ function PresentationDialog({ ai, onClose }) {
173
211
  const [notice, setNotice] = useState("");
174
212
  const [progress, setProgress] = useState(null);
175
213
  const controller = useRef(null);
214
+ const follow = useRef(false);
215
+ const suggestButton = useRef(null);
216
+ const stopButton = useRef(null);
176
217
  useEffect(() => {
177
218
  let disposed = false;
178
219
  const parsed = markdownToDoc(parseMarkdown(markdownSource));
@@ -187,6 +228,14 @@ function PresentationDialog({ ai, onClose }) {
187
228
  controller.current?.abort();
188
229
  };
189
230
  }, [markdownSource, workspaceContainer]);
231
+ const running = progress !== null;
232
+ const wasRunning = useRef(false);
233
+ useEffect(() => {
234
+ if (wasRunning.current === running) return;
235
+ wasRunning.current = running;
236
+ if (document.activeElement === null || document.activeElement === document.body)
237
+ (running ? stopButton.current : suggestButton.current)?.focus();
238
+ }, [running]);
190
239
  const doc = loaded?.source === markdownSource ? loaded.doc : null;
191
240
  const generated = useMemo(() => {
192
241
  if (!doc) return null;
@@ -219,6 +268,15 @@ function PresentationDialog({ ai, onClose }) {
219
268
  };
220
269
  }
221
270
  }, [doc, plan, preview?.activeTheme]);
271
+ const aiEntries = useMemo(() => {
272
+ const entries = /* @__PURE__ */ new Map();
273
+ for (const block of hints.blocks)
274
+ entries.set(
275
+ block.blockId,
276
+ new Map((block.ai ?? []).map((entry) => [entry.source, entry.layout]))
277
+ );
278
+ return entries;
279
+ }, [hints]);
222
280
  const selectedIndex = Math.max(
223
281
  0,
224
282
  plan?.beats.findIndex(
@@ -226,9 +284,7 @@ function PresentationDialog({ ai, onClose }) {
226
284
  ) ?? -1
227
285
  );
228
286
  const beat = plan?.beats[selectedIndex];
229
- const section = sections.find(
230
- (item) => beat && beat.sourceStart >= item.sourceStart && beat.sourceStart < item.sourceEnd
231
- );
287
+ const section = beat && sectionAt(sections, beat.sourceStart);
232
288
  const hint = hints.blocks.find((item) => item.blockId === section?.blockId);
233
289
  const visual = projection?.blocks.find((block) => block.id === beat?.id);
234
290
  const updateHint = (patch) => {
@@ -250,52 +306,47 @@ function PresentationDialog({ ai, onClose }) {
250
306
  setHints(next);
251
307
  };
252
308
  const refine = async () => {
253
- if (!ai || !plan || progress) return;
309
+ if (!ai || !plan || controller.current) return;
254
310
  const run = new AbortController();
255
311
  controller.current = run;
312
+ follow.current = true;
313
+ const total = plan.beats.length;
314
+ const outcome = { done: 0, refined: 0, retained: 0 };
256
315
  setError(null);
257
316
  setNotice("");
258
317
  try {
259
- const result = await refinePresentationPlan(ai, plan, {
318
+ await refinePresentationPlan(ai, plan, {
260
319
  signal: run.signal,
261
- onProgress: (done, total) => setProgress(`Suggesting slide ${Math.min(done + 1, total)} of ${total}\u2026`)
262
- });
263
- if (!run.signal.aborted) {
264
- setHints((current) => {
265
- const blocks = current.blocks.map((item) => ({
266
- ...item,
267
- ai: item.ai ? [...item.ai] : void 0
268
- }));
269
- for (const index of result.refined) {
270
- const item = result.plan.beats[index];
271
- const parent = sections.find(
272
- (s) => item.sourceStart >= s.sourceStart && item.sourceStart < s.sourceEnd
273
- );
274
- if (!parent) continue;
275
- let target = blocks.find((b) => b.blockId === parent.blockId);
276
- if (!target) {
277
- target = { blockId: parent.blockId, ai: void 0 };
278
- blocks.push(target);
279
- }
280
- const source = plan.sourceText.slice(item.sourceStart, item.sourceEnd);
281
- target.ai = [
282
- ...(target.ai ?? []).filter((v) => v.source !== source),
283
- {
284
- source,
285
- layout: item.layout,
286
- headline: item.headline,
287
- points: item.points
288
- }
289
- ].slice(-80);
320
+ onProgress: (done) => {
321
+ outcome.done = done;
322
+ const next = plan.beats[done];
323
+ setProgress({
324
+ done,
325
+ total,
326
+ active: next ? { sourceStart: next.sourceStart, sourceEnd: next.sourceEnd } : null
327
+ });
328
+ if (next && follow.current) setSelectedSourceStart(next.sourceStart);
329
+ },
330
+ // Each accepted slide reaches the list and preview as soon as it
331
+ // resolves, so stopping early keeps everything suggested so far.
332
+ onBeat: (_index, item) => {
333
+ if (!item) {
334
+ outcome.retained++;
335
+ return;
290
336
  }
291
- return { ...current, blocks };
292
- });
293
- setNotice(
294
- result.retained ? `Kept automatic summaries for ${result.retained} slides because the AI suggestions could not be validated.` : "AI summaries are ready to review. They will be saved with their source passages."
295
- );
296
- }
337
+ outcome.refined++;
338
+ setHints((current) => withSuggestion(current, sections, plan.sourceText, item));
339
+ }
340
+ });
341
+ setNotice(
342
+ outcome.retained ? `Kept automatic summaries for ${outcome.retained} slides because the AI suggestions could not be validated.` : "AI summaries are ready to review. They will be saved with their source passages."
343
+ );
297
344
  } catch (cause) {
298
345
  if (!run.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
346
+ else if (controller.current === run)
347
+ setNotice(
348
+ `Stopped after ${outcome.done} of ${total} slides. ${outcome.refined === 0 ? "No slides changed." : outcome.refined === 1 ? "The one AI suggestion is kept." : `The ${outcome.refined} AI suggestions are kept.`}`
349
+ );
299
350
  } finally {
300
351
  if (controller.current === run) {
301
352
  controller.current = null;
@@ -304,7 +355,7 @@ function PresentationDialog({ ai, onClose }) {
304
355
  }
305
356
  };
306
357
  const apply = () => {
307
- if (!doc || !plan || progress) return;
358
+ if (!doc || !plan || running) return;
308
359
  try {
309
360
  const next = setFrontmatterValues(markdownSource, {
310
361
  [PRESENTATION_HINTS_KEY]: serializePresentationHints(hints),
@@ -328,10 +379,11 @@ function PresentationDialog({ ai, onClose }) {
328
379
  onClose,
329
380
  closeOnBackdrop: false,
330
381
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
331
- progress ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => controller.current?.abort(), children: "Cancel suggestions" }) : /* @__PURE__ */ jsx(
382
+ /* @__PURE__ */ jsx(
332
383
  "button",
333
384
  {
334
385
  type: "button",
386
+ disabled: running,
335
387
  onClick: () => {
336
388
  setHints(defaultPresentationHints());
337
389
  setSelectedSourceStart(0);
@@ -341,8 +393,46 @@ function PresentationDialog({ ai, onClose }) {
341
393
  children: "Reset design"
342
394
  }
343
395
  ),
344
- ai && /* @__PURE__ */ jsx(AiRefineButton, { ai, disabled: !plan || !!progress, onRefine: () => void refine() }),
345
- /* @__PURE__ */ jsx("button", { type: "button", disabled: !plan || !!progress || !!error, onClick: apply, children: "Use dynamic slides" })
396
+ ai && !progress && /* @__PURE__ */ jsx(
397
+ AiRefineButton,
398
+ {
399
+ ai,
400
+ buttonRef: suggestButton,
401
+ disabled: !plan,
402
+ onRefine: () => void refine()
403
+ }
404
+ ),
405
+ ai && progress && /* @__PURE__ */ jsxs(Fragment, { children: [
406
+ /* @__PURE__ */ jsxs("div", { className: "db-presentation-progress", role: "status", children: [
407
+ /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
408
+ /* @__PURE__ */ jsxs("span", { children: [
409
+ "Suggesting slide ",
410
+ Math.min(progress.done + 1, progress.total),
411
+ " of ",
412
+ progress.total,
413
+ "\u2026"
414
+ ] }),
415
+ /* @__PURE__ */ jsx(
416
+ "progress",
417
+ {
418
+ max: progress.total,
419
+ value: progress.done,
420
+ "aria-label": "Slides suggested so far"
421
+ }
422
+ )
423
+ ] }),
424
+ /* @__PURE__ */ jsx(
425
+ "button",
426
+ {
427
+ ref: stopButton,
428
+ type: "button",
429
+ title: "Stop suggesting. Slides already suggested keep their new wording.",
430
+ onClick: () => controller.current?.abort(),
431
+ children: "Stop"
432
+ }
433
+ )
434
+ ] }),
435
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: !plan || running || !!error, onClick: apply, children: "Use dynamic slides" })
346
436
  ] }),
347
437
  children: [
348
438
  /* @__PURE__ */ jsx("p", { className: "db-presentation-intro", children: "Design how your text is summarized for presentation in slides and video. Note that your document text and narration will remain unchanged." }),
@@ -353,7 +443,7 @@ function PresentationDialog({ ai, onClose }) {
353
443
  "select",
354
444
  {
355
445
  value: hints.density,
356
- disabled: !!progress,
446
+ disabled: running,
357
447
  onChange: (event) => setHints({ ...hints, density: event.target.value }),
358
448
  children: [
359
449
  /* @__PURE__ */ jsx("option", { value: "concise", children: "Concise \u2014 shorter passages per slide" }),
@@ -369,7 +459,7 @@ function PresentationDialog({ ai, onClose }) {
369
459
  {
370
460
  type: "checkbox",
371
461
  checked: hints.inbetweens,
372
- disabled: !!progress,
462
+ disabled: running,
373
463
  onChange: (event) => setHints({ ...hints, inbetweens: event.target.checked })
374
464
  }
375
465
  ),
@@ -377,28 +467,53 @@ function PresentationDialog({ ai, onClose }) {
377
467
  "Add supporting visual slides where useful"
378
468
  ] })
379
469
  ] }),
380
- /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", children: progress ?? (plan && projection?.doc ? `${plan.beats.length} slides \xB7 ${projection.doc.duration.toFixed(1)} seconds \xB7 ${doc?.presentationNarration ? "Follows narration" : "Estimated pacing \u2014 add narration to synchronize"}` : "Preparing dynamic slides\u2026") }),
470
+ /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", children: plan && projection?.doc ? `${plan.beats.length} slides \xB7 ${projection.doc.duration.toFixed(1)} seconds \xB7 ${doc?.presentationNarration ? "Follows narration" : "Estimated pacing \u2014 add narration to synchronize"}` : "Preparing dynamic slides\u2026" }),
381
471
  (error || generated?.error || projection?.error) && /* @__PURE__ */ jsx("p", { role: "alert", children: error ?? generated?.error ?? projection?.error }),
382
- notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
472
+ notice && /* @__PURE__ */ jsxs("p", { role: "status", children: [
473
+ notice,
474
+ ai && !running && /* @__PURE__ */ jsxs(Fragment, { children: [
475
+ " ",
476
+ /* @__PURE__ */ jsx(AiSuggestionReport, { ai, hints })
477
+ ] })
478
+ ] }),
383
479
  plan && beat && section && /* @__PURE__ */ jsxs("div", { className: "db-presentation-workspace", children: [
384
- /* @__PURE__ */ jsx("nav", { className: "db-presentation-beats", "aria-label": "Presentation slides", children: plan.beats.map((item, index) => /* @__PURE__ */ jsxs(
385
- "button",
386
- {
387
- type: "button",
388
- "aria-current": index === selectedIndex ? "step" : void 0,
389
- title: item.headline,
390
- onClick: () => setSelectedSourceStart(item.sourceStart),
391
- children: [
392
- /* @__PURE__ */ jsxs("span", { className: "db-presentation-beat-title", children: [
393
- index + 1,
394
- ". ",
395
- item.headline
396
- ] }),
397
- /* @__PURE__ */ jsx("small", { children: LABELS[item.layout] })
398
- ]
399
- },
400
- item.id
401
- )) }),
480
+ /* @__PURE__ */ jsx("nav", { className: "db-presentation-beats", "aria-label": "Presentation slides", children: plan.beats.map((item, index) => {
481
+ const owner = sectionAt(sections, item.sourceStart);
482
+ const layout = owner && aiEntries.get(owner.blockId)?.get(plan.sourceText.slice(item.sourceStart, item.sourceEnd));
483
+ const suggested = layout !== void 0 && (layout !== "image" || !!item.imageSrc);
484
+ const active = progress?.active;
485
+ const pending = !!active && active.sourceStart === item.sourceStart && active.sourceEnd === item.sourceEnd;
486
+ return /* @__PURE__ */ jsxs(
487
+ "button",
488
+ {
489
+ type: "button",
490
+ "aria-current": index === selectedIndex ? "step" : void 0,
491
+ title: item.headline,
492
+ onClick: () => {
493
+ follow.current = false;
494
+ setSelectedSourceStart(item.sourceStart);
495
+ },
496
+ children: [
497
+ /* @__PURE__ */ jsxs("span", { className: "db-presentation-beat-title", children: [
498
+ index + 1,
499
+ ". ",
500
+ item.headline
501
+ ] }),
502
+ /* @__PURE__ */ jsxs("span", { className: "db-presentation-beat-tags", children: [
503
+ /* @__PURE__ */ jsx("small", { children: LABELS[item.layout] }),
504
+ pending ? /* @__PURE__ */ jsxs("small", { className: "db-presentation-beat-ai", children: [
505
+ /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
506
+ "Suggesting\u2026"
507
+ ] }) : suggested ? /* @__PURE__ */ jsxs("small", { className: "db-presentation-beat-ai", children: [
508
+ /* @__PURE__ */ jsx("span", { className: "db-visually-hidden", children: "Suggested by " }),
509
+ "AI"
510
+ ] }) : null
511
+ ] })
512
+ ]
513
+ },
514
+ item.id
515
+ );
516
+ }) }),
402
517
  /* @__PURE__ */ jsxs("div", { className: "db-presentation-detail", children: [
403
518
  /* @__PURE__ */ jsx(
404
519
  "div",
@@ -425,7 +540,7 @@ function PresentationDialog({ ai, onClose }) {
425
540
  "select",
426
541
  {
427
542
  value: hint?.layout ?? "",
428
- disabled: !!progress,
543
+ disabled: running,
429
544
  onChange: (event) => updateHint({
430
545
  layout: event.target.value || void 0
431
546
  }),
@@ -444,7 +559,7 @@ function PresentationDialog({ ai, onClose }) {
444
559
  "select",
445
560
  {
446
561
  value: hint?.inbetweens === void 0 ? "" : String(hint.inbetweens),
447
- disabled: !!progress,
562
+ disabled: running,
448
563
  onChange: (event) => updateHint({
449
564
  inbetweens: event.target.value === "" ? void 0 : event.target.value === "true"
450
565
  }),
@@ -474,7 +589,7 @@ function PresentationDialog({ ai, onClose }) {
474
589
  {
475
590
  defaultValue: beat.headline,
476
591
  maxLength: 110,
477
- disabled: !!progress,
592
+ disabled: running,
478
593
  onBlur: (event) => updateHint({
479
594
  wording: {
480
595
  ...hint?.wording?.source === section.text ? hint.wording : {},
@@ -493,7 +608,7 @@ function PresentationDialog({ ai, onClose }) {
493
608
  {
494
609
  rows: 3,
495
610
  defaultValue: beat.points.join("\n"),
496
- disabled: !!progress,
611
+ disabled: running,
497
612
  onBlur: (event) => updateHint({
498
613
  wording: {
499
614
  ...hint?.wording?.source === section.text ? hint.wording : {},