@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.
- package/THIRD_PARTY_NOTICES.txt +205 -35
- package/dist/{AiDiagrams-VOCEZS6U.js → AiDiagrams-2IKKSBAU.js} +6 -4
- package/dist/{AiEditorAssistant-2MW6UYNI.js → AiEditorAssistant-ANYGVG7P.js} +128 -12
- package/dist/AiImagePanel-OXWEFOBC.js +314 -0
- package/dist/{AiStudyDialog-DQZVTBI7.js → AiStudyDialog-T3FY74HY.js} +3 -3
- package/dist/{AudioExportDialog-EICCBSSE.js → AudioExportDialog-CBMSLQBS.js} +1 -1
- package/dist/DocumentConflictDialog-4R4QMKBT.js +217 -0
- package/dist/{ExportDialog-LGWBXIWN.js → ExportDialog-FNFD53SY.js} +2 -2
- package/dist/{ExportToolbarControls-4LYTXGT3.js → ExportToolbarControls-ZOYH4I36.js} +146 -68
- package/dist/{GitUI-F3CNBURE.js → GitUI-3FANM7I3.js} +88 -155
- package/dist/{KeyboardShortcutsDialog-HVKDFP5Z.js → KeyboardShortcutsDialog-7XJTDIX2.js} +1 -1
- package/dist/{PresentationMaker-T526TBRJ.js → PresentationMaker-COLTVMTS.js} +3 -3
- package/dist/{QuickOpenDialog-3QEX7RZG.js → QuickOpenDialog-U2FWCEPA.js} +1 -1
- package/dist/{Settings-CfhY489G.d.ts → Settings-ButIB78y.d.ts} +20 -4
- package/dist/{ShareDialog-PF7D7ORP.js → ShareDialog-WOWHI6YI.js} +1 -1
- package/dist/{SpeechToolbarControl-E6OSJGHL.js → SpeechToolbarControl-BZW6IVZM.js} +1 -1
- package/dist/{TextFromNarration-ME6FAYWO.js → TextFromNarration-6BYTUKVZ.js} +2 -2
- package/dist/chunk-27E2OON2.js +90 -0
- package/dist/{chunk-5O5YTOXG.js → chunk-5YMULR6A.js} +13 -1
- package/dist/{chunk-NOYDVL6M.js → chunk-6QHZNL6C.js} +45 -17
- package/dist/{chunk-45YTIVYS.js → chunk-A7BK6QFL.js} +363 -26
- package/dist/{chunk-PRUEYW3G.js → chunk-BGA5UVKL.js} +58 -17
- package/dist/{chunk-T42BH6YX.js → chunk-CXLXGFG2.js} +37 -15
- package/dist/{chunk-7CSBRAMT.js → chunk-F7TR5EKX.js} +8 -6
- package/dist/chunk-HIKMOKBQ.js +37 -0
- package/dist/chunk-OW6JWRAA.js +66 -0
- package/dist/{chunk-BNKIPCXO.js → chunk-QXBV3G6T.js} +5 -3
- package/dist/{chunk-7HBKKXRV.js → chunk-TN7BY5N3.js} +722 -281
- package/dist/{chunk-VJPGZCFS.js → chunk-UYJCL6OW.js} +4 -2
- package/dist/{chunk-GFK4TCKN.js → chunk-WDNE5TI3.js} +21 -7
- package/dist/{chunk-KBUHWZCF.js → chunk-YX275NPE.js} +1 -1
- package/dist/editor.js +3 -3
- package/dist/export/index.d.ts +16 -162
- package/dist/export/index.js +6 -4
- package/dist/image-save-D18dJrUr.d.ts +182 -0
- package/dist/index.d.ts +15 -11
- package/dist/index.js +1365 -1078
- package/dist/{run-export-SPNNH32M.js → run-export-C3PLLWLS.js} +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js +3 -2
- package/package.json +8 -8
- package/src/styles/docblocks.css +349 -11
- package/dist/DocumentConflictDialog-DZ2BV2IH.js +0 -111
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AiDiagramDialog
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
16
|
+
} from "./chunk-6QHZNL6C.js";
|
|
17
|
+
import "./chunk-HIKMOKBQ.js";
|
|
17
18
|
import {
|
|
18
19
|
useConfirmDialog
|
|
19
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
40
|
+
} from "./chunk-UYJCL6OW.js";
|
|
41
41
|
|
|
42
42
|
// src/Ai/AiStudyDialog.tsx
|
|
43
43
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|