@bendyline/docblocks-react 2.7.5 → 2.7.6

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 (26) hide show
  1. package/THIRD_PARTY_NOTICES.txt +14 -14
  2. package/dist/{AiDiagrams-5P657GFC.js → AiDiagrams-VOCEZS6U.js} +4 -2
  3. package/dist/{AiEditorAssistant-V4MZRC6Q.js → AiEditorAssistant-2MW6UYNI.js} +51 -11
  4. package/dist/{AiStudyDialog-GSCFTG3G.js → AiStudyDialog-DQZVTBI7.js} +14 -4
  5. package/dist/{AudioExportDialog-IOYIEAER.js → AudioExportDialog-EICCBSSE.js} +1 -1
  6. package/dist/{ExportToolbarControls-HOV424TO.js → ExportToolbarControls-4LYTXGT3.js} +1 -1
  7. package/dist/{PresentationMaker-H5B44Q7P.js → PresentationMaker-T526TBRJ.js} +2 -2
  8. package/dist/{SpeechToolbarControl-KNI2C4JJ.js → SpeechToolbarControl-E6OSJGHL.js} +217 -71
  9. package/dist/{TextFromNarration-EYOMW2IW.js → TextFromNarration-ME6FAYWO.js} +1 -1
  10. package/dist/{WorkspaceSearchPanel-J6BKEZQJ.js → WorkspaceSearchPanel-MEBBTK7F.js} +3 -3
  11. package/dist/{chunk-I44HKDQX.js → chunk-3TSJQESW.js} +16 -3
  12. package/dist/chunk-45YTIVYS.js +1162 -0
  13. package/dist/{chunk-FBPCURCS.js → chunk-7HBKKXRV.js} +291 -82
  14. package/dist/{chunk-KBDZDFPU.js → chunk-BNKIPCXO.js} +4 -1
  15. package/dist/{chunk-2IYBBFQX.js → chunk-BZRYQENB.js} +59 -16
  16. package/dist/{chunk-DICE7VBJ.js → chunk-KHQ44ZFA.js} +16 -1
  17. package/dist/chunk-NOYDVL6M.js +180 -0
  18. package/dist/{chunk-USKX66SA.js → chunk-PRUEYW3G.js} +1 -1
  19. package/dist/{chunk-CUCWCZRO.js → chunk-T42BH6YX.js} +122 -91
  20. package/dist/editor.js +2 -2
  21. package/dist/index.js +20 -19
  22. package/dist/settings/index.js +2 -1
  23. package/package.json +5 -5
  24. package/src/styles/docblocks.css +501 -11
  25. package/dist/chunk-I4JURPYS.js +0 -812
  26. package/dist/{QuickOpenDialog-ENVFGIHS.js → QuickOpenDialog-3QEX7RZG.js} +3 -3
@@ -1,18 +1,44 @@
1
+ import {
2
+ HighlightedText
3
+ } from "./chunk-TBLZBNQF.js";
1
4
  import {
2
5
  Dialog
3
6
  } from "./chunk-VJPGZCFS.js";
4
7
 
5
8
  // src/Settings/AiSettings.tsx
6
- import { useCallback, useEffect as useEffect2, useId, useRef as useRef2, useState as useState2 } from "react";
9
+ import { useCallback, useEffect as useEffect2, useId as useId2, useRef as useRef2, useState as useState2 } from "react";
7
10
 
8
11
  // src/Settings/AiKnowledgeSettings.tsx
9
- import { useEffect, useRef, useState } from "react";
12
+ import { useEffect, useId, useRef, useState } from "react";
13
+ import { createPortal } from "react-dom";
10
14
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
15
  function sizedArticle(bytes) {
12
16
  const megabytes = String(Math.max(1, Math.round(bytes / 1024 ** 2)));
13
17
  const an = megabytes.startsWith("8") || megabytes.length % 3 === 2 && (megabytes.startsWith("11") || megabytes.startsWith("18"));
14
18
  return `${an ? "an" : "a"} ${megabytes} MB`;
15
19
  }
