@bendyline/docblocks-react 2.7.2 → 2.7.4

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 (39) hide show
  1. package/THIRD_PARTY_NOTICES.txt +215 -23
  2. package/dist/{AiDiagrams-LFV4YU6W.js → AiDiagrams-QZMJNGDT.js} +3 -3
  3. package/dist/{AiEditorAssistant-GN2KXKZD.js → AiEditorAssistant-4HFFGVU4.js} +205 -166
  4. package/dist/AiStudyDialog-P3E7PJV5.js +1150 -0
  5. package/dist/{AudioExportDialog-F23QXNPB.js → AudioExportDialog-IOYIEAER.js} +1 -1
  6. package/dist/{DocumentConflictDialog-5LRVIGXS.js → DocumentConflictDialog-DZ2BV2IH.js} +1 -1
  7. package/dist/ExportDialog-LGWBXIWN.js +9 -0
  8. package/dist/{ExportToolbarControls-2FEIHNMW.js → ExportToolbarControls-HOV424TO.js} +114 -14
  9. package/dist/{GitUI-RCWUZVS6.js → GitUI-F3CNBURE.js} +1 -1
  10. package/dist/{KeyboardShortcutsDialog-NOD4P25G.js → KeyboardShortcutsDialog-HVKDFP5Z.js} +2 -2
  11. package/dist/{PresentationMaker-ITNP5SDO.js → PresentationMaker-MUMFAXEI.js} +3 -3
  12. package/dist/{QuickOpenDialog-2KK4SEBU.js → QuickOpenDialog-ENVFGIHS.js} +1 -1
  13. package/dist/{Settings-RDYvDDI0.d.ts → Settings-CfhY489G.d.ts} +3 -5
  14. package/dist/{ShareDialog-XUA2OVGI.js → ShareDialog-PF7D7ORP.js} +1 -1
  15. package/dist/{SpeechToolbarControl-CLU7RB2G.js → SpeechToolbarControl-KNI2C4JJ.js} +1 -1
  16. package/dist/{TextFromNarration-NITJQ4IB.js → TextFromNarration-EYOMW2IW.js} +1 -1
  17. package/dist/{chunk-HRWEGPTY.js → chunk-7CSBRAMT.js} +109 -24
  18. package/dist/{chunk-NAH52736.js → chunk-7V34VUK6.js} +969 -98
  19. package/dist/{chunk-K7XJDJUJ.js → chunk-ACQF63SB.js} +14 -4
  20. package/dist/{chunk-X3DDTJKR.js → chunk-D4HIOS7E.js} +256 -18
  21. package/dist/{chunk-BLUEZG7T.js → chunk-FBPCURCS.js} +9 -2
  22. package/dist/{chunk-Y5OE3HJC.js → chunk-GFK4TCKN.js} +11 -5
  23. package/dist/chunk-H3BK5MK6.js +34 -0
  24. package/dist/{chunk-6B7G33D5.js → chunk-KBUHWZCF.js} +1 -1
  25. package/dist/{chunk-SSB43524.js → chunk-PNZFIIWW.js} +91 -12
  26. package/dist/{chunk-56KO2LYN.js → chunk-VJPGZCFS.js} +4 -0
  27. package/dist/chunk-XNT66JC2.js +168 -0
  28. package/dist/editor.js +4 -4
  29. package/dist/export/index.d.ts +19 -4
  30. package/dist/export/index.js +4 -4
  31. package/dist/index.d.ts +19 -5
  32. package/dist/index.js +858 -557
  33. package/dist/{run-export-CS3HRNAZ.js → run-export-SPNNH32M.js} +1 -1
  34. package/dist/settings/index.d.ts +1 -1
  35. package/dist/settings/index.js +2 -2
  36. package/package.json +8 -8
  37. package/src/styles/docblocks.css +526 -63
  38. package/dist/ExportDialog-JRVZCG3J.js +0 -9
  39. package/dist/chunk-YBEYTVU2.js +0 -12
@@ -1,9 +1,14 @@
1
1
  import {
2
2
  AiDiagramDialog
3
- } from "./chunk-X3DDTJKR.js";
3
+ } from "./chunk-D4HIOS7E.js";
4
+ import {
5
+ AiGenerationStatus,
6
+ STUDY_AID_KINDS,
7
+ STUDY_AID_LABELS
8
+ } from "./chunk-XNT66JC2.js";
4
9
  import {
5
10
  useConfirmDialog
6
- } from "./chunk-6B7G33D5.js";
11
+ } from "./chunk-KBUHWZCF.js";
7
12
  import {
8
13
  useMenuKeyboard
9
14
  } from "./chunk-GMA4KGGO.js";
@@ -11,6 +16,7 @@ import {
11
16
  AI_INSTRUCTION_CHARACTERS,
12
17
  AI_REVIEW_DOCUMENT_CHARACTERS,
13
18
  AI_REWRITE_SELECTION_CHARACTERS,
19
+ AiReportButton,
14
20
  aiReviewReplyWasCutOff,
15
21
  applyAiReviewFinding,
16
22
  buildDraftContinuation,
@@ -19,76 +25,34 @@ import {
19
25
  findUniqueExcerpt,
20
26
  parseAiReviewResponse,
21
27
  parseTruncatedAiReviewResponse,
22
- sanitizeGeneratedMarkdown
23
- } from "./chunk-NAH52736.js";
28
+ reviewDocumentCharacters,
29
+ sanitizeGeneratedMarkdown,
30
+ withoutPictureFindings
31
+ } from "./chunk-7V34VUK6.js";
24
32
  import {
25
33
  unavailableSentence,
26
34
  useAiStatus
27
35
  } from "./chunk-K4I3NJFL.js";
