@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.
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.txt +290 -48
- package/dist/{AiDiagrams-LFV4YU6W.js → AiDiagrams-QE3LVYFK.js} +2 -2
- package/dist/{AiEditorAssistant-GN2KXKZD.js → AiEditorAssistant-YLJ5OZK2.js} +203 -164
- package/dist/AiStudyDialog-4TQZZGU3.js +1150 -0
- package/dist/{ExportDialog-IMBVGRYG.js → ExportDialog-JRVZCG3J.js} +2 -2
- package/dist/{ExportToolbarControls-MUM6HM3K.js → ExportToolbarControls-A2YBKUUD.js} +46 -9
- package/dist/KeyboardShortcutsDialog-G5OBDTNR.js +145 -0
- package/dist/{PresentationMaker-W2JIBHRN.js → PresentationMaker-P2JP6UUX.js} +2 -2
- package/dist/{Settings-DuXk6baH.d.ts → Settings-ByNImjk5.d.ts} +13 -13
- package/dist/{SpeechToolbarControl-OSQS7NVY.js → SpeechToolbarControl-CLU7RB2G.js} +1 -1
- package/dist/{TextFromNarration-XYRAB6O2.js → TextFromNarration-NITJQ4IB.js} +1 -1
- package/dist/{WorkspaceSearchPanel-BKZJRPMY.js → WorkspaceSearchPanel-J6BKEZQJ.js} +1 -1
- package/dist/{chunk-X3DDTJKR.js → chunk-3CE5RH2B.js} +255 -17
- package/dist/{chunk-UG4G7PJ4.js → chunk-5O5YTOXG.js} +1 -1
- package/dist/{chunk-VO2JYYQZ.js → chunk-6JJOGV6M.js} +5 -13
- package/dist/{chunk-X437UAF7.js → chunk-6MSHXQNB.js} +7 -2
- package/dist/{chunk-BCQZTWE5.js → chunk-6PA7QBZT.js} +107 -24
- package/dist/chunk-ACQF63SB.js +136 -0
- package/dist/chunk-DICE7VBJ.js +77 -0
- package/dist/{chunk-EPOLZNFD.js → chunk-EOU2JWAQ.js} +186 -71
- package/dist/{chunk-G4MFB3IO.js → chunk-VG434562.js} +2 -70
- package/dist/chunk-XNT66JC2.js +168 -0
- package/dist/{chunk-BD54Z5XP.js → chunk-Y5OE3HJC.js} +4 -3
- package/dist/{chunk-NAH52736.js → chunk-YPIVCCTB.js} +969 -98
- package/dist/{chunk-W55E6TE2.js → chunk-ZMCQO57Q.js} +28 -23
- package/dist/editor.js +7 -5
- package/dist/export/index.d.ts +27 -8
- package/dist/export/index.js +4 -4
- package/dist/index.d.ts +36 -8
- package/dist/index.js +629 -446
- package/dist/run-export-OHRNT2TP.js +13 -0
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js +1 -1
- package/package.json +9 -8
- package/src/styles/docblocks.css +968 -329
- package/dist/chunk-EA4BO6E4.js +0 -73
- 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-
|
|
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.
|
|
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 ||
|
|
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
|
-
|
|
318
|
+
await refinePresentationPlan(ai, plan, {
|
|
260
319
|
signal: run.signal,
|
|
261
|
-
onProgress: (done
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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 ||
|
|
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
|
-
|
|
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(
|
|
345
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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__ */
|
|
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) =>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
611
|
+
disabled: running,
|
|
497
612
|
onBlur: (event) => updateHint({
|
|
498
613
|
wording: {
|
|
499
614
|
...hint?.wording?.source === section.text ? hint.wording : {},
|