@bendyline/docblocks-react 2.7.6 → 2.7.8

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 (43) hide show
  1. package/THIRD_PARTY_NOTICES.txt +205 -35
  2. package/dist/{AiDiagrams-VOCEZS6U.js → AiDiagrams-2IKKSBAU.js} +6 -4
  3. package/dist/{AiEditorAssistant-2MW6UYNI.js → AiEditorAssistant-ANYGVG7P.js} +128 -12
  4. package/dist/AiImagePanel-OXWEFOBC.js +314 -0
  5. package/dist/{AiStudyDialog-DQZVTBI7.js → AiStudyDialog-T3FY74HY.js} +3 -3
  6. package/dist/{AudioExportDialog-EICCBSSE.js → AudioExportDialog-CBMSLQBS.js} +1 -1
  7. package/dist/DocumentConflictDialog-4R4QMKBT.js +217 -0
  8. package/dist/{ExportDialog-LGWBXIWN.js → ExportDialog-FNFD53SY.js} +2 -2
  9. package/dist/{ExportToolbarControls-4LYTXGT3.js → ExportToolbarControls-ZOYH4I36.js} +146 -68
  10. package/dist/{GitUI-F3CNBURE.js → GitUI-3FANM7I3.js} +88 -155
  11. package/dist/{KeyboardShortcutsDialog-HVKDFP5Z.js → KeyboardShortcutsDialog-7XJTDIX2.js} +1 -1
  12. package/dist/{PresentationMaker-T526TBRJ.js → PresentationMaker-COLTVMTS.js} +3 -3
  13. package/dist/{QuickOpenDialog-3QEX7RZG.js → QuickOpenDialog-U2FWCEPA.js} +1 -1
  14. package/dist/{Settings-CfhY489G.d.ts → Settings-ButIB78y.d.ts} +20 -4
  15. package/dist/{ShareDialog-PF7D7ORP.js → ShareDialog-WOWHI6YI.js} +1 -1
  16. package/dist/{SpeechToolbarControl-E6OSJGHL.js → SpeechToolbarControl-BZW6IVZM.js} +1 -1
  17. package/dist/{TextFromNarration-ME6FAYWO.js → TextFromNarration-6BYTUKVZ.js} +2 -2
  18. package/dist/chunk-27E2OON2.js +90 -0
  19. package/dist/{chunk-5O5YTOXG.js → chunk-5YMULR6A.js} +13 -1
  20. package/dist/{chunk-NOYDVL6M.js → chunk-6QHZNL6C.js} +45 -17
  21. package/dist/{chunk-45YTIVYS.js → chunk-A7BK6QFL.js} +363 -26
  22. package/dist/{chunk-PRUEYW3G.js → chunk-BGA5UVKL.js} +58 -17
  23. package/dist/{chunk-T42BH6YX.js → chunk-CXLXGFG2.js} +37 -15
  24. package/dist/{chunk-7CSBRAMT.js → chunk-F7TR5EKX.js} +8 -6
  25. package/dist/chunk-HIKMOKBQ.js +37 -0
  26. package/dist/chunk-OW6JWRAA.js +66 -0
  27. package/dist/{chunk-BNKIPCXO.js → chunk-QXBV3G6T.js} +5 -3
  28. package/dist/{chunk-7HBKKXRV.js → chunk-TN7BY5N3.js} +722 -281
  29. package/dist/{chunk-VJPGZCFS.js → chunk-UYJCL6OW.js} +4 -2
  30. package/dist/{chunk-GFK4TCKN.js → chunk-WDNE5TI3.js} +21 -7
  31. package/dist/{chunk-KBUHWZCF.js → chunk-YX275NPE.js} +1 -1
  32. package/dist/editor.js +3 -3
  33. package/dist/export/index.d.ts +16 -162
  34. package/dist/export/index.js +6 -4
  35. package/dist/image-save-D18dJrUr.d.ts +182 -0
  36. package/dist/index.d.ts +15 -11
  37. package/dist/index.js +1365 -1078
  38. package/dist/{run-export-SPNNH32M.js → run-export-C3PLLWLS.js} +2 -2
  39. package/dist/settings/index.d.ts +1 -1
  40. package/dist/settings/index.js +3 -2
  41. package/package.json +8 -8
  42. package/src/styles/docblocks.css +349 -11
  43. package/dist/DocumentConflictDialog-DZ2BV2IH.js +0 -111
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AiDiagramDialog
3
- } from "./chunk-45YTIVYS.js";
3
+ } from "./chunk-A7BK6QFL.js";
4
4
  import {
5
5
  AiGenerationStatus,
6
6
  STUDY_AID_KINDS,
@@ -13,14 +13,16 @@ import {
13
13
  factsNeedReview,
14
14
  useAiKnowledge,
15
15
  useFactCheck
16
- } from "./chunk-NOYDVL6M.js";
16
+ } from "./chunk-6QHZNL6C.js";
17
+ import "./chunk-HIKMOKBQ.js";
17
18
  import {
18
19
  useConfirmDialog
19
- } from "./chunk-KBUHWZCF.js";
20
+ } from "./chunk-YX275NPE.js";
20
21
  import "./chunk-KHQ44ZFA.js";