28
36
  import {
29
37
  Dialog
30
- } from "./chunk-56KO2LYN.js";
38
+ } from "./chunk-VJPGZCFS.js";
31
39
 
32
40
  // src/Ai/AiEditorAssistant.tsx
33
- import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
41
+ import {
42
+ lazy,
43
+ Suspense,
44
+ useCallback,
45
+ useEffect,
46
+ useMemo,
47
+ useRef,
48
+ useState
49
+ } from "react";
34
50
  import { createPortal } from "react-dom";
35
51
  import {
36
52
  useEditorContext,
37
53
  useEditorContextMenuItems
38
54
  } from "@bendyline/squisq-editor-react";
39
55
 
40
- // src/Ai/AiGenerationStatus.tsx
41
- import { useEffect, useState } from "react";
42
- import { jsx, jsxs } from "react/jsx-runtime";
43
- var LABELS = {
44
- starting: "Waiting for model\u2026",
45
- queued: "Waiting for an available model\u2026",
46
- loading_model: "Loading model\u2026",
47
- prefill: "Processing input (prefill)\u2026",
48
- reasoning: "Thinking\u2026",
49
- generating: "Writing\u2026"
50
- };
51
- function duration(milliseconds) {
52
- const seconds = Math.max(0, Math.floor(milliseconds / 1e3));
53
- return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
54
- }
55
- function AiGenerationStatus({
56
- state,
57
- characters
58
- }) {
59
- const [now, setNow] = useState(Date.now);
60
- useEffect(() => {
61
- const timer = setInterval(() => setNow(Date.now()), 1e3);
62
- return () => clearInterval(timer);
63
- }, []);
64
- const progress = state.progress;
65
- const label = progress ? LABELS[progress.phase] : "Waiting for response\u2026";
66
- const percent = progress?.phase === "prefill" ? progress.percent : null;
67
- const details = [`${duration(now - state.startedAt)} elapsed`];
68
- if (progress?.outputTokens != null) {
69
- details.push(`${progress.outputTokens.toLocaleString()} tokens generated`);
70
- } else if (characters > 0) {
71
- details.push(`${characters.toLocaleString()} characters received`);
72
- }
73
- if (progress?.tokensPerSecond != null) {
74
- details.push(`${progress.tokensPerSecond.toFixed(1)} tokens/s`);
75
- }
76
- if (now - state.lastActivityAt >= 15e3) {
77
- details.push(
78
- progress || characters > 0 ? `Last update ${duration(now - state.lastActivityAt)} ago` : "No progress updates received"
79
- );
80
- }
81
- return /* @__PURE__ */ jsxs("div", { className: "db-ai-generation-status", children: [
82
- /* @__PURE__ */ jsxs("p", { className: "db-ai-progress", role: "status", children: [
83
- /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
84
- label,
85
- percent != null ? ` ${Math.floor(percent)}%` : ""
86
- ] }),
87
- percent != null && /* @__PURE__ */ jsx("progress", { "aria-label": label.replace("\u2026", ""), max: 100, value: percent }),
88
- /* @__PURE__ */ jsx("p", { className: "db-ai-note", "aria-live": "off", children: details.join(" \xB7 ") })
89
- ] });
90
- }
91
-
92
56
  // src/Ai/draft-capacity.ts
93
57
  import { AI_WIRE_LIMITS } from "@bendyline/docblocks/host";