20
+ function catalogFacts(catalog, version) {
21
+ return [
22
+ `Version ${version}`,
23
+ catalog.documents === null ? null : `${catalog.documents.toLocaleString()} documents`,
24
+ catalog.downloadBytes === null ? null : `${(catalog.downloadBytes / 1024 ** 2).toFixed(1)} MB`
25
+ ].filter((part) => part !== null).join(" \xB7 ");
26
+ }
27
+ function catalogSummary(catalog) {
28
+ if (catalog.state === "downloading")
29
+ return catalog.percent === null ? "Downloading\u2026" : `Downloading ${Math.round(catalog.percent)}%`;
30
+ if (catalog.state === "error") return "Needs attention";
31
+ const use = catalog.enabled ? "In use" : "Not in use";
32
+ return catalog.updateAvailable ? `${use} \xB7 Update available` : use;
33
+ }
34
+ function matchRanges(text, query) {
35
+ if (!query) return [];
36
+ const haystack = text.toLowerCase();
37
+ const ranges = [];
38
+ for (let at = haystack.indexOf(query); at >= 0; at = haystack.indexOf(query, at + query.length))
39
+ ranges.push([at, at + query.length]);
40
+ return ranges;
41
+ }
16
42
  function AiKnowledgeSettings({ knowledge }) {
17
43
  const [state, setState] = useState(null);
18
44
  const [error, setError] = useState(null);
@@ -20,9 +46,10 @@ function AiKnowledgeSettings({ knowledge }) {
20
46
  const [unsupported, setUnsupported] = useState(null);
21
47
  const [busy, setBusy] = useState(false);
22
48
  const [revision, setRevision] = useState(0);
23
- const [browse, setBrowse] = useState(false);
24
- const [query, setQuery] = useState("");
49
+ const [adding, setAdding] = useState(false);
50
+ const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
25
51
  const [removing, setRemoving] = useState(null);
52
+ const binHeadingId = useId();
26
53
  const generation = useRef(0);
27
54
  useEffect(
28
55
  () => () => {
@@ -33,7 +60,7 @@ function AiKnowledgeSettings({ knowledge }) {
33
60
  useEffect(() => {
34
61
  let live = true;
35
62
  let timer;
36
- const refresh = async () => {
63
+ const refresh2 = async () => {
37
64
  try {
38
65
  const result = await knowledge.state();
39
66
  if (!live) return;
@@ -46,13 +73,13 @@ function AiKnowledgeSettings({ knowledge }) {
46
73
  setLoadError(null);
47
74
  setUnsupported(null);
48
75
  if (result.value.improvement?.downloading || result.value.catalogs.some((catalog) => catalog.state === "downloading")) {
49
- timer = setTimeout(() => void refresh(), 1500);
76
+ timer = setTimeout(() => void refresh2(), 1500);
50
77
  }
51
78
  } catch {
52
79
  if (live) setLoadError("Could not refresh knowledge catalogs. Try again.");
53
80
  }
54
81
  };
55
- void refresh();
82
+ void refresh2();
56
83
  return () => {
57
84
  live = false;
58
85
  if (timer) clearTimeout(timer);
@@ -76,9 +103,21 @@ function AiKnowledgeSettings({ knowledge }) {
76
103
  if (current === generation.current) setBusy(false);
77
104
  }
78
105
  };
79
- const visible = state?.catalogs.filter(
80
- (catalog) => (browse || catalog.installedVersion !== null || catalog.state === "downloading") && `${catalog.name} ${catalog.description}`.toLowerCase().includes(query.toLowerCase())
106
+ const refresh = () => setRevision((value) => value + 1);
107
+ const showAdd = (open) => {
108
+ setError(null);
109
+ setAdding(open);
110
+ if (open) refresh();
111
+ };
112
+ const toggle = (catalogId) => setExpanded((current) => {
113
+ const next = new Set(current);
114
+ if (!next.delete(catalogId)) next.add(catalogId);
115
+ return next;
116
+ });
117
+ const downloaded = state?.catalogs.filter(
118
+ (catalog) => catalog.installedVersion !== null || catalog.state === "downloading"
81
119
  ) ?? [];
120
+ const problem = error ?? loadError;
82
121
  if (unsupported)
83
122
  return /* @__PURE__ */ jsxs("section", { className: "db-settings-ai-knowledge", "aria-label": "Knowledge catalogs", children: [
84
123
  /* @__PURE__ */ jsx("h3", { className: "db-settings-select-header", children: "Knowledge catalogs" }),
@@ -87,8 +126,17 @@ function AiKnowledgeSettings({ knowledge }) {
87
126
  return /* @__PURE__ */ jsxs("section", { className: "db-settings-ai-knowledge", "aria-label": "Knowledge catalogs", children: [
88
127
  /* @__PURE__ */ jsx("h3", { className: "db-settings-select-header", children: "Knowledge catalogs" }),
89
128
  /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: "Add reference material for AI writing, review, and chat. Relevant passages from enabled catalogs are included in each request." }),
90
- (error ?? loadError) && /* @__PURE__ */ jsx("p", { className: "db-settings-hint db-settings-ai-error", role: "alert", children: error ?? loadError }),
129
+ problem && !adding && /* @__PURE__ */ jsx("p", { className: "db-settings-hint db-settings-ai-error", role: "alert", children: problem }),
91
130
  !state && !loadError && /* @__PURE__ */ jsx("p", { className: "db-settings-hint", role: "status", children: "Loading knowledge catalogs\u2026" }),
131
+ !state && loadError && /* @__PURE__ */ jsx(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ className: "db-settings-action db-settings-action--secondary",
136
+ onClick: refresh,
137
+ children: "Try again"
138
+ }
139
+ ),
92
140
  state?.improvement && !state.improvement.downloading && /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: /* @__PURE__ */ jsxs(
93
141
  "button",
94
142
  {
@@ -103,52 +151,90 @@ function AiKnowledgeSettings({ knowledge }) {
103
151
  ]
104
152
  }
105
153
  ) }),
106
- /* @__PURE__ */ jsxs("div", { className: "db-settings-ai-knowledge-actions", children: [
107
- /* @__PURE__ */ jsx(
108
- "button",
109
- {
110
- type: "button",
111
- className: "db-settings-action db-settings-action--secondary",
112
- disabled: busy,
113
- onClick: () => setBrowse(!browse),
114
- children: browse ? "Show downloaded catalogs" : "Browse additional catalogs"
115
- }
116
- ),
117
- /* @__PURE__ */ jsx(
118
- "button",
119
- {
120
- type: "button",
121
- className: "db-settings-action db-settings-action--secondary",
122
- disabled: busy,
123
- onClick: () => setRevision((value) => value + 1),
124
- children: "Refresh catalogs"
125
- }
126
- )
127
- ] }),
128
- state && /* @__PURE__ */ jsxs("label", { className: "db-settings-select", children: [
129
- /* @__PURE__ */ jsx("span", { className: "db-settings-select-header", children: "Find a catalog" }),
130
- /* @__PURE__ */ jsx(
131
- "input",
154
+ state && /* @__PURE__ */ jsxs("div", { className: "db-knowledge-bin", children: [
155
+ /* @__PURE__ */ jsxs("div", { className: "db-knowledge-bin-header", children: [
156
+ /* @__PURE__ */ jsx("h4", { id: binHeadingId, className: "db-knowledge-bin-title", children: "Downloaded catalogs" }),
157
+ /* @__PURE__ */ jsx("span", { className: "db-knowledge-bin-count", "aria-hidden": "true", children: downloaded.length }),
158
+ /* @__PURE__ */ jsx(
159
+ "button",
160
+ {
161
+ type: "button",
162
+ className: "db-settings-action db-settings-action--secondary",
163
+ onClick: () => showAdd(true),
164
+ children: "Add catalog\u2026"
165
+ }
166
+ )
167
+ ] }),
168
+ downloaded.length === 0 ? /* @__PURE__ */ jsx("p", { className: "db-settings-hint db-knowledge-bin-empty", children: "No catalogs downloaded yet. Add a catalog to give AI writing, review, and chat reference material." }) : /* @__PURE__ */ jsx("ul", { className: "db-knowledge-bin-list", "aria-labelledby": binHeadingId, children: downloaded.map((catalog) => /* @__PURE__ */ jsx(
169
+ DownloadedCatalog,
132
170
  {
133
- className: "db-settings-select-input",
134
- type: "search",
135
- value: query,
136
- onChange: (event) => setQuery(event.currentTarget.value)
137
- }
138
- )
171
+ catalog,
172
+ expanded: expanded.has(catalog.id),
173
+ removing: removing === catalog.id,
174
+ busy,
175
+ onToggle: () => toggle(catalog.id),
176
+ onRemoving: (confirming) => setRemoving(confirming ? catalog.id : null),
177
+ onAction: (action) => void update(action)
178
+ },
179
+ catalog.id
180
+ )) })
139
181
  ] }),
140
- state && visible.length === 0 && /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: browse ? "No matching catalogs are available." : "No downloaded catalogs match. Browse additional catalogs to add reference material." }),
141
- /* @__PURE__ */ jsx("ul", { className: "db-settings-ai-knowledge-list", children: visible.map((catalog) => /* @__PURE__ */ jsxs("li", { children: [
142
- /* @__PURE__ */ jsx("strong", { children: catalog.name }),
182
+ adding && state && /* @__PURE__ */ jsx(
183
+ AddCatalogDialog,
184
+ {
185
+ catalogs: state.catalogs,
186
+ busy,
187
+ problem,
188
+ onAction: (action) => void update(action),
189
+ onRefresh: refresh,
190
+ onClose: () => showAdd(false)
191
+ }
192
+ )
193
+ ] });
194
+ }
195
+ function DownloadedCatalog({
196
+ catalog,
197
+ expanded,
198
+ removing,
199
+ busy,
200
+ onToggle,
201
+ onRemoving,
202
+ onAction
203
+ }) {
204
+ const bodyId = useId();
205
+ return /* @__PURE__ */ jsxs("li", { className: "db-knowledge-item", children: [
206
+ /* @__PURE__ */ jsxs(
207
+ "button",
208
+ {
209
+ type: "button",
210
+ className: "db-knowledge-item-toggle",
211
+ "aria-expanded": expanded,
212
+ "aria-controls": expanded ? bodyId : void 0,
213
+ onClick: onToggle,
214
+ children: [
215
+ /* @__PURE__ */ jsx(
216
+ "span",
217
+ {
218
+ className: `db-knowledge-item-caret${expanded ? " db-knowledge-item-caret--open" : ""}`,
219
+ "aria-hidden": "true"
220
+ }
221
+ ),
222
+ /* @__PURE__ */ jsx("span", { className: "db-knowledge-item-name", children: catalog.name }),
223
+ /* @__PURE__ */ jsx(
224
+ "span",
225
+ {
226
+ className: `db-knowledge-item-summary${catalog.state === "error" ? " db-knowledge-item-summary--error" : ""}`,
227
+ children: catalogSummary(catalog)
228
+ }
229
+ )
230
+ ]
231
+ }
232
+ ),
233
+ expanded && /* @__PURE__ */ jsxs("div", { id: bodyId, className: "db-knowledge-item-body", children: [
143
234
  /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: catalog.description }),
144
- /* @__PURE__ */ jsxs("p", { className: "db-settings-hint", children: [
145
- "Version ",
146
- catalog.installedVersion ?? catalog.version,
147
- catalog.documents === null ? "" : ` \xB7 ${catalog.documents.toLocaleString()} documents`,
148
- catalog.downloadBytes === null ? "" : ` \xB7 ${(catalog.downloadBytes / 1024 ** 2).toFixed(1)} MB download`
149
- ] }),
235
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: catalogFacts(catalog, catalog.installedVersion ?? catalog.version) }),
150
236
  catalog.message && /* @__PURE__ */ jsx("p", { className: "db-settings-hint", role: catalog.state === "error" ? "alert" : "status", children: catalog.message }),
151
- catalog.state === "downloading" ? /* @__PURE__ */ jsxs(Fragment, { children: [
237
+ catalog.state === "downloading" ? /* @__PURE__ */ jsxs("div", { className: "db-settings-ai-knowledge-actions", children: [
152
238
  /* @__PURE__ */ jsx(
153
239
  "progress",
154
240
  {
@@ -163,19 +249,19 @@ function AiKnowledgeSettings({ knowledge }) {
163
249
  type: "button",
164
250
  className: "db-settings-action db-settings-action--secondary",
165
251
  disabled: busy,
166
- onClick: () => void update({ action: "cancel", catalogId: catalog.id }),
252
+ onClick: () => onAction({ action: "cancel", catalogId: catalog.id }),
167
253
  children: "Cancel download"
168
254
  }
169
255
  )
170
256
  ] }) : /* @__PURE__ */ jsxs("div", { className: "db-settings-ai-knowledge-actions", children: [
171
- catalog.installedVersion !== null && /* @__PURE__ */ jsxs("label", { className: "db-settings-checkbox", children: [
257
+ /* @__PURE__ */ jsxs("label", { className: "db-settings-checkbox", children: [
172
258
  /* @__PURE__ */ jsx(
173
259
  "input",
174
260
  {
175
261
  type: "checkbox",
176
262
  checked: catalog.enabled,
177
263
  disabled: busy,
178
- onChange: (event) => void update({
264
+ onChange: (event) => onAction({
179
265
  action: event.currentTarget.checked ? "enable" : "disable",
180
266
  catalogId: catalog.id
181
267
  })
@@ -184,23 +270,26 @@ function AiKnowledgeSettings({ knowledge }) {
184
270
  "Use ",
185
271
  catalog.name
186
272
  ] }),
187
- (catalog.installedVersion === null || catalog.updateAvailable) && /* @__PURE__ */ jsx(
273
+ catalog.updateAvailable && /* @__PURE__ */ jsxs(
188
274
  "button",
189
275
  {
190
276
  type: "button",
191
277
  className: "db-settings-action",
192
278
  disabled: busy,
193
- onClick: () => void update({ action: "install", catalogId: catalog.id }),
194
- children: catalog.updateAvailable ? `Update to ${catalog.version}` : "Download catalog"
279
+ onClick: () => onAction({ action: "install", catalogId: catalog.id }),
280
+ children: [
281
+ "Update to ",
282
+ catalog.version
283
+ ]
195
284
  }
196
285
  ),
197
- catalog.installedVersion !== null && /* @__PURE__ */ jsxs(
286
+ /* @__PURE__ */ jsxs(
198
287
  "button",
199
288
  {
200
289
  type: "button",
201
290
  className: "db-settings-action db-settings-action--secondary",
202
291
  disabled: busy,
203
- onClick: () => setRemoving(catalog.id),
292
+ onClick: () => onRemoving(true),
204
293
  children: [
205
294
  "Remove ",
206
295
  catalog.name,
@@ -209,34 +298,154 @@ function AiKnowledgeSettings({ knowledge }) {
209
298
  }
210
299
  )
211
300
  ] }),
212
- removing === catalog.id && /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": `Confirm removal of ${catalog.name}`, children: [
301
+ removing && /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": `Confirm removal of ${catalog.name}`, children: [
213
302
  /* @__PURE__ */ jsxs("p", { className: "db-settings-hint", children: [
214
303
  "Remove ",
215
304
  catalog.name,
216
305
  " from the connected AI service? Other apps using that service will also lose access to this catalog."
217
306
  ] }),
307
+ /* @__PURE__ */ jsxs("div", { className: "db-settings-ai-knowledge-actions", children: [
308
+ /* @__PURE__ */ jsx(
309
+ "button",
310
+ {
311
+ type: "button",
312
+ className: "db-settings-action",
313
+ disabled: busy,
314
+ onClick: () => onAction({ action: "remove", catalogId: catalog.id }),
315
+ children: "Confirm removal"
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsx(
319
+ "button",
320
+ {
321
+ type: "button",
322
+ className: "db-settings-action db-settings-action--secondary",
323
+ onClick: () => onRemoving(false),
324
+ children: "Keep catalog"
325
+ }
326
+ )
327
+ ] })
328
+ ] })
329
+ ] })
330
+ ] });
331
+ }
332
+ function AddCatalogDialog({
333
+ catalogs,
334
+ busy,
335
+ problem,
336
+ onAction,
337
+ onRefresh,
338
+ onClose
339
+ }) {
340
+ const [query, setQuery] = useState("");
341
+ const searchRef = useRef(null);
342
+ const [order] = useState(
343
+ () => [...catalogs].sort((a, b) => Number(a.installedVersion !== null) - Number(b.installedVersion !== null)).map((catalog) => catalog.id)
344
+ );
345
+ const rank = (catalog) => {
346
+ const index = order.indexOf(catalog.id);
347
+ return index < 0 ? order.length : index;
348
+ };
349
+ const needle = query.trim().toLowerCase();
350
+ const matches = catalogs.filter((catalog) => `${catalog.name} ${catalog.description}`.toLowerCase().includes(needle)).sort((a, b) => rank(a) - rank(b));
351
+ const dialog = /* @__PURE__ */ jsxs(
352
+ Dialog,
353
+ {
354
+ title: "Add knowledge catalog",
355
+ size: "wide",
356
+ onClose,
357
+ initialFocusRef: searchRef,
358
+ className: "db-knowledge-add-dialog",
359
+ bodyClassName: "db-knowledge-add-body",
360
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
218
361
  /* @__PURE__ */ jsx(
219
362
  "button",
220
363
  {
221
364
  type: "button",
222
- className: "db-settings-action",
365
+ className: "db-settings-action db-settings-action--secondary",
223
366
  disabled: busy,
224
- onClick: () => void update({ action: "remove", catalogId: catalog.id }),
225
- children: "Confirm removal"
367
+ onClick: onRefresh,
368
+ children: "Refresh catalogs"
226
369
  }
227
370
  ),
228
- /* @__PURE__ */ jsx(
229
- "button",
230
- {
231
- type: "button",
232
- className: "db-settings-action db-settings-action--secondary",
233
- onClick: () => setRemoving(null),
234
- children: "Keep catalog"
235
- }
236
- )
237
- ] })
238
- ] }, catalog.id)) })
239
- ] });
371
+ /* @__PURE__ */ jsx("button", { type: "button", className: "db-settings-action", onClick: onClose, children: "Done" })
372
+ ] }),
373
+ children: [
374
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: "Catalogs come from the connected AI service. Once one is downloaded, relevant passages from it are included in AI writing, review, and chat." }),
375
+ problem && /* @__PURE__ */ jsx("p", { className: "db-settings-hint db-settings-ai-error", role: "alert", children: problem }),
376
+ /* @__PURE__ */ jsxs("label", { className: "db-settings-text db-knowledge-search", children: [
377
+ "Search catalogs",
378
+ /* @__PURE__ */ jsx(
379
+ "input",
380
+ {
381
+ ref: searchRef,
382
+ className: "db-settings-text-input",
383
+ type: "search",
384
+ value: query,
385
+ onChange: (event) => setQuery(event.currentTarget.value)
386
+ }
387
+ )
388
+ ] }),
389
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", role: "status", children: matches.length > 0 ? "" : catalogs.length === 0 ? "The connected AI service doesn\u2019t offer any catalogs right now." : `No catalogs match \u201C${query.trim()}\u201D.` }),
390
+ matches.length > 0 && /* @__PURE__ */ jsx("ul", { className: "db-knowledge-catalog-list", "aria-label": "Catalogs", children: matches.map((catalog) => /* @__PURE__ */ jsxs("li", { className: "db-knowledge-catalog", children: [
391
+ /* @__PURE__ */ jsxs("div", { className: "db-knowledge-catalog-text", children: [
392
+ /* @__PURE__ */ jsx("strong", { className: "db-knowledge-catalog-name", children: /* @__PURE__ */ jsx(HighlightedText, { text: catalog.name, ranges: matchRanges(catalog.name, needle) }) }),
393
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: /* @__PURE__ */ jsx(
394
+ HighlightedText,
395
+ {
396
+ text: catalog.description,
397
+ ranges: matchRanges(catalog.description, needle)
398
+ }
399
+ ) }),
400
+ /* @__PURE__ */ jsx("p", { className: "db-settings-hint", children: catalogFacts(catalog, catalog.version) }),
401
+ catalog.message && /* @__PURE__ */ jsx(
402
+ "p",
403
+ {
404
+ className: "db-settings-hint",
405
+ role: catalog.state === "error" ? "alert" : "status",
406
+ children: catalog.message
407
+ }
408
+ )
409
+ ] }),
410
+ /* @__PURE__ */ jsx("div", { className: "db-knowledge-catalog-action", children: catalog.state === "downloading" ? /* @__PURE__ */ jsxs(Fragment, { children: [
411
+ /* @__PURE__ */ jsx(
412
+ "progress",
413
+ {
414
+ max: 100,
415
+ value: catalog.percent ?? void 0,
416
+ "aria-label": `${catalog.name} download progress`
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ className: "db-settings-action db-settings-action--secondary",
424
+ disabled: busy,
425
+ "aria-label": `Cancel ${catalog.name} download`,
426
+ onClick: () => onAction({ action: "cancel", catalogId: catalog.id }),
427
+ children: "Cancel"
428
+ }
429
+ )
430
+ ] }) : catalog.installedVersion !== null ? /* @__PURE__ */ jsxs("span", { className: "db-knowledge-catalog-added", children: [
431
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2713 " }),
432
+ "Downloaded"
433
+ ] }) : /* @__PURE__ */ jsx(
434
+ "button",
435
+ {
436
+ type: "button",
437
+ className: "db-settings-action",
438
+ disabled: busy,
439
+ "aria-label": `Download ${catalog.name}`,
440
+ onClick: () => onAction({ action: "install", catalogId: catalog.id }),
441
+ children: "Download"
442
+ }
443
+ ) })
444
+ ] }, catalog.id)) })
445
+ ]
446
+ }
447
+ );
448
+ return typeof document === "undefined" ? dialog : createPortal(dialog, document.body);
240
449
  }
241
450
 
242
451
  // src/Settings/AiSettings.tsx
@@ -329,7 +538,7 @@ function AiSettingsControls({ ai }) {
329
538
  const [failure, setFailure] = useState2(null);
330
539
  const mounted = useRef2(true);
331
540
  const installHandle = useRef2(null);
332
- const modelSelectId = useId();
541
+ const modelSelectId = useId2();
333
542
  useEffect2(() => {
334
543
  mounted.current = true;
335
544
  setProviderInstalled(false);
@@ -637,7 +846,7 @@ function AiSettingsControls({ ai }) {
637
846
  }
638
847
 
639
848
  // src/Settings/WorkspaceSettingsControls.tsx
640
- import { useEffect as useEffect3, useId as useId2, useState as useState3 } from "react";
849
+ import { useEffect as useEffect3, useId as useId3, useState as useState3 } from "react";
641
850
  import {
642
851
  DEFAULT_CATALOG_HTML_PATH,
643
852
  DEFAULT_CATALOG_JSON_PATH,
@@ -690,7 +899,7 @@ function WorkspaceSettingsControls({
690
899
  onRegenerate,
691
900
  regenerating = false
692
901
  }) {
693
- const id = useId2();
902
+ const id = useId3();
694
903
  const htmlEnabled = value.catalog?.html?.enabled === true;
695
904
  const jsonEnabled = value.catalog?.json?.enabled === true;
696
905
  const catalogEnabled = htmlEnabled || jsonEnabled;
@@ -992,7 +1201,7 @@ function WorkspaceSettingsControls({
992
1201
  import {
993
1202
  useCallback as useCallback2,
994
1203
  useEffect as useEffect4,
995
- useId as useId3,
1204
+ useId as useId4,
996
1205
  useLayoutEffect,
997
1206
  useRef as useRef3,
998
1207
  useState as useState4
@@ -1430,8 +1639,8 @@ function FontSchemePicker({ value, onChange }) {
1430
1639
  const triggerRef = useRef3(null);
1431
1640
  const listboxRef = useRef3(null);
1432
1641
  const optionRefs = useRef3([]);
1433
- const labelId = useId3();
1434
- const listboxId = useId3();
1642
+ const labelId = useId4();
1643
+ const listboxId = useId4();
1435
1644
  const close = useCallback2((returnFocus) => {
1436
1645
  setIsOpen(false);
1437
1646
  setMenuPosition(null);
@@ -22,6 +22,9 @@ function abortable(promise, signal) {
22
22
  );
23
23
  });
24
24
  }
25
+ function stripPauseEllipses(transcript) {
26
+ return transcript.replace(/(?:\.{3,}|…)+/gu, " ").replace(/\s+([,;:!?]|\.(?=\s|$))/gu, "$1").replace(/\s+/gu, " ").trim();
27
+ }
25
28
  function createSpeechInputProvider(speech, options) {
26
29
  const transcribe = speech.transcribe;
27
30
  return {
@@ -51,7 +54,7 @@ function createSpeechInputProvider(speech, options) {
51
54
  signal
52
55
  );
53
56
  if (!result.ok) throw new Error(result.error.message);
54
- return { text: result.value.text };
57
+ return { text: stripPauseEllipses(result.value.text) };
55
58
  }
56
59
  };
57
60
  }
@@ -57,6 +57,7 @@ var ProgressivePlayer = class _ProgressivePlayer {
57
57
  this.playbackOffset = 0;
58
58
  }
59
59
  await this.context.resume();
60
+ if (this.playing || this.disposed) return this.playing;
60
61
  this.stopSources();
61
62
  this.playing = true;
62
63
  this.playbackStartedAt = this.context.currentTime + 0.025;
@@ -82,6 +83,19 @@ var ProgressivePlayer = class _ProgressivePlayer {
82
83
  if (wasPlaying) void this.play();
83
84
  else this.emit();
84
85
  }
86
+ /** Drop the whole timeline but keep the audio context, to start again from a new place. */
87
+ reset() {
88
+ if (this.disposed) return;
89
+ this.playing = false;
90
+ this.stopSources();
91
+ this.clearEndTimer();
92
+ this.chunks.length = 0;
93
+ this.pending.clear();
94
+ this.bufferedDuration = 0;
95
+ this.playbackOffset = 0;
96
+ this.complete = false;
97
+ this.emit();
98
+ }
85
99
  /** No more chunks will arrive. */
86
100
  finish() {
87
101
  this.complete = true;
@@ -173,25 +187,54 @@ var ProgressivePlayer = class _ProgressivePlayer {
173
187
  if (!this.disposed) this.onChange(this.snapshot());
174
188
  }
175
189
  };
190
+ var MIN_AHEAD_SECONDS = 3;
191
+ var LOOKAHEAD_SECONDS = 60;
176
192
  var AutoPlayGate = class {
177
193
  constructor() {
178
- this.firstAt = null;
179
- this.firstDuration = 0;
180
- this.firstCharacters = 0;
181
- }
182
- /** Call after each appended chunk; true when it is safe to start playing. */
183
- ready(snapshot, progress, nowMs) {
184
- if (this.firstAt === null) {
185
- this.firstAt = nowMs;
186
- this.firstDuration = snapshot.bufferedDuration;
187
- this.firstCharacters = progress?.completedCharacters ?? 0;
194
+ this.spokenCharacters = 0;
195
+ this.spokenSeconds = 0;
196
+ this.timedCharacters = 0;
197
+ this.busySeconds = 0;
198
+ this.lastAt = null;
199
+ }
200
+ /**
201
+ * Record a synthesized chunk. The first chunk after `restart()` only marks
202
+ * the clock, so model loading and skips are not counted against synthesis.
203
+ */
204
+ record(characters, audioSeconds, nowMs) {
205
+ this.spokenCharacters += characters;
206
+ this.spokenSeconds += audioSeconds;
207
+ if (this.lastAt !== null) {
208
+ this.timedCharacters += characters;
209
+ this.busySeconds += (nowMs - this.lastAt) / 1e3;
210
+ }
211
+ this.lastAt = nowMs;
212
+ }
213
+ /** Synthesis stopped and starts again; the next gap is not a rate sample. */
214
+ restart() {
215
+ this.lastAt = null;
216
+ }
217
+ /**
218
+ * True when `ahead` seconds of buffered audio should carry playback through
219
+ * the sentences still to be synthesized, given as character counts in order.
220
+ * The first of them has been synthesizing since the last recorded chunk.
221
+ */
222
+ ready(ahead, upcoming, nowMs) {
223
+ if (this.busySeconds <= 0.25 || this.timedCharacters <= 0 || this.spokenCharacters <= 0) {
224
+ return false;
225
+ }
226
+ const charactersPerSecond = this.timedCharacters / this.busySeconds;
227
+ const secondsPerCharacter = this.spokenSeconds / this.spokenCharacters;
228
+ let arrives = this.lastAt === null ? 0 : -(nowMs - this.lastAt) / 1e3;
229
+ let startsAfter = 0;
230
+ let stall = 0;
231
+ for (const characters of upcoming) {
232
+ arrives = Math.max(0, arrives + characters / charactersPerSecond);
233
+ stall = Math.max(stall, arrives - startsAfter);
234
+ startsAfter += characters * secondsPerCharacter;
235
+ if (startsAfter >= LOOKAHEAD_SECONDS) break;
188
236
  }
189
- const elapsed = (nowMs - this.firstAt) / 1e3;
190
- const audioRate = elapsed > 0.25 ? (snapshot.bufferedDuration - this.firstDuration) / elapsed : 0;
191
- const characterRate = elapsed > 0.25 ? ((progress?.completedCharacters ?? 0) - this.firstCharacters) / elapsed : 0;
192
- const remaining = progress && characterRate > 0 ? (progress.totalCharacters - progress.completedCharacters) / characterRate : Number.POSITIVE_INFINITY;
193
- const ahead = snapshot.bufferedDuration - snapshot.currentTime;
194
- return ahead >= 4 && (audioRate >= 1.15 || ahead >= remaining + 3);
237
+ return ahead >= Math.max(MIN_AHEAD_SECONDS, stall);
195
238
  }
196
239
  };
197
240