21
22
  import {
22
23
  useMenuKeyboard
23
24
  } from "./chunk-GMA4KGGO.js";
25
+ import "./chunk-OW6JWRAA.js";
24
26
  import {
25
27
  AI_INSTRUCTION_CHARACTERS,
26
28
  AI_REVIEW_DOCUMENT_CHARACTERS,
@@ -37,14 +39,14 @@ import {
37
39
  reviewDocumentCharacters,
38
40
  sanitizeGeneratedMarkdown,
39
41
  withoutPictureFindings
40
- } from "./chunk-T42BH6YX.js";
42
+ } from "./chunk-CXLXGFG2.js";
41
43
  import {
42
44
  unavailableSentence,
43
45
  useAiStatus
44
46
  } from "./chunk-K4I3NJFL.js";
45
47
  import {
46
48
  Dialog
47
- } from "./chunk-VJPGZCFS.js";
49
+ } from "./chunk-UYJCL6OW.js";
48
50
 
49
51
  // src/Ai/AiEditorAssistant.tsx
50
52
  import {
@@ -59,9 +61,79 @@ import {
59
61
  import { createPortal } from "react-dom";
60
62
  import {
61
63
  useEditorContext,
62
- useEditorContextMenuItems
64
+ useEditorContextMenuItems,
65
+ useEditorInsertMenuItems
63
66
  } from "@bendyline/squisq-editor-react";
64
67
 
68
+ // src/Ai/knowledge-citations.ts
69
+ var MARKER = /[ \t]?(?<![\]\\\p{L}_])\[(\d{1,3}(?:\s*(?:[-–]|,)\s*\d{1,3})*)\](?![(:[])/gu;
70
+ function citationNumbers(inner) {
71
+ const numbers = [];
72
+ for (const part of inner.split(",")) {
73
+ const range = /^\s*(\d{1,3})\s*[-–]\s*(\d{1,3})\s*$/u.exec(part);
74
+ if (range) {
75
+ const from = Number(range[1]);
76
+ const to = Number(range[2]);
77
+ for (let n = Math.min(from, to); n <= Math.max(from, to) && numbers.length < 50; n++) {
78
+ numbers.push(n);
79
+ }
80
+ } else if (/^\s*\d{1,3}\s*$/u.test(part)) numbers.push(Number(part));
81
+ }
82
+ return [...new Set(numbers)];
83
+ }
84
+ function mapProse(markdown, transform) {
85
+ let fence = null;
86
+ return markdown.split("\n").map((line) => {
87
+ const opener = /^\s{0,3}(`{3,}|~{3,})/u.exec(line)?.[1];
88
+ if (fence !== null) {
89
+ if (opener && opener[0] === fence[0] && opener.length >= fence.length) fence = null;
90
+ return line;
91
+ }
92
+ if (opener) {
93
+ fence = opener;
94
+ return line;
95
+ }
96
+ return transform(line);
97
+ }).join("\n");
98
+ }
99
+ function stripCitationMarkers(markdown) {
100
+ return mapProse(markdown, (line) => line.replace(MARKER, ""));
101
+ }
102
+ function citationsToFootnotes(markdown, evidence, catalogNames, document2) {
103
+ const taken = new Set([...document2.matchAll(/\[\^([^\]\s]+)\]/gu)].map((match) => match[1]));
104
+ const labels = /* @__PURE__ */ new Map();
105
+ let next = 1;
106
+ const labelFor = (n) => {
107
+ const passage = evidence[n - 1];
108
+ const article = passage.title ? `${passage.catalogId}\0${passage.title}` : `#${n}`;
109
+ const known = labels.get(article);
110
+ if (known) return known.label;
111
+ let label = `source-${next++}`;
112
+ while (taken.has(label)) label = `source-${next++}`;
113
+ labels.set(article, { label, passage });
114
+ return label;
115
+ };
116
+ const body = mapProse(
117
+ markdown,
118
+ (line) => line.replace(
119
+ MARKER,
120
+ (_marker, inner) => [
121
+ ...new Set(
122
+ citationNumbers(inner).filter((n) => n >= 1 && n <= evidence.length).map(labelFor)
123
+ )
124
+ ].map((label) => `[^${label}]`).join("")
125
+ )
126
+ );
127
+ if (labels.size === 0) return body;
128
+ const notes = [...labels.values()].map(({ label, passage }) => {
129
+ const catalog = catalogNames.get(passage.catalogId) ?? passage.catalogId;
130
+ return `[^${label}]: ${passage.title || "Untitled article"}, from the ${catalog} knowledge catalog.`;
131
+ });
132
+ return `${body.trimEnd()}
133
+
134
+ ${notes.join("\n")}`;
135
+ }
136
+
65
137
  // src/Ai/draft-capacity.ts
66
138
  import { AI_WIRE_LIMITS } from "@bendyline/docblocks/host";
67
139
  function estimateTokens(text) {
@@ -92,7 +164,7 @@ function draftCapacityNotice(request, model, expectedOutput = "") {
92
164
  // src/Ai/AiEditorAssistant.tsx
93
165
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
94
166
  var AiStudyDialog = lazy(
95
- () => import("./AiStudyDialog-DQZVTBI7.js").then((module) => ({ default: module.AiStudyDialog }))
167
+ () => import("./AiStudyDialog-T3FY74HY.js").then((module) => ({ default: module.AiStudyDialog }))
96
168
  );
97
169
  function AiToolbarControl({
98
170
  ai,
@@ -133,6 +205,21 @@ function AiToolbarControl({
133
205
  }, [menuOpen]);
134
206
  useEffect(() => setStudyKind(null), [workspaceContainer]);
135
207
  const ready = status?.kind === "ready" && status.model !== null;
208
+ const imagesOffered = ai.images !== void 0 && status?.kind === "ready" && !readOnly && editorMode === "markdown";
209
+ const openImages = () => {
210
+ closeMenu(false);
211
+ onOpenPanel("images");
212
+ };
213
+ useEditorInsertMenuItems(
214
+ imagesOffered ? [
215
+ {
216
+ id: "docblocks-ai-image",
217
+ label: "Image from a description\u2026",
218
+ icon: /* @__PURE__ */ jsx(SparklesGlyph, {}),
219
+ onSelect: openImages
220
+ }
221
+ ] : []
222
+ );
136
223
  const openDiagram = () => {
137
224
  const current = getSelection();
138
225
  if (!current) return;
@@ -149,6 +236,16 @@ function AiToolbarControl({
149
236
  onSelect: openDiagram
150
237
  }
151
238
  ] : [];
239
+ if (imagesOffered) {
240
+ contextMenuItems.push({
241
+ id: "docblocks-ai-image",
242
+ label: "Generate image from selection\u2026",
243
+ group: "ai",
244
+ when: "selection",
245
+ disabled: (context) => !context.editable,
246
+ onSelect: openImages
247
+ });
248
+ }
152
249
  useEditorContextMenuItems(contextMenuItems);
153
250
  if (editorMode !== "markdown" || status?.kind === "unavailable" && status.reason === "opt-out") {
154
251
  return null;
@@ -261,6 +358,18 @@ function AiToolbarControl({
261
358
  children: "Insert diagram\u2026"
262
359
  }
263
360
  ),
361
+ ai.images && /* @__PURE__ */ jsx(
362
+ "button",
363
+ {
364
+ type: "button",
365
+ role: "menuitem",
366
+ tabIndex: -1,
367
+ className: "db-toolbar-menu-item",
368
+ disabled: !imagesOffered,
369
+ onClick: openImages,
370
+ children: openPanel === "images" ? "Show image generation" : "Generate image\u2026"
371
+ }
372
+ ),
264
373
  /* @__PURE__ */ jsx("div", { className: "db-toolbar-menu-divider", role: "separator" }),
265
374
  STUDY_AID_KINDS.map((kind) => /* @__PURE__ */ jsx(
266
375
  "button",
@@ -349,13 +458,13 @@ function AiDraftDialog({
349
458
  const handleRef = useRef(null);
350
459
  const requestRef = useRef(0);
351
460
  const { confirm: confirmAction, confirmDialog } = useConfirmDialog();
352
- const { knowledge, ai: knowledgeAi, reset: resetKnowledge } = useAiKnowledge(ai);
461
+ const { knowledge, citable, ai: knowledgeAi, reset: resetKnowledge } = useAiKnowledge(ai);
353
462
  const selectedMarkdown = capturedSelection.markdown ?? capturedSelection.text;
354
463
  const factsGiven = useMemo(
355
464
  () => originalRequest?.messages.filter((message) => message.role === "user").map((message) => message.content).join("\n\n") ?? "",
356
465
  [originalRequest]
357
466
  );
358
- const facts = useFactCheck(ai, running ? null : draft, knowledge, factsGiven);
467
+ const facts = useFactCheck(ai, running ? null : draft, knowledge, factsGiven, citable);
359
468
  const draftRequest = useMemo(
360
469
  () => ({
361
470
  ...buildDraftRequest({
@@ -479,7 +588,7 @@ function AiDraftDialog({
479
588
  if (running) return;
480
589
  const replacement = sanitizeGeneratedMarkdown(draft ?? "", documentSource);
481
590
  if (!replacement) return;
482
- const factCheck = !incomplete && ai.checkFacts && factCheckRequest(replacement, knowledge, factsGiven);
591
+ const factCheck = !incomplete && ai.checkFacts && factCheckRequest(replacement, knowledge, factsGiven, citable);
483
592
  if (factCheck) {
484
593
  const previousRequest = requestRef.current;
485
594
  const checked = await ai.checkFacts?.(factCheck);
@@ -518,7 +627,13 @@ function AiDraftDialog({
518
627
  setError("The selected text changed. Close this dialog and select it again.");
519
628
  return;
520
629
  }
521
- if (!replaceSelection(replacement)) {
630
+ const inserted = knowledge?.outcome !== "used" ? replacement : mode === "compose" && citable ? citationsToFootnotes(
631
+ replacement,
632
+ knowledge.evidence,
633
+ new Map(knowledge.sources.map((source) => [source.catalogId, source.name])),
634
+ documentSource
635
+ ) : stripCitationMarkers(replacement);
636
+ if (!replaceSelection(inserted)) {
522
637
  setError("DocBlocks could not apply the draft in the current editor view.");
523
638
  return;
524
639
  }
@@ -537,7 +652,8 @@ function AiDraftDialog({
537
652
  confirmAction,
538
653
  ai,
539
654
  knowledge,
540
- factsGiven
655
+ factsGiven,
656
+ citable
541
657
  ]);
542
658
  const dialog = /* @__PURE__ */ jsxs(
543
659
  Dialog,
@@ -0,0 +1,314 @@
1
+ import {
2
+ imageAltText,
3
+ imageMarkdown,
4
+ saveGeneratedImage
5
+ } from "./chunk-HIKMOKBQ.js";
6
+ import {
7
+ imageAccessSentence,
8
+ imageProgressFraction,
9
+ imageProgressText,
10
+ useImageAccess
11
+ } from "./chunk-OW6JWRAA.js";
12
+
13
+ // src/Ai/AiImagePanel.tsx
14
+ import { useCallback, useEffect, useRef, useState } from "react";
15
+ import { useEditorContext } from "@bendyline/squisq-editor-react";
16
+ import { AI_IMAGE_ASPECTS } from "@bendyline/docblocks/host";
17
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
+ var ASPECT_LABELS = {
19
+ "1:1": "Square",
20
+ "4:3": "Landscape",
21
+ "3:4": "Portrait",
22
+ "16:9": "Wide",
23
+ "9:16": "Tall"
24
+ };
25
+ var PREFILL_CHARACTERS = 600;
26
+ function imagesOf(ai) {
27
+ return ai.images;
28
+ }
29
+ function AiImagePanel({ ai, onClose }) {
30
+ const images = imagesOf(ai);
31
+ const access = useImageAccess(images);
32
+ const { getSelection, insertBlockAfterCursor, mediaProvider, bumpMediaRevision } = useEditorContext();
33
+ const [prompt, setPrompt] = useState(() => {
34
+ const selection = getSelection();
35
+ const text = selection && !selection.empty ? selection.text.trim() : "";
36
+ return text.slice(0, PREFILL_CHARACTERS);
37
+ });
38
+ const [aspect, setAspect] = useState("1:1");
39
+ const [model, setModel] = useState(null);
40
+ const [models, setModels] = useState(null);
41
+ const [modelsError, setModelsError] = useState(null);
42
+ const [results, setResults] = useState([]);
43
+ const [notice, setNotice] = useState(null);
44
+ const [allowing, setAllowing] = useState(false);
45
+ const handle = useRef(null);
46
+ const urls = useRef(/* @__PURE__ */ new Set());
47
+ const counter = useRef(0);
48
+ const ready = access?.kind === "ready";
49
+ useEffect(() => {
50
+ if (!images) return;
51
+ let live = true;
52
+ void images.getPreferences().then((preferences) => {
53
+ if (!live) return;
54
+ setAspect(preferences.aspect);
55
+ setModel(preferences.model);
56
+ }).catch(() => void 0);
57
+ return () => {
58
+ live = false;
59
+ };
60
+ }, [images]);
61
+ useEffect(() => {
62
+ if (!images || !ready) return;
63
+ let live = true;
64
+ void images.models().then((result) => {
65
+ if (!live) return;
66
+ if (result.ok) {
67
+ setModels(result.value);
68
+ setModelsError(null);
69
+ } else {
70
+ setModelsError(result.error.message);
71
+ }
72
+ });
73
+ return () => {
74
+ live = false;
75
+ };
76
+ }, [images, ready]);
77
+ useEffect(() => {
78
+ const owned = urls.current;
79
+ return () => {
80
+ handle.current?.cancel();
81
+ for (const url of owned) URL.revokeObjectURL(url);
82
+ owned.clear();
83
+ };
84
+ }, []);
85
+ const installed = (models ?? []).filter((entry) => entry.state === "installed");
86
+ const chosen = installed.find((entry) => entry.id === model) ?? installed[0] ?? null;
87
+ const rendering = results.some((result) => result.status === "rendering");
88
+ const update = useCallback((key, change) => {
89
+ setResults(
90
+ (current) => current.map((result) => result.key === key ? { ...result, ...change } : result)
91
+ );
92
+ }, []);
93
+ const render = (text, shape) => {
94
+ if (!images || !chosen || !text.trim() || handle.current) return;
95
+ counter.current += 1;
96
+ const key = `image-${String(counter.current)}`;
97
+ setNotice(null);
98
+ setResults((current) => [
99
+ {
100
+ key,
101
+ prompt: text.trim(),
102
+ aspect: shape,
103
+ model: chosen.id,
104
+ status: "rendering",
105
+ progress: null,
106
+ image: null,
107
+ url: null,
108
+ alt: imageAltText(text),
109
+ error: null,
110
+ inserted: false
111
+ },
112
+ ...current
113
+ ]);
114
+ const started = images.generate(
115
+ { prompt: text.trim(), aspect: shape, model: chosen.id },
116
+ (event) => {
117
+ if (event.kind === "progress") update(key, { progress: event.progress });
118
+ }
119
+ );
120
+ handle.current = started;
121
+ void started.done.then((outcome) => {
122
+ if (handle.current === started) handle.current = null;
123
+ if (outcome.ok) {
124
+ const url = URL.createObjectURL(new Blob([outcome.value.data], { type: "image/png" }));
125
+ urls.current.add(url);
126
+ update(key, { status: "done", image: outcome.value, url, progress: null });
127
+ } else {
128
+ update(key, {
129
+ status: outcome.error.code === "cancelled" ? "stopped" : "failed",
130
+ error: outcome.error.code === "cancelled" ? null : outcome.error.message,
131
+ progress: null
132
+ });
133
+ }
134
+ });
135
+ };
136
+ const chooseAspect = (next) => {
137
+ setAspect(next);
138
+ void images?.setPreferences({ aspect: next }).catch(() => void 0);
139
+ };
140
+ const chooseModel = (next) => {
141
+ setModel(next);
142
+ void images?.setPreferences({ model: next }).catch(() => void 0);
143
+ };
144
+ const insert = async (result) => {
145
+ if (!result.image) return;
146
+ if (!mediaProvider) {
147
+ setNotice("Save the document in a folder to insert images.");
148
+ return;
149
+ }
150
+ try {
151
+ const path = await saveGeneratedImage(mediaProvider, result.image, result.prompt);
152
+ bumpMediaRevision?.();
153
+ if (!insertBlockAfterCursor(imageMarkdown(path, result.alt || result.prompt))) {
154
+ setNotice("Switch to Write or Source view, place the cursor, then insert again.");
155
+ return;
156
+ }
157
+ update(result.key, { inserted: true });
158
+ } catch (error) {
159
+ setNotice(
160
+ `DocBlocks could not save the image beside the document. ${error instanceof Error ? error.message : ""}`.trim()
161
+ );
162
+ }
163
+ };
164
+ const discard = (result) => {
165
+ if (result.url) {
166
+ URL.revokeObjectURL(result.url);
167
+ urls.current.delete(result.url);
168
+ }
169
+ setResults((current) => current.filter((entry) => entry.key !== result.key));
170
+ };
171
+ const allow = async () => {
172
+ if (!images) return;
173
+ setAllowing(true);
174
+ const outcome = await images.requestAccess();
175
+ setAllowing(false);
176
+ if (!outcome.ok) setNotice(outcome.error.message);
177
+ };
178
+ const blocked = imageAccessSentence(access);
179
+ const modelLabel = (id) => (models ?? []).find((entry) => entry.id === id)?.label ?? "the image model";
180
+ return /* @__PURE__ */ jsxs("aside", { className: "db-ai-review-panel db-ai-image-panel", "aria-label": "Generate image", children: [
181
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-review-header", children: [
182
+ /* @__PURE__ */ jsxs("div", { children: [
183
+ /* @__PURE__ */ jsx("h2", { children: "Generate image" }),
184
+ chosen && /* @__PURE__ */ jsx("span", { className: "db-ai-review-count", children: chosen.label })
185
+ ] }),
186
+ /* @__PURE__ */ jsx(
187
+ "button",
188
+ {
189
+ type: "button",
190
+ className: "db-ai-review-close",
191
+ onClick: onClose,
192
+ "aria-label": "Close Generate image",
193
+ children: "\xD7"
194
+ }
195
+ )
196
+ ] }),
197
+ blocked && /* @__PURE__ */ jsxs("div", { className: "db-ai-review-empty", role: "status", children: [
198
+ access?.kind === "awaiting-approval" && access.verificationCode ? /* @__PURE__ */ jsxs(Fragment, { children: [
199
+ "In Gezel, approve DocBlocks and enter this code:",
200
+ /* @__PURE__ */ jsx("span", { className: "db-ai-image-code", children: access.verificationCode })
201
+ ] }) : blocked,
202
+ access?.kind === "needs-approval" && /* @__PURE__ */ jsx("div", { className: "db-ai-illustrate-bar db-ai-image-allow", children: /* @__PURE__ */ jsx("button", { type: "button", disabled: allowing, onClick: () => void allow(), children: "Allow image generation\u2026" }) })
203
+ ] }),
204
+ ready && models !== null && installed.length === 0 && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty", role: "status", children: "No image model is installed yet. Download one under Settings, AI assistance." }),
205
+ ready && modelsError && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty db-ai-error", role: "alert", children: modelsError }),
206
+ ready && installed.length > 0 && /* @__PURE__ */ jsxs("section", { className: "db-ai-image-form", "aria-label": "Describe the image", children: [
207
+ /* @__PURE__ */ jsxs("label", { className: "db-ai-field", children: [
208
+ "Description",
209
+ /* @__PURE__ */ jsx(
210
+ "textarea",
211
+ {
212
+ value: prompt,
213
+ onChange: (event) => setPrompt(event.target.value),
214
+ placeholder: "A watercolor lighthouse on a cliff at dusk",
215
+ maxLength: 4e3
216
+ }
217
+ )
218
+ ] }),
219
+ /* @__PURE__ */ jsx("div", { className: "db-ai-image-aspects", role: "radiogroup", "aria-label": "Shape", children: AI_IMAGE_ASPECTS.map((shape) => /* @__PURE__ */ jsx(
220
+ "button",
221
+ {
222
+ type: "button",
223
+ role: "radio",
224
+ "aria-checked": aspect === shape,
225
+ onClick: () => chooseAspect(shape),
226
+ children: ASPECT_LABELS[shape]
227
+ },
228
+ shape
229
+ )) }),
230
+ installed.length > 1 && /* @__PURE__ */ jsxs("label", { className: "db-ai-field db-ai-field--inline", children: [
231
+ "Model",
232
+ /* @__PURE__ */ jsx(
233
+ "select",
234
+ {
235
+ value: chosen?.id ?? "",
236
+ onChange: (event) => chooseModel(event.target.value),
237
+ children: installed.map((entry) => /* @__PURE__ */ jsx("option", { value: entry.id, children: entry.label }, entry.id))
238
+ }
239
+ )
240
+ ] }),
241
+ /* @__PURE__ */ jsx("div", { className: "db-ai-illustrate-bar db-ai-image-actions", children: rendering ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => handle.current?.cancel(), children: "Stop" }) : /* @__PURE__ */ jsx(
242
+ "button",
243
+ {
244
+ type: "button",
245
+ className: "db-ai-image-generate",
246
+ disabled: !prompt.trim(),
247
+ onClick: () => render(prompt, aspect),
248
+ children: "Generate"
249
+ }
250
+ ) })
251
+ ] }),
252
+ notice && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty db-ai-error", role: "alert", children: notice }),
253
+ results.length > 0 && /* @__PURE__ */ jsx("ol", { className: "db-ai-review-list db-ai-image-results", children: results.map((result) => {
254
+ const fraction = imageProgressFraction(result.progress);
255
+ return /* @__PURE__ */ jsxs("li", { className: "db-ai-review-finding db-ai-image-result", children: [
256
+ /* @__PURE__ */ jsx("p", { className: "db-ai-image-prompt", children: result.prompt }),
257
+ result.status === "rendering" && /* @__PURE__ */ jsxs("div", { className: "db-ai-illustrate-progress", role: "status", children: [
258
+ /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
259
+ imageProgressText(result.progress, modelLabel(result.model)),
260
+ fraction !== null && /* @__PURE__ */ jsx("progress", { max: 1, value: fraction })
261
+ ] }),
262
+ result.status === "failed" && /* @__PURE__ */ jsx("p", { className: "db-ai-error", role: "alert", children: result.error }),
263
+ result.status === "stopped" && /* @__PURE__ */ jsx("p", { className: "db-ai-note", children: "Stopped." }),
264
+ result.status === "done" && result.url && result.image && /* @__PURE__ */ jsxs(Fragment, { children: [
265
+ /* @__PURE__ */ jsx(
266
+ "img",
267
+ {
268
+ className: "db-ai-image-preview",
269
+ src: result.url,
270
+ alt: result.alt,
271
+ width: result.image.width,
272
+ height: result.image.height
273
+ }
274
+ ),
275
+ /* @__PURE__ */ jsxs("label", { className: "db-ai-field db-ai-image-alt", children: [
276
+ "Description for readers",
277
+ /* @__PURE__ */ jsx(
278
+ "input",
279
+ {
280
+ value: result.alt,
281
+ onChange: (event) => update(result.key, { alt: event.target.value }),
282
+ maxLength: 160
283
+ }
284
+ )
285
+ ] })
286
+ ] }),
287
+ /* @__PURE__ */ jsxs("div", { className: "db-ai-illustrate-bar db-ai-image-result-actions", children: [
288
+ result.status === "done" && /* @__PURE__ */ jsx(
289
+ "button",
290
+ {
291
+ type: "button",
292
+ disabled: result.inserted,
293
+ onClick: () => void insert(result),
294
+ children: result.inserted ? "Inserted" : "Insert"
295
+ }
296
+ ),
297
+ result.status !== "rendering" && /* @__PURE__ */ jsx(
298
+ "button",
299
+ {
300
+ type: "button",
301
+ disabled: rendering || !chosen,
302
+ onClick: () => render(result.prompt, result.aspect),
303
+ children: result.status === "done" ? "Regenerate" : "Try again"
304
+ }
305
+ ),
306
+ result.status !== "rendering" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => discard(result), children: "Discard" })
307
+ ] })
308
+ ] }, result.key);
309
+ }) })
310
+ ] });
311
+ }
312
+ export {
313
+ AiImagePanel
314
+ };
@@ -12,7 +12,7 @@ import {
12
12
  import {
13
13
  AiKnowledgeIndicator,
14
14
  useAiKnowledge
15
- } from "./chunk-NOYDVL6M.js";
15
+ } from "./chunk-6QHZNL6C.js";
16
16
  import "./chunk-KHQ44ZFA.js";
17
17
  import {
18
18
  AiReportButton,
@@ -30,14 +30,14 @@ import {
30
30
  extractNumbers,
31
31
  sanitizeGeneratedMarkdown,
32
32
  stripReasoning
33
- } from "./chunk-T42BH6YX.js";
33
+ } from "./chunk-CXLXGFG2.js";
34
34
  import {
35
35
  unavailableSentence,
36
36
  useAiStatus
37
37
  } from "./chunk-K4I3NJFL.js";
38
38
  import {
39
39
  Dialog
40
- } from "./chunk-VJPGZCFS.js";
40
+ } from "./chunk-UYJCL6OW.js";
41
41
 
42
42
  // src/Ai/AiStudyDialog.tsx
43
43
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-RP3DVNTI.js";
8
8
  import {
9
9
  Dialog
10
- } from "./chunk-VJPGZCFS.js";
10
+ } from "./chunk-UYJCL6OW.js";
11
11
 
12
12
  // src/Speech/AudioExportDialog.tsx
13
13
  import { useEffect, useId, useRef, useState } from "react";