94
58
  function estimateTokens(text) {
@@ -117,7 +81,10 @@ function draftCapacityNotice(request, model, expectedOutput = "") {
117
81
  }
118
82
 
119
83
  // src/Ai/AiEditorAssistant.tsx
120
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
84
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
85
+ var AiStudyDialog = lazy(
86
+ () => import("./AiStudyDialog-P3E7PJV5.js").then((module) => ({ default: module.AiStudyDialog }))
87
+ );
121
88
  function AiToolbarControl({
122
89
  ai,
123
90
  readOnly = false,
@@ -127,24 +94,27 @@ function AiToolbarControl({
127
94
  const {
128
95
  activeView,
129
96
  editorMode,
97
+ fileName,
130
98
  getSelection,
131
99
  insertBlockAfterCursor,
132
100
  layoutMode,
133
101
  markdownSource,
134
102
  replaceSelection,
135
- selectionVersion
103
+ selectionVersion,
104
+ workspaceContainer
136
105
  } = useEditorContext();
137
106
  const status = useAiStatus(ai);
138
- const [menuOpen, setMenuOpen] = useState2(false);
139
- const [draftMode, setDraftMode] = useState2(null);
140
- const [capturedSelection, setCapturedSelection] = useState2(null);
141
- const [diagramSelection, setDiagramSelection] = useState2(null);
107
+ const [menuOpen, setMenuOpen] = useState(false);
108
+ const [draftMode, setDraftMode] = useState(null);
109
+ const [capturedSelection, setCapturedSelection] = useState(null);
110
+ const [diagramSelection, setDiagramSelection] = useState(null);
111
+ const [studyKind, setStudyKind] = useState(null);
142
112
  const rootRef = useRef(null);
143
113
  const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(menuOpen, setMenuOpen);
144
114
  void activeView;
145
115
  void selectionVersion;
146
116
  const selection = getSelection();
147
- useEffect2(() => {
117
+ useEffect(() => {
148
118
  if (!menuOpen) return;
149
119
  const onPointerDown = (event) => {
150
120
  if (rootRef.current && !rootRef.current.contains(event.target)) setMenuOpen(false);
@@ -152,6 +122,7 @@ function AiToolbarControl({
152
122
  document.addEventListener("pointerdown", onPointerDown);
153
123
  return () => document.removeEventListener("pointerdown", onPointerDown);
154
124
  }, [menuOpen]);
125
+ useEffect(() => setStudyKind(null), [workspaceContainer]);
155
126
  const ready = status?.kind === "ready" && status.model !== null;
156
127
  const openDiagram = () => {
157
128
  const current = getSelection();
@@ -174,6 +145,7 @@ function AiToolbarControl({
174
145
  return null;
175
146
  }
176
147
  const editableSelection = !readOnly && ready ? selection : null;
148
+ const canStudy = ready && !readOnly && workspaceContainer !== null && markdownSource.trim().length > 0;
177
149
  const canCompose = editableSelection?.empty === true;
178
150
  const canRewrite = editableSelection?.empty === false && (editableSelection.markdown ?? editableSelection.text).length <= AI_REWRITE_SELECTION_CHARACTERS;
179
151
  const openDraft = (mode) => {
@@ -187,9 +159,9 @@ function AiToolbarControl({
187
159
  setDraftMode(null);
188
160
  setCapturedSelection(null);
189
161
  };
190
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
191
- /* @__PURE__ */ jsxs2("div", { className: "db-toolbar-menu db-ai-toolbar", ref: rootRef, children: [
192
- /* @__PURE__ */ jsx2(
162
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
163
+ /* @__PURE__ */ jsxs("div", { className: "db-toolbar-menu db-ai-toolbar", ref: rootRef, children: [
164
+ /* @__PURE__ */ jsx(
193
165
  "button",
194
166
  {
195
167
  ref: triggerRef,
@@ -201,10 +173,10 @@ function AiToolbarControl({
201
173
  "aria-haspopup": "menu",
202
174
  "aria-expanded": menuOpen,
203
175
  "data-tooltip": "AI actions",
204
- children: /* @__PURE__ */ jsx2(SparklesGlyph, {})
176
+ children: /* @__PURE__ */ jsx(SparklesGlyph, {})
205
177
  }
206
178
  ),
207
- menuOpen && /* @__PURE__ */ jsxs2(
179
+ menuOpen && /* @__PURE__ */ jsxs(
208
180
  "div",
209
181
  {
210
182
  ref: menuRef,
@@ -213,8 +185,8 @@ function AiToolbarControl({
213
185
  "aria-label": "AI actions",
214
186
  onKeyDown: handleMenuKeyDown,
215
187
  children: [
216
- !ready && /* @__PURE__ */ jsx2("div", { className: "db-ai-toolbar-status", role: "status", children: unavailableSentence(status) }),
217
- /* @__PURE__ */ jsx2(
188
+ !ready && /* @__PURE__ */ jsx("div", { className: "db-ai-toolbar-status", role: "status", children: unavailableSentence(status) }),
189
+ /* @__PURE__ */ jsx(
218
190
  "button",
219
191
  {
220
192
  type: "button",
@@ -226,7 +198,7 @@ function AiToolbarControl({
226
198
  children: "Add content\u2026"
227
199
  }
228
200
  ),
229
- /* @__PURE__ */ jsx2(
201
+ /* @__PURE__ */ jsx(
230
202
  "button",
231
203
  {
232
204
  type: "button",
@@ -238,7 +210,7 @@ function AiToolbarControl({
238
210
  children: "Rewrite selection\u2026"
239
211
  }
240
212
  ),
241
- /* @__PURE__ */ jsx2(
213
+ /* @__PURE__ */ jsx(
242
214
  "button",
243
215
  {
244
216
  type: "button",
@@ -253,7 +225,7 @@ function AiToolbarControl({
253
225
  children: openPanel === "review" ? "Show document review" : "Review document\u2026"
254
226
  }
255
227
  ),
256
- /* @__PURE__ */ jsx2(
228
+ /* @__PURE__ */ jsx(
257
229
  "button",
258
230
  {
259
231
  type: "button",
@@ -268,7 +240,7 @@ function AiToolbarControl({
268
240
  children: openPanel === "illustrate" ? "Show illustrations" : "Illustrate document\u2026"
269
241
  }
270
242
  ),
271
- /* @__PURE__ */ jsx2(
243
+ /* @__PURE__ */ jsx(
272
244
  "button",
273
245
  {
274
246
  type: "button",
@@ -280,14 +252,31 @@ function AiToolbarControl({
280
252
  children: "Insert diagram\u2026"
281
253
  }
282
254
  ),
283
- ready && !selection && /* @__PURE__ */ jsx2("div", { className: "db-ai-toolbar-status", children: "Switch to Write or Source to edit with AI." }),
284
- ready && selection && !selection.empty && !canRewrite && /* @__PURE__ */ jsx2("div", { className: "db-ai-toolbar-status", children: "The selection is too long to rewrite." }),
285
- ready && layoutMode !== "document" && /* @__PURE__ */ jsx2("div", { className: "db-ai-toolbar-status", children: "Switch to the Document layout to illustrate." })
255
+ /* @__PURE__ */ jsx("div", { className: "db-toolbar-menu-divider", role: "separator" }),
256
+ STUDY_AID_KINDS.map((kind) => /* @__PURE__ */ jsx(
257
+ "button",
258
+ {
259
+ type: "button",
260
+ role: "menuitem",
261
+ tabIndex: -1,
262
+ className: "db-toolbar-menu-item",
263
+ disabled: !canStudy,
264
+ onClick: () => {
265
+ closeMenu(false);
266
+ setStudyKind(kind);
267
+ },
268
+ children: STUDY_AID_LABELS[kind].menu
269
+ },
270
+ kind
271
+ )),
272
+ ready && !selection && /* @__PURE__ */ jsx("div", { className: "db-ai-toolbar-status", children: "Switch to Write or Source to edit with AI." }),
273
+ ready && selection && !selection.empty && !canRewrite && /* @__PURE__ */ jsx("div", { className: "db-ai-toolbar-status", children: "The selection is too long to rewrite." }),
274
+ ready && layoutMode !== "document" && /* @__PURE__ */ jsx("div", { className: "db-ai-toolbar-status", children: "Switch to the Document layout to illustrate." })
286
275
  ]
287
276
  }
288
277
  )
289
278
  ] }),
290
- draftMode && capturedSelection && /* @__PURE__ */ jsx2(
279
+ draftMode && capturedSelection && /* @__PURE__ */ jsx(
291
280
  AiDraftDialog,
292
281
  {
293
282
  ai,
@@ -300,7 +289,7 @@ function AiToolbarControl({
300
289
  onClose: closeDraft
301
290
  }
302
291
  ),
303
- diagramSelection && /* @__PURE__ */ jsx2(
292
+ diagramSelection && /* @__PURE__ */ jsx(
304
293
  AiDiagramDialog,
305
294
  {
306
295
  ai,
@@ -310,7 +299,19 @@ function AiToolbarControl({
310
299
  insertBlockAfterCursor,
311
300
  onClose: () => setDiagramSelection(null)
312
301
  }
313
- )
302
+ ),
303
+ studyKind && workspaceContainer && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
304
+ AiStudyDialog,
305
+ {
306
+ ai,
307
+ kind: studyKind,
308
+ documentSource: markdownSource,
309
+ documentName: (fileName ?? "").split("/").pop() || "Untitled.md",
310
+ container: workspaceContainer,
311
+ onClose: () => setStudyKind(null)
312
+ },
313
+ studyKind
314
+ ) })
314
315
  ] });
315
316
  }
316
317
  function AiDraftDialog({
@@ -324,17 +325,17 @@ function AiDraftDialog({
324
325
  onClose
325
326
  }) {
326
327
  const defaultInstructions = mode === "compose" ? "Add a concise introduction for this document." : "Improve clarity and flow while preserving the meaning and voice.";
327
- const [instructions, setInstructions] = useState2("");
328
- const [draft, setDraft] = useState2(null);
328
+ const [instructions, setInstructions] = useState("");
329
+ const [draft, setDraft] = useState(null);
329
330
  const generatedDraftRef = useRef("");
330
331
  const carriedEditsRef = useRef(false);
331
- const [originalRequest, setOriginalRequest] = useState2(null);
332
- const [incomplete, setIncomplete] = useState2(false);
333
- const [prefixCharacters, setPrefixCharacters] = useState2(0);
334
- const [running, setRunning] = useState2(false);
335
- const [generation, setGeneration] = useState2(null);
336
- const [error, setError] = useState2(null);
337
- const [finishNote, setFinishNote] = useState2(null);
332
+ const [originalRequest, setOriginalRequest] = useState(null);
333
+ const [incomplete, setIncomplete] = useState(false);
334
+ const [prefixCharacters, setPrefixCharacters] = useState(0);
335
+ const [running, setRunning] = useState(false);
336
+ const [generation, setGeneration] = useState(null);
337
+ const [error, setError] = useState(null);
338
+ const [finishNote, setFinishNote] = useState(null);
338
339
  const promptRef = useRef(null);
339
340
  const handleRef = useRef(null);
340
341
  const requestRef = useRef(0);
@@ -358,7 +359,7 @@ function AiDraftDialog({
358
359
  model,
359
360
  incomplete || mode === "compose" ? "" : selectedMarkdown
360
361
  );
361
- useEffect2(
362
+ useEffect(
362
363
  () => () => {
363
364
  requestRef.current += 1;
364
365
  handleRef.current?.cancel();
@@ -415,7 +416,7 @@ function AiDraftDialog({
415
416
  }
416
417
  const emptyContinuation = Boolean(prefix) && !event.completion.text.trim();
417
418
  const finished = event.completion.finishReason === "stop" && !emptyContinuation;
418
- generatedDraftRef.current = prefix || !finished ? prefix + event.completion.text : sanitizeGeneratedMarkdown(event.completion.text);
419
+ generatedDraftRef.current = prefix || !finished ? prefix + event.completion.text : sanitizeGeneratedMarkdown(event.completion.text, documentSource);
419
420
  setDraft(generatedDraftRef.current);
420
421
  setIncomplete(!finished && Boolean(generatedDraftRef.current.trim()));
421
422
  if (event.completion.finishReason === "cancelled") {
@@ -434,7 +435,7 @@ function AiDraftDialog({
434
435
  });
435
436
  handleRef.current = handle;
436
437
  },
437
- [ai]
438
+ [ai, documentSource]
438
439
  );
439
440
  const generate = useCallback(async () => {
440
441
  if (running || draftCapacityNotice(draftRequest, model)?.blocked) return;
@@ -460,7 +461,7 @@ function AiDraftDialog({
460
461
  const stop = useCallback(() => handleRef.current?.cancel(), []);
461
462
  const apply = useCallback(async () => {
462
463
  if (running) return;
463
- const replacement = sanitizeGeneratedMarkdown(draft ?? "");
464
+ const replacement = sanitizeGeneratedMarkdown(draft ?? "", documentSource);
464
465
  if (!replacement) return;
465
466
  if (incomplete) {
466
467
  const previousRequest = requestRef.current;
@@ -493,6 +494,7 @@ function AiDraftDialog({
493
494
  }, [
494
495
  capturedSelection,
495
496
  selectedMarkdown,
497
+ documentSource,
496
498
  draft,
497
499
  getSelection,
498
500
  mode,
@@ -502,43 +504,43 @@ function AiDraftDialog({
502
504
  incomplete,
503
505
  confirmAction
504
506
  ]);
505
- const dialog = /* @__PURE__ */ jsxs2(
507
+ const dialog = /* @__PURE__ */ jsxs(
506
508
  Dialog,
507
509
  {
508
510
  title: mode === "compose" ? "Add content with AI" : "Rewrite with AI",
509
511
  onClose,
510
512
  size: "wide",
511
- closeOnBackdrop: !running,
513
+ closeOnBackdrop: false,
512
514
  initialFocusRef: promptRef,
513
515
  className: "db-ai-draft-dialog",
514
516
  bodyClassName: "db-ai-draft-body",
515
517
  footerClassName: "db-ai-draft-footer",
516
- footer: /* @__PURE__ */ jsxs2(Fragment, { children: [
517
- (running || finishNote || error || capacity) && /* @__PURE__ */ jsxs2("div", { className: "db-ai-footer-status", children: [
518
- running && generation && /* @__PURE__ */ jsx2(
518
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
519
+ (running || finishNote || error || capacity) && /* @__PURE__ */ jsxs("div", { className: "db-ai-footer-status", children: [
520
+ running && generation && /* @__PURE__ */ jsx(
519
521
  AiGenerationStatus,
520
522
  {
521
523
  state: generation,
522
524
  characters: Math.max(0, (draft?.length ?? 0) - prefixCharacters)
523
525
  }
524
526
  ),
525
- finishNote && /* @__PURE__ */ jsx2("p", { className: "db-ai-note", children: finishNote }),
526
- !running && capacity && /* @__PURE__ */ jsx2("p", { className: "db-ai-note", role: "status", children: capacity.message }),
527
- error && /* @__PURE__ */ jsx2("p", { className: "db-ai-error", role: "alert", children: error })
527
+ finishNote && /* @__PURE__ */ jsx("p", { className: "db-ai-note", children: finishNote }),
528
+ !running && capacity && /* @__PURE__ */ jsx("p", { className: "db-ai-note", role: "status", children: capacity.message }),
529
+ error && /* @__PURE__ */ jsx("p", { className: "db-ai-error", role: "alert", children: error })
528
530
  ] }),
529
- /* @__PURE__ */ jsxs2("div", { className: "db-ai-draft-actions", children: [
530
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: onClose, children: "Cancel" }),
531
- running ? /* @__PURE__ */ jsx2("button", { type: "button", className: "db-ai-secondary-action", onClick: stop, children: "Stop" }) : draft !== null ? /* @__PURE__ */ jsxs2(Fragment, { children: [
532
- /* @__PURE__ */ jsx2("button", { type: "button", className: "db-ai-secondary-action", onClick: generate, children: "Regenerate" }),
533
- incomplete && draft.trim() && /* @__PURE__ */ jsx2("button", { type: "button", disabled: capacity?.blocked, onClick: continueDraft, children: "Continue draft" })
531
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-draft-actions", children: [
532
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Cancel" }),
533
+ running ? /* @__PURE__ */ jsx("button", { type: "button", className: "db-ai-secondary-action", onClick: stop, children: "Stop" }) : draft !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [
534
+ /* @__PURE__ */ jsx("button", { type: "button", className: "db-ai-secondary-action", onClick: generate, children: "Regenerate" }),
535
+ incomplete && draft.trim() && /* @__PURE__ */ jsx("button", { type: "button", disabled: capacity?.blocked, onClick: continueDraft, children: "Continue draft" })
534
536
  ] }) : null,
535
- /* @__PURE__ */ jsx2("button", { type: "button", disabled: !draft?.trim() || running, onClick: apply, children: mode === "compose" ? "Insert" : "Replace selection" })
537
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: !draft?.trim() || running, onClick: apply, children: mode === "compose" ? "Insert" : "Replace selection" })
536
538
  ] })
537
539
  ] }),
538
540
  children: [
539
- /* @__PURE__ */ jsxs2("label", { className: "db-ai-field", children: [
540
- /* @__PURE__ */ jsx2("span", { children: mode === "compose" ? "What should be added?" : "How should this be rewritten?" }),
541
- /* @__PURE__ */ jsx2(
541
+ /* @__PURE__ */ jsxs("label", { className: "db-ai-field", children: [
542
+ /* @__PURE__ */ jsx("span", { children: mode === "compose" ? "What should be added?" : "How should this be rewritten?" }),
543
+ /* @__PURE__ */ jsx(
542
544
  "textarea",
543
545
  {
544
546
  ref: promptRef,
@@ -550,14 +552,14 @@ function AiDraftDialog({
550
552
  }
551
553
  )
552
554
  ] }),
553
- mode === "rewrite" && /* @__PURE__ */ jsxs2("div", { className: "db-ai-selection-preview", children: [
554
- /* @__PURE__ */ jsx2("span", { children: "Selected text" }),
555
- /* @__PURE__ */ jsx2("blockquote", { tabIndex: 0, "aria-label": "Selected text", children: capturedSelection.text })
555
+ mode === "rewrite" && /* @__PURE__ */ jsxs("div", { className: "db-ai-selection-preview", children: [
556
+ /* @__PURE__ */ jsx("span", { children: "Selected text" }),
557
+ /* @__PURE__ */ jsx("blockquote", { tabIndex: 0, "aria-label": "Selected text", children: capturedSelection.text })
556
558
  ] }),
557
- draft === null ? /* @__PURE__ */ jsx2("div", { className: "db-ai-draft-start", children: /* @__PURE__ */ jsx2("button", { type: "button", onClick: generate, disabled: capacity?.blocked, children: capacity && !capacity.blocked ? "Generate anyway" : "Generate" }) }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
558
- /* @__PURE__ */ jsxs2("label", { className: "db-ai-field db-ai-draft-field", children: [
559
- /* @__PURE__ */ jsx2("span", { children: "AI draft" }),
560
- /* @__PURE__ */ jsx2(
559
+ draft === null ? /* @__PURE__ */ jsx("div", { className: "db-ai-draft-start", children: /* @__PURE__ */ jsx("button", { type: "button", onClick: generate, disabled: capacity?.blocked, children: capacity && !capacity.blocked ? "Generate anyway" : "Generate" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
560
+ /* @__PURE__ */ jsxs("label", { className: "db-ai-field db-ai-draft-field", children: [
561
+ /* @__PURE__ */ jsx("span", { children: "AI draft" }),
562
+ /* @__PURE__ */ jsx(
561
563
  "textarea",
562
564
  {
563
565
  className: "db-ai-draft-output",
@@ -570,26 +572,49 @@ function AiDraftDialog({
570
572
  }
571
573
  )
572
574
  ] }),
573
- /* @__PURE__ */ jsx2("p", { className: "db-ai-disclaimer", children: "Review and edit AI-generated content before adding it." })
575
+ /* @__PURE__ */ jsxs("p", { className: "db-ai-disclaimer", children: [
576
+ "Review and edit AI-generated content before adding it.",
577
+ !running && /* @__PURE__ */ jsxs(Fragment, { children: [
578
+ " ",
579
+ /* @__PURE__ */ jsx(
580
+ AiReportButton,
581
+ {
582
+ feature: mode === "compose" ? "Add content" : "Rewrite selection",
583
+ getOutput: () => generatedDraftRef.current,
584
+ model: model?.label ?? null
585
+ }
586
+ )
587
+ ] })
588
+ ] })
574
589
  ] })
575
590
  ]
576
591
  }
577
592
  );
578
- const dialogs = /* @__PURE__ */ jsxs2(Fragment, { children: [
593
+ const dialogs = /* @__PURE__ */ jsxs(Fragment, { children: [
579
594
  dialog,
580
595
  confirmDialog
581
596
  ] });
582
597
  return typeof document === "undefined" ? dialogs : createPortal(dialogs, document.body);
583
598
  }
599
+ function formatReviewFindings(findings) {
600
+ return findings.map(
601
+ (finding) => [
602
+ `${finding.severity} \xB7 ${finding.category}: ${finding.message}`,
603
+ finding.rationale,
604
+ finding.replacement !== null ? `Suggested: ${finding.replacement}` : ""
605
+ ].filter(Boolean).join("\n")
606
+ ).join("\n\n");
607
+ }
584
608
  function AiReviewPanel({ ai, onClose }) {
585
609
  const { markdownSource, replaceAll } = useEditorContext();
586
- const [phase, setPhase] = useState2("idle");
587
- const [findings, setFindings] = useState2([]);
588
- const [reviewedSource, setReviewedSource] = useState2(null);
589
- const [resolvedIds, setResolvedIds] = useState2(() => /* @__PURE__ */ new Set());
590
- const [error, setError] = useState2(null);
591
- const [cutOff, setCutOff] = useState2(false);
592
- const [applyErrorId, setApplyErrorId] = useState2(null);
610
+ const [phase, setPhase] = useState("idle");
611
+ const [findings, setFindings] = useState([]);
612
+ const [reviewedSource, setReviewedSource] = useState(null);
613
+ const [resolvedIds, setResolvedIds] = useState(() => /* @__PURE__ */ new Set());
614
+ const [error, setError] = useState(null);
615
+ const [cutOff, setCutOff] = useState(false);
616
+ const [applyErrorId, setApplyErrorId] = useState(null);
617
+ const [reviewModel, setReviewModel] = useState(null);
593
618
  const handleRef = useRef(null);
594
619
  const requestRef = useRef(0);
595
620
  const startedRef = useRef(false);
@@ -609,7 +634,7 @@ function AiReviewPanel({ ai, onClose }) {
609
634
  setPhase("ready");
610
635
  return;
611
636
  }
612
- if (markdownSource.length > AI_REVIEW_DOCUMENT_CHARACTERS) {
637
+ if (reviewDocumentCharacters(markdownSource) > AI_REVIEW_DOCUMENT_CHARACTERS) {
613
638
  setError("This document is too long for one AI review pass. Review a shorter document.");
614
639
  setPhase("error");
615
640
  return;
@@ -641,18 +666,19 @@ function AiReviewPanel({ ai, onClose }) {
641
666
  return;
642
667
  }
643
668
  setCutOff(truncated);
644
- setFindings(parsed);
669
+ setReviewModel(event.completion.model || null);
670
+ setFindings(withoutPictureFindings(parsed, source));
645
671
  setReviewedSource(source);
646
672
  setPhase("ready");
647
673
  });
648
674
  handleRef.current = handle;
649
675
  }, [ai, markdownSource]);
650
- useEffect2(() => {
676
+ useEffect(() => {
651
677
  if (startedRef.current) return;
652
678
  startedRef.current = true;
653
679
  runReview();
654
680
  }, [runReview]);
655
- useEffect2(
681
+ useEffect(
656
682
  () => () => {
657
683
  requestRef.current += 1;
658
684
  handleRef.current?.cancel();
@@ -681,17 +707,17 @@ function AiReviewPanel({ ai, onClose }) {
681
707
  }, []);
682
708
  const visibleFindings = findings.filter((finding) => !resolvedIds.has(finding.id));
683
709
  const documentChanged = reviewedSource !== null && markdownSource !== reviewedSource;
684
- return /* @__PURE__ */ jsxs2("aside", { className: "db-ai-review-panel", "aria-label": "AI document review", children: [
685
- /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-header", children: [
686
- /* @__PURE__ */ jsxs2("div", { children: [
687
- /* @__PURE__ */ jsx2("h2", { children: "AI review" }),
688
- phase === "ready" && /* @__PURE__ */ jsxs2("span", { className: "db-ai-review-count", children: [
710
+ return /* @__PURE__ */ jsxs("aside", { className: "db-ai-review-panel", "aria-label": "AI document review", children: [
711
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-review-header", children: [
712
+ /* @__PURE__ */ jsxs("div", { children: [
713
+ /* @__PURE__ */ jsx("h2", { children: "AI review" }),
714
+ phase === "ready" && /* @__PURE__ */ jsxs("span", { className: "db-ai-review-count", children: [
689
715
  visibleFindings.length,
690
716
  " ",
691
717
  visibleFindings.length === 1 ? "finding" : "findings"
692
718
  ] })
693
719
  ] }),
694
- /* @__PURE__ */ jsx2(
720
+ /* @__PURE__ */ jsx(
695
721
  "button",
696
722
  {
697
723
  type: "button",
@@ -702,48 +728,48 @@ function AiReviewPanel({ ai, onClose }) {
702
728
  }
703
729
  )
704
730
  ] }),
705
- /* @__PURE__ */ jsx2("div", { className: "db-ai-review-actions", children: phase === "running" ? /* @__PURE__ */ jsx2("button", { type: "button", onClick: cancelReview, children: "Stop review" }) : /* @__PURE__ */ jsx2("button", { type: "button", onClick: runReview, children: phase === "idle" ? "Review document" : "Review again" }) }),
706
- phase === "running" && /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-empty", role: "status", children: [
707
- /* @__PURE__ */ jsx2("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
731
+ /* @__PURE__ */ jsx("div", { className: "db-ai-review-actions", children: phase === "running" ? /* @__PURE__ */ jsx("button", { type: "button", onClick: cancelReview, children: "Stop review" }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: runReview, children: phase === "idle" ? "Review document" : "Review again" }) }),
732
+ phase === "running" && /* @__PURE__ */ jsxs("div", { className: "db-ai-review-empty", role: "status", children: [
733
+ /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
708
734
  " Reviewing this document\u2026"
709
735
  ] }),
710
- phase === "error" && error && /* @__PURE__ */ jsx2("div", { className: "db-ai-review-empty db-ai-error", role: "alert", children: error }),
711
- cutOff && phase === "ready" && /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-stale", role: "status", children: [
736
+ phase === "error" && error && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty db-ai-error", role: "alert", children: error }),
737
+ cutOff && phase === "ready" && /* @__PURE__ */ jsxs("div", { className: "db-ai-review-stale", role: "status", children: [
712
738
  "The AI ran out of room after ",
713
739
  findings.length,
714
740
  " ",
715
741
  findings.length === 1 ? "finding" : "findings",
716
742
  ", so there may be more. After you apply these, review again to look for others."
717
743
  ] }),
718
- documentChanged && phase === "ready" && /* @__PURE__ */ jsx2("div", { className: "db-ai-review-stale", role: "status", children: "The document changed after this review. Suggestions are applied only when their original text still matches exactly." }),
719
- phase === "ready" && visibleFindings.length === 0 && /* @__PURE__ */ jsx2("div", { className: "db-ai-review-empty", children: findings.length === 0 ? "Nothing to report." : "All findings have been handled." }),
720
- /* @__PURE__ */ jsx2("ol", { className: "db-ai-review-list", children: visibleFindings.map((finding) => {
744
+ documentChanged && phase === "ready" && /* @__PURE__ */ jsx("div", { className: "db-ai-review-stale", role: "status", children: "The document changed after this review. Suggestions are applied only when their original text still matches exactly." }),
745
+ phase === "ready" && visibleFindings.length === 0 && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty", children: findings.length === 0 ? "Nothing to report." : "All findings have been handled." }),
746
+ /* @__PURE__ */ jsx("ol", { className: "db-ai-review-list", children: visibleFindings.map((finding) => {
721
747
  const canApply = finding.replacement !== null && findUniqueExcerpt(markdownSource, finding.originalText) !== null;
722
- return /* @__PURE__ */ jsxs2("li", { className: "db-ai-review-finding", children: [
723
- /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-finding-meta", children: [
724
- /* @__PURE__ */ jsx2(
748
+ return /* @__PURE__ */ jsxs("li", { className: "db-ai-review-finding", children: [
749
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-review-finding-meta", children: [
750
+ /* @__PURE__ */ jsx(
725
751
  "span",
726
752
  {
727
753
  className: `db-ai-review-severity db-ai-review-severity--${finding.severity}`,
728
754
  children: finding.severity
729
755
  }
730
756
  ),
731
- /* @__PURE__ */ jsx2("span", { children: finding.category })
757
+ /* @__PURE__ */ jsx("span", { children: finding.category })
732
758
  ] }),
733
- /* @__PURE__ */ jsx2("h3", { children: finding.message }),
734
- finding.rationale && /* @__PURE__ */ jsx2("p", { children: finding.rationale }),
735
- /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-excerpt", children: [
736
- /* @__PURE__ */ jsx2("span", { children: "Current text" }),
737
- /* @__PURE__ */ jsx2("blockquote", { children: finding.originalText })
759
+ /* @__PURE__ */ jsx("h3", { children: finding.message }),
760
+ finding.rationale && /* @__PURE__ */ jsx("p", { children: finding.rationale }),
761
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-review-excerpt", children: [
762
+ /* @__PURE__ */ jsx("span", { children: "Current text" }),
763
+ /* @__PURE__ */ jsx("blockquote", { children: finding.originalText })
738
764
  ] }),
739
- finding.replacement !== null && /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-excerpt db-ai-review-excerpt--replacement", children: [
740
- /* @__PURE__ */ jsx2("span", { children: "Suggested" }),
741
- /* @__PURE__ */ jsx2("blockquote", { children: finding.replacement })
765
+ finding.replacement !== null && /* @__PURE__ */ jsxs("div", { className: "db-ai-review-excerpt db-ai-review-excerpt--replacement", children: [
766
+ /* @__PURE__ */ jsx("span", { children: "Suggested" }),
767
+ /* @__PURE__ */ jsx("blockquote", { children: finding.replacement })
742
768
  ] }),
743
- applyErrorId === finding.id && /* @__PURE__ */ jsx2("p", { className: "db-ai-error", role: "alert", children: "This text no longer has one exact match. Review the document again before applying it." }),
744
- /* @__PURE__ */ jsxs2("div", { className: "db-ai-review-finding-actions", children: [
745
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => dismissFinding(finding.id), children: "Dismiss" }),
746
- finding.replacement !== null && /* @__PURE__ */ jsx2(
769
+ applyErrorId === finding.id && /* @__PURE__ */ jsx("p", { className: "db-ai-error", role: "alert", children: "This text no longer has one exact match. Review the document again before applying it." }),
770
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-review-finding-actions", children: [
771
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => dismissFinding(finding.id), children: "Dismiss" }),
772
+ finding.replacement !== null && /* @__PURE__ */ jsx(
747
773
  "button",
748
774
  {
749
775
  type: "button",
@@ -756,11 +782,24 @@ function AiReviewPanel({ ai, onClose }) {
756
782
  ] })
757
783
  ] }, finding.id);
758
784
  }) }),
759
- /* @__PURE__ */ jsx2("p", { className: "db-ai-review-disclaimer", children: "AI suggestions can be wrong. Review each change." })
785
+ /* @__PURE__ */ jsxs("p", { className: "db-ai-review-disclaimer", children: [
786
+ "AI suggestions can be wrong. Review each change.",
787
+ phase === "ready" && findings.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
788
+ " ",
789
+ /* @__PURE__ */ jsx(
790
+ AiReportButton,
791
+ {
792
+ feature: "Review document",
793
+ getOutput: () => formatReviewFindings(findings),
794
+ model: reviewModel
795
+ }
796
+ )
797
+ ] })
798
+ ] })
760
799
  ] });
761
800
  }
762
801
  function SparklesGlyph() {
763
- return /* @__PURE__ */ jsxs2(
802
+ return /* @__PURE__ */ jsxs(
764
803
  "svg",
765
804
  {
766
805
  viewBox: "0 0 18 18",
@@ -773,8 +812,8 @@ function SparklesGlyph() {
773
812
  strokeLinejoin: "round",
774
813
  "aria-hidden": "true",
775
814
  children: [
776
- /* @__PURE__ */ jsx2("path", { d: "M7.2 2.2c.45 2.2 1.75 3.5 3.95 3.95-2.2.45-3.5 1.75-3.95 3.95-.45-2.2-1.75-3.5-3.95-3.95C5.45 5.7 6.75 4.4 7.2 2.2Z" }),
777
- /* @__PURE__ */ jsx2("path", { d: "M13.1 9.2c.3 1.45 1.15 2.3 2.6 2.6-1.45.3-2.3 1.15-2.6 2.6-.3-1.45-1.15-2.3-2.6-2.6 1.45-.3 2.3-1.15 2.6-2.6Z" })
815
+ /* @__PURE__ */ jsx("path", { d: "M7.2 2.2c.45 2.2 1.75 3.5 3.95 3.95-2.2.45-3.5 1.75-3.95 3.95-.45-2.2-1.75-3.5-3.95-3.95C5.45 5.7 6.75 4.4 7.2 2.2Z" }),
816
+ /* @__PURE__ */ jsx("path", { d: "M13.1 9.2c.3 1.45 1.15 2.3 2.6 2.6-1.45.3-2.3 1.15-2.6 2.6-.3-1.45-1.15-2.3-2.6-2.6 1.45-.3 2.3-1.15 2.6-2.6Z" })
778
817
  ]
779
818
  }
780
819
  );