@arbidocs/blocks 0.4.27 → 0.4.29

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 (107) hide show
  1. package/README.md +17 -0
  2. package/dist/agents/index.cjs +224 -212
  3. package/dist/agents/index.js +6 -6
  4. package/dist/agents/tasks/TaskRowMenu.d.ts +4 -1
  5. package/dist/agents/tasks/TaskRunsDialog.d.ts +3 -1
  6. package/dist/agents/tasks/taskRows.d.ts +2 -0
  7. package/dist/agents/workflows/WorkflowDocumentsField.d.ts +12 -0
  8. package/dist/agents/workflows/index.d.ts +2 -0
  9. package/dist/agents/workflows/workspaceScope.d.ts +18 -0
  10. package/dist/chat/ChatPage.d.ts +1 -1
  11. package/dist/chat/chat-page-types.d.ts +12 -1
  12. package/dist/chat/index.cjs +129 -129
  13. package/dist/chat/index.js +2 -2
  14. package/dist/chunk-4XJZ6AAY.js +407 -0
  15. package/dist/chunk-4XJZ6AAY.js.map +1 -0
  16. package/dist/{chunk-UOLMFC5K.js → chunk-6YVI74BC.js} +498 -108
  17. package/dist/chunk-6YVI74BC.js.map +1 -0
  18. package/dist/{chunk-CQ6CYPV4.js → chunk-7FF473XB.js} +7 -119
  19. package/dist/chunk-7FF473XB.js.map +1 -0
  20. package/dist/{chunk-ZEIOREJY.js → chunk-EHXSBH6U.js} +37 -6
  21. package/dist/chunk-EHXSBH6U.js.map +1 -0
  22. package/dist/chunk-EYPPB5CY.cjs +85 -0
  23. package/dist/chunk-EYPPB5CY.cjs.map +1 -0
  24. package/dist/{chunk-ENY6XXKR.js → chunk-I7FJS7WG.js} +155 -39
  25. package/dist/chunk-I7FJS7WG.js.map +1 -0
  26. package/dist/{chunk-557EU3G4.js → chunk-JLOYWXRB.js} +3 -3
  27. package/dist/{chunk-557EU3G4.js.map → chunk-JLOYWXRB.js.map} +1 -1
  28. package/dist/chunk-KF4EC5XU.js +68 -0
  29. package/dist/chunk-KF4EC5XU.js.map +1 -0
  30. package/dist/{chunk-3ZXXHROK.cjs → chunk-L6YRTT5M.cjs} +158 -39
  31. package/dist/chunk-L6YRTT5M.cjs.map +1 -0
  32. package/dist/chunk-S5HZBXZA.cjs +430 -0
  33. package/dist/chunk-S5HZBXZA.cjs.map +1 -0
  34. package/dist/{chunk-JCVZKCXC.cjs → chunk-TLQQV7WW.cjs} +37 -6
  35. package/dist/chunk-TLQQV7WW.cjs.map +1 -0
  36. package/dist/{chunk-NNH7N62N.cjs → chunk-VMCOH3AY.cjs} +497 -105
  37. package/dist/chunk-VMCOH3AY.cjs.map +1 -0
  38. package/dist/{chunk-JLPYOPCX.js → chunk-WSJVHSGN.js} +225 -36
  39. package/dist/chunk-WSJVHSGN.js.map +1 -0
  40. package/dist/{chunk-DCEI37AX.cjs → chunk-YVIOCITQ.cjs} +3 -3
  41. package/dist/{chunk-DCEI37AX.cjs.map → chunk-YVIOCITQ.cjs.map} +1 -1
  42. package/dist/{chunk-J22DXUJY.cjs → chunk-ZHLMDJCO.cjs} +226 -34
  43. package/dist/chunk-ZHLMDJCO.cjs.map +1 -0
  44. package/dist/{chunk-TL2DHSC5.cjs → chunk-ZXGBUHES.cjs} +5 -120
  45. package/dist/chunk-ZXGBUHES.cjs.map +1 -0
  46. package/dist/connect/docx.cjs +26 -8
  47. package/dist/connect/docx.cjs.map +1 -1
  48. package/dist/connect/docx.d.ts +1 -0
  49. package/dist/connect/docx.js +24 -7
  50. package/dist/connect/docx.js.map +1 -1
  51. package/dist/connect/index.cjs +303 -61
  52. package/dist/connect/index.cjs.map +1 -1
  53. package/dist/connect/index.d.ts +4 -0
  54. package/dist/connect/index.js +263 -25
  55. package/dist/connect/index.js.map +1 -1
  56. package/dist/connection/ConnectedAgentChatPanel.d.ts +7 -0
  57. package/dist/connection/ConnectedNativeDocxEditor.d.ts +2 -1
  58. package/dist/connection/ConnectedSkillUpload.d.ts +2 -0
  59. package/dist/connection/ConnectedStreamedWordArtifact.d.ts +4 -0
  60. package/dist/connection/ConnectedTaskAccessRequest.d.ts +4 -0
  61. package/dist/connection/ai.d.ts +4 -1
  62. package/dist/connection/useConnectedWorkNotifications.d.ts +21 -0
  63. package/dist/docx/DocxMentions.d.ts +9 -2
  64. package/dist/docx/NativeDocxEditor.d.ts +11 -3
  65. package/dist/docx/StreamedWordArtifact.d.ts +15 -0
  66. package/dist/docx/index.cjs +16 -8
  67. package/dist/docx/index.d.ts +3 -2
  68. package/dist/docx/index.js +2 -2
  69. package/dist/docx/streamWordDocument.d.ts +12 -0
  70. package/dist/facts/index.cjs +1 -1
  71. package/dist/facts/index.js +1 -1
  72. package/dist/files/index.cjs +3 -3
  73. package/dist/files/index.js +3 -3
  74. package/dist/index.cjs +778 -766
  75. package/dist/index.js +15 -15
  76. package/dist/settings/index.cjs +42 -232
  77. package/dist/settings/index.cjs.map +1 -1
  78. package/dist/settings/index.js +7 -203
  79. package/dist/settings/index.js.map +1 -1
  80. package/dist/site/index.cjs +5 -5
  81. package/dist/site/index.js +1 -1
  82. package/dist/studio/index.cjs +7 -6
  83. package/dist/studio/index.cjs.map +1 -1
  84. package/dist/studio/index.js +5 -4
  85. package/dist/studio/index.js.map +1 -1
  86. package/dist/workspaces/index.cjs +2 -2
  87. package/dist/workspaces/index.js +2 -2
  88. package/package.json +3 -3
  89. package/src/docx/styles.css +13 -0
  90. package/dist/chunk-3ZXXHROK.cjs.map +0 -1
  91. package/dist/chunk-5TRALRWN.js +0 -42
  92. package/dist/chunk-5TRALRWN.js.map +0 -1
  93. package/dist/chunk-CQ6CYPV4.js.map +0 -1
  94. package/dist/chunk-CQCC7LE7.js +0 -210
  95. package/dist/chunk-CQCC7LE7.js.map +0 -1
  96. package/dist/chunk-ENY6XXKR.js.map +0 -1
  97. package/dist/chunk-EUOGPUMJ.cjs +0 -59
  98. package/dist/chunk-EUOGPUMJ.cjs.map +0 -1
  99. package/dist/chunk-J22DXUJY.cjs.map +0 -1
  100. package/dist/chunk-JCVZKCXC.cjs.map +0 -1
  101. package/dist/chunk-JLPYOPCX.js.map +0 -1
  102. package/dist/chunk-NNH7N62N.cjs.map +0 -1
  103. package/dist/chunk-RFIBUWLF.cjs +0 -232
  104. package/dist/chunk-RFIBUWLF.cjs.map +0 -1
  105. package/dist/chunk-TL2DHSC5.cjs.map +0 -1
  106. package/dist/chunk-UOLMFC5K.js.map +0 -1
  107. package/dist/chunk-ZEIOREJY.js.map +0 -1
@@ -1,9 +1,12 @@
1
1
  "use client";
2
2
  import { filterMentionCandidates, MentionMenu, findMentionQuery, extractMentions } from './chunk-AA3PT6KR.js';
3
3
  import { unzipSync, strFromU8, strToU8, zipSync } from 'fflate';
4
- import React2, { useRef, useEffect, useState, useLayoutEffect } from 'react';
5
- import { useReview, DocxEditorReview, reviewModule } from '@docx-editor.dev/pro/react';
4
+ import React2, { createContext, useContext, useState, useRef, useEffect, useLayoutEffect } from 'react';
5
+ import { createPortal } from 'react-dom';
6
+ import { useReviewItem, useReview, DocxEditorReview, reviewModule } from '@docx-editor.dev/pro/react';
7
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, splitMarkdownBlocks, AiMarkdown } from '@arbidocs/react/ui';
6
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import { renderToStaticMarkup } from 'react-dom/server';
7
10
  import * as Y from 'yjs';
8
11
  import { useChromeTranslate, DocxEditor, createFontSource } from '@docx-editor.dev/react';
9
12
  import { collaborationModule } from '@docx-editor.dev/pro';
@@ -90,14 +93,130 @@ function mentionCandidates(mentions) {
90
93
  function mentionedDocxRecipients(text, mentions) {
91
94
  return extractMentions(text, mentionCandidates(mentions)).map((person) => person.id);
92
95
  }
96
+ var ActivityContext = createContext({});
97
+ function DocxCommentActivity() {
98
+ const item = useReviewItem();
99
+ const review = useReview();
100
+ const activity = useContext(ActivityContext);
101
+ const [reading, setReading] = useState(false);
102
+ if (!item || item.kind !== "comment") return null;
103
+ const commentId = item.parentId || item.item.id;
104
+ const thread = review.items.filter(
105
+ (entry) => entry.kind === "comment" && (entry.item.id === commentId || entry.parentId === commentId)
106
+ );
107
+ const statuses = Object.entries(activity).filter(([, run]) => run.commentId === commentId);
108
+ const labels = {
109
+ delivering: "Delivering mention\u2026",
110
+ queued: "Queued\u2026",
111
+ in_progress: "Working\u2026",
112
+ completed: "Finished",
113
+ failed: "Failed",
114
+ cancelled: "Stopped",
115
+ incomplete: "Incomplete",
116
+ unknown: "Status unavailable"
117
+ };
118
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-comment-extra", "data-review-selectable": "", children: [
119
+ statuses.map(([id, run]) => /* @__PURE__ */ jsxs(
120
+ "div",
121
+ {
122
+ role: "status",
123
+ "data-testid": "docx-comment-agent-status",
124
+ "data-status": run.status,
125
+ className: "native-docx-comment-activity",
126
+ children: [
127
+ ["delivering", "queued", "in_progress"].includes(run.status) && /* @__PURE__ */ jsx("span", { className: "native-docx-agent-spinner", "aria-hidden": "true" }),
128
+ run.label,
129
+ ": ",
130
+ labels[run.status]
131
+ ]
132
+ },
133
+ id
134
+ )),
135
+ /* @__PURE__ */ jsx(
136
+ "button",
137
+ {
138
+ type: "button",
139
+ "data-testid": "docx-read-full-thread",
140
+ onClick: (event) => {
141
+ event.stopPropagation();
142
+ setReading(true);
143
+ },
144
+ children: "Read full thread"
145
+ }
146
+ ),
147
+ /* @__PURE__ */ jsx(Dialog, { open: reading, onOpenChange: setReading, children: /* @__PURE__ */ jsxs(
148
+ DialogContent,
149
+ {
150
+ className: "max-h-[85dvh] overflow-y-auto",
151
+ onClick: (event) => event.stopPropagation(),
152
+ children: [
153
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Document comment" }) }),
154
+ thread.map((entry) => /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-b pb-4 last:border-0", children: [
155
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: entry.author || "Reviewer" }),
156
+ /* @__PURE__ */ jsx(
157
+ "p",
158
+ {
159
+ className: "whitespace-pre-wrap break-words text-sm",
160
+ "data-testid": "docx-full-comment-text",
161
+ children: entry.text
162
+ }
163
+ )
164
+ ] }, entry.key)),
165
+ statuses.map(([id, run]) => /* @__PURE__ */ jsxs("p", { role: "status", className: "text-sm", children: [
166
+ run.label,
167
+ ": ",
168
+ labels[run.status]
169
+ ] }, id))
170
+ ]
171
+ }
172
+ ) })
173
+ ] });
174
+ }
93
175
  function DocxMentions({
94
176
  children,
95
177
  mentions = [],
96
178
  initialCommentId,
97
179
  requestAgent,
180
+ getAgentStatus,
98
181
  notify
99
182
  }) {
100
183
  const review = useReview();
184
+ const [activity, setActivity] = useState({});
185
+ const root = useRef(null);
186
+ const [composers, setComposers] = useState([]);
187
+ useEffect(() => {
188
+ const element = root.current;
189
+ if (!element || !requestAgent) return;
190
+ const discover = () => {
191
+ const fields = Array.from(
192
+ element.querySelectorAll(
193
+ '[data-testid="review-draft-input"], [data-testid="review-reply-input"]'
194
+ )
195
+ ).filter((field) => !field.readOnly && !!field.closest("form"));
196
+ setComposers(
197
+ (previous) => previous.length === fields.length && previous.every((field, index) => field === fields[index]) ? previous : fields
198
+ );
199
+ };
200
+ discover();
201
+ const observer = new MutationObserver(discover);
202
+ observer.observe(element, { childList: true, subtree: true });
203
+ return () => observer.disconnect();
204
+ }, [requestAgent]);
205
+ const prefixMention = (field, name = "ARBI") => {
206
+ const value = `@${name} ${field.value}`;
207
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(field, value);
208
+ field.dispatchEvent(new Event("input", { bubbles: true }));
209
+ field.focus();
210
+ field.setSelectionRange(value.length, value.length);
211
+ setSuggestions(null);
212
+ };
213
+ const alive = useRef(true);
214
+ useEffect(() => {
215
+ alive.current = true;
216
+ return () => {
217
+ alive.current = false;
218
+ };
219
+ }, []);
101
220
  const activated = useRef(void 0);
102
221
  useEffect(() => {
103
222
  if (!initialCommentId || activated.current === initialCommentId || !review.ready) return;
@@ -126,18 +245,50 @@ function DocxMentions({
126
245
  if (!created || created.kind !== "comment") return;
127
246
  pending.current = null;
128
247
  const commentId = created.parentId || created.item.id;
248
+ const label = submission.recipientIds.includes("arbi") ? "ARBI" : "Mentioned agent";
249
+ const update = (status) => {
250
+ if (alive.current)
251
+ setActivity((previous) => ({
252
+ ...previous,
253
+ [submission.requestId]: { commentId, label, status }
254
+ }));
255
+ };
256
+ const hasAgent = submission.recipientIds.some(
257
+ (id) => id === "arbi" || mentions.some((person) => person.id === id && person.kind === "agent")
258
+ );
259
+ if (hasAgent) update("delivering");
129
260
  void requestAgent({
130
261
  commentId,
131
262
  instruction: submission.text,
132
263
  requestId: submission.requestId,
133
264
  recipientIds: submission.recipientIds
265
+ }).then(async (delivery) => {
266
+ if (!delivery?.responseIds.length || !getAgentStatus) {
267
+ if (hasAgent) update("unknown");
268
+ return;
269
+ }
270
+ update("queued");
271
+ while (alive.current) {
272
+ try {
273
+ const states = await Promise.all(delivery.responseIds.map((id) => getAgentStatus(id)));
274
+ if (!alive.current) return;
275
+ const active = states.find((status2) => status2 === "in_progress") || states.find((status2) => status2 === "queued");
276
+ const status = active || states.find((status2) => status2 !== "completed") || "completed";
277
+ update(status);
278
+ if (!active) return;
279
+ } catch {
280
+ update("unknown");
281
+ }
282
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
283
+ }
134
284
  }).catch((error) => {
285
+ if (hasAgent) update("failed");
135
286
  notify?.(
136
287
  error instanceof Error ? error.message : "Could not start the document agent.",
137
288
  "error"
138
289
  );
139
290
  });
140
- }, [review.items, requestAgent, notify]);
291
+ }, [review.items, requestAgent, getAgentStatus, mentions, notify]);
141
292
  const choose = (name) => {
142
293
  const field = input.current;
143
294
  if (!field || !suggestions) return;
@@ -184,10 +335,31 @@ function DocxMentions({
184
335
  }
185
336
  return false;
186
337
  };
187
- return /* @__PURE__ */ jsxs(
338
+ return /* @__PURE__ */ jsx(ActivityContext.Provider, { value: activity, children: /* @__PURE__ */ jsxs(
188
339
  "div",
189
340
  {
190
341
  className: "native-docx-mentions",
342
+ ref: root,
343
+ onFocusCapture: (event) => {
344
+ const field = event.target;
345
+ if (!(field instanceof HTMLInputElement) || field.dataset.testid !== "review-reply-input" || field.value || !requestAgent)
346
+ return;
347
+ const target = review.items.find(
348
+ (entry) => entry.key === review.activeKey && entry.kind === "comment"
349
+ );
350
+ if (!target || target.kind !== "comment") return;
351
+ const commentId = target.parentId || target.item.id;
352
+ const replies = review.items.filter(
353
+ (entry) => entry.kind === "comment" && entry.parentId === commentId
354
+ );
355
+ const author = replies.at(-1)?.author || target.author;
356
+ const agent = mentions.find((person) => person.kind === "agent" && person.name === author);
357
+ if (agent || author === "ARBI" || author === "Assistant")
358
+ prefixMention(
359
+ field,
360
+ author === "ARBI" || author === "Assistant" ? "ARBI" : agent?.name || "ARBI"
361
+ );
362
+ },
191
363
  onInputCapture: (event) => {
192
364
  const field = event.target;
193
365
  if (!(field instanceof HTMLInputElement) || !field.dataset.testid?.startsWith("review-"))
@@ -248,6 +420,28 @@ function DocxMentions({
248
420
  },
249
421
  children: [
250
422
  children,
423
+ composers.filter((field) => field.isConnected && field.closest("form")).map(
424
+ (field, index) => createPortal(
425
+ /* @__PURE__ */ jsx(
426
+ "button",
427
+ {
428
+ type: "button",
429
+ className: "native-docx-include-arbi",
430
+ "data-testid": "docx-include-arbi",
431
+ onMouseDown: (event) => event.preventDefault(),
432
+ onClick: (event) => {
433
+ event.stopPropagation();
434
+ if (!mentionedDocxRecipients(field.value, mentions).includes("arbi"))
435
+ prefixMention(field);
436
+ else field.focus();
437
+ },
438
+ children: "Include ARBI"
439
+ }
440
+ ),
441
+ field.closest("form"),
442
+ `mention-${index}`
443
+ )
444
+ ),
251
445
  suggestions && candidates.length > 0 && /* @__PURE__ */ jsx(
252
446
  "div",
253
447
  {
@@ -280,8 +474,95 @@ function DocxMentions({
280
474
  )
281
475
  ]
282
476
  }
283
- );
477
+ ) });
478
+ }
479
+ function wordDraftHtml(markdown) {
480
+ const template = document.createElement("template");
481
+ template.innerHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(AiMarkdown, { children: markdown }));
482
+ template.content.querySelectorAll('button, svg, [data-testid="code-block-header"]').forEach((node) => node.remove());
483
+ template.content.querySelectorAll('[data-testid="markdown-code-block"]').forEach((node) => {
484
+ const pre = node.querySelector("pre");
485
+ if (pre) node.replaceWith(pre);
486
+ });
487
+ return template.innerHTML;
284
488
  }
489
+ function requireEdit(result) {
490
+ if (!result.ok) throw new Error(result.reason);
491
+ }
492
+ var WordDraftStream = class {
493
+ constructor(editor) {
494
+ this.editor = editor;
495
+ }
496
+ editor;
497
+ committed = [];
498
+ pending;
499
+ resetAfterFailure = false;
500
+ update(content, complete) {
501
+ const blocks = splitMarkdownBlocks(content);
502
+ const completeCount = complete ? blocks.length : Math.max(0, blocks.length - 1);
503
+ requireEdit(this.editor.setEditingMode("editing"));
504
+ try {
505
+ if (this.resetAfterFailure || this.committed.some((block, index) => blocks[index] !== block)) {
506
+ requireEdit(this.editor.exec({ type: "selectAll" }));
507
+ requireEdit(this.editor.exec({ type: "deleteText" }));
508
+ this.committed = [];
509
+ this.pending = void 0;
510
+ this.resetAfterFailure = false;
511
+ }
512
+ for (let index = this.committed.length; index < blocks.length; index++) {
513
+ const tail = this.editor.query({ type: "paragraphs" }).at(-1);
514
+ const start = this.pending?.start || tail?.paraId;
515
+ if (!start) throw new Error("The draft insertion paragraph is unavailable.");
516
+ requireEdit(
517
+ this.editor.exec(
518
+ this.pending ? {
519
+ type: "setSelection",
520
+ range: { from: { paraId: start }, to: { paraId: this.pending.end } }
521
+ } : { type: "setSelection", anchor: { paraId: start } }
522
+ )
523
+ );
524
+ const html = wordDraftHtml(blocks[index]) + "<p></p>";
525
+ const element = document.createElement("div");
526
+ element.innerHTML = html;
527
+ const startIndex = this.editor.query({ type: "paragraphs" }).findIndex((p) => p.paraId === start);
528
+ requireEdit(this.editor.exec({ type: "paste", html, text: element.textContent || "" }));
529
+ const inserted = this.editor.query({ type: "paragraphs" }).slice(startIndex);
530
+ let headingIndex = 0;
531
+ for (const heading of Array.from(element.querySelectorAll("h1,h2,h3,h4,h5,h6"))) {
532
+ const match = inserted.findIndex(
533
+ (p, i) => i >= headingIndex && p.text === heading.textContent
534
+ );
535
+ if (match >= 0 && inserted[match].paraId) {
536
+ requireEdit(
537
+ this.editor.exec({
538
+ type: "setSelection",
539
+ anchor: { paraId: inserted[match].paraId }
540
+ })
541
+ );
542
+ requireEdit(
543
+ this.editor.exec({
544
+ type: "setParagraphStyle",
545
+ styleId: `Heading${heading.tagName.slice(1)}`
546
+ })
547
+ );
548
+ headingIndex = match + 1;
549
+ }
550
+ }
551
+ const end = this.editor.query({ type: "paragraphs" }).at(-1)?.paraId;
552
+ if (!end) throw new Error("The draft continuation paragraph is unavailable.");
553
+ if (index < completeCount) {
554
+ this.committed.push(blocks[index]);
555
+ this.pending = void 0;
556
+ } else this.pending = { start, end };
557
+ }
558
+ } catch (error) {
559
+ this.resetAfterFailure = true;
560
+ throw error;
561
+ } finally {
562
+ requireEdit(this.editor.setEditingMode("viewing"));
563
+ }
564
+ }
565
+ };
285
566
  var drafts = /* @__PURE__ */ new Map();
286
567
  var stateBytes = (room) => Y.encodeStateAsUpdate(room.ydoc);
287
568
  var encodeState = (room) => {
@@ -431,6 +712,7 @@ function NativeDocxEditor({
431
712
  initialCommentId,
432
713
  snapshot = false,
433
714
  onSaved,
715
+ stream,
434
716
  adapter
435
717
  }) {
436
718
  const [ready, setReady] = useState(), [error, setError] = useState(""), [retry, setRetry] = useState(0);
@@ -439,6 +721,49 @@ function NativeDocxEditor({
439
721
  const stageRef = useRef(null), fitCleanup = useRef(void 0);
440
722
  const ref = useRef(null), dirtyRef = useRef(false), draftTimer = useRef(void 0);
441
723
  const isDraft = draftContent !== void 0;
724
+ const streamRef = useRef(stream);
725
+ streamRef.current = stream;
726
+ const streamWriter = useRef(null);
727
+ const streamFinished = useRef(false);
728
+ const streamTimer = useRef(void 0);
729
+ const [streamAttempt, setStreamAttempt] = useState(0);
730
+ function updateStream() {
731
+ const current = streamRef.current;
732
+ if (!current || !streamWriter.current || streamFinished.current) return;
733
+ try {
734
+ streamWriter.current.update(current.content, !current.busy);
735
+ setStatus(
736
+ current.busy ? "Generating \xB7 editing is locked" : current.error ? "Generation stopped \xB7 draft retained" : "Saving generated Word document\u2026"
737
+ );
738
+ if (!current.busy && !current.error && current.content.trim()) {
739
+ streamFinished.current = true;
740
+ void ref.current.save().then(current.onGenerated).catch((failure) => {
741
+ streamFinished.current = false;
742
+ setError(
743
+ failure instanceof Error ? failure.message : "Could not store the generated document."
744
+ );
745
+ });
746
+ }
747
+ } catch (failure) {
748
+ setError(
749
+ failure instanceof Error ? failure.message : "Could not import the generated document."
750
+ );
751
+ }
752
+ }
753
+ useEffect(() => {
754
+ if (!streamRef.current) return;
755
+ if (!streamRef.current.busy) {
756
+ clearTimeout(streamTimer.current);
757
+ streamTimer.current = void 0;
758
+ updateStream();
759
+ } else if (!streamTimer.current) {
760
+ streamTimer.current = setTimeout(() => {
761
+ streamTimer.current = void 0;
762
+ updateStream();
763
+ }, 120);
764
+ }
765
+ }, [stream?.content, stream?.busy, stream?.error, streamAttempt]);
766
+ useEffect(() => () => clearTimeout(streamTimer.current), []);
442
767
  useEffect(() => {
443
768
  let cancelled = false;
444
769
  let room;
@@ -451,7 +776,7 @@ function NativeDocxEditor({
451
776
  async function start() {
452
777
  const fonts = await adapter.loadFonts();
453
778
  if (isDraft || snapshot) {
454
- const data = isDraft ? drafts.get(documentId) || await (await adapter.request("/prepare", {
779
+ const data = isDraft ? !streamRef.current && drafts.get(documentId) || await (await adapter.request("/prepare", {
455
780
  method: "POST",
456
781
  headers: { "Content-Type": "application/json" },
457
782
  body: JSON.stringify({ content: draftContent })
@@ -599,7 +924,7 @@ function NativeDocxEditor({
599
924
  };
600
925
  }, [documentId, isDraft, ready]);
601
926
  async function saveVersion() {
602
- if (!ready || saving) return;
927
+ if (!ready || saving || stream) return;
603
928
  setSaving(true);
604
929
  const atRevision = revision.current;
605
930
  try {
@@ -632,6 +957,10 @@ function NativeDocxEditor({
632
957
  }
633
958
  function editorReady(editor) {
634
959
  ref.current = editor;
960
+ if (streamRef.current) {
961
+ streamWriter.current = new WordDraftStream(editor);
962
+ updateStream();
963
+ }
635
964
  fitCleanup.current?.();
636
965
  const viewport = stageRef.current?.querySelector(".native-docx-viewport");
637
966
  if (!viewport) return;
@@ -670,112 +999,122 @@ function NativeDocxEditor({
670
999
  }, 200);
671
1000
  }
672
1001
  }
673
- return /* @__PURE__ */ jsxs("section", { className: "native-docx-viewer", "data-testid": "native-docx-editor", children: [
674
- /* @__PURE__ */ jsxs("div", { className: "native-docx-actions", children: [
675
- /* @__PURE__ */ jsxs("span", { className: "native-docx-status", role: "status", children: [
676
- offline ? /* @__PURE__ */ jsx(WifiOff, { size: 14 }) : !dirty && ready ? /* @__PURE__ */ jsx(Check, { size: 14 }) : null,
677
- status
678
- ] }),
679
- /* @__PURE__ */ jsx(DocxParticipants, { session: ready?.room?.session }),
680
- /* @__PURE__ */ jsxs("button", { onClick: () => void exportDocument(), disabled: !ready, "aria-label": "Download DOCX", children: [
681
- /* @__PURE__ */ jsx(Download, { size: 14 }),
682
- "DOCX"
683
- ] }),
684
- !snapshot && ready?.canEdit && /* @__PURE__ */ jsxs(
685
- "button",
686
- {
687
- className: "native-docx-save",
688
- onClick: () => void saveVersion(),
689
- disabled: !ready || saving || offline,
690
- "data-testid": "native-docx-save",
691
- children: [
692
- /* @__PURE__ */ jsx(Save, { size: 14 }),
693
- saving ? "Saving\u2026" : isDraft ? "Save note" : "Save version"
694
- ]
695
- }
696
- )
697
- ] }),
698
- offline && /* @__PURE__ */ jsx("div", { className: "native-docx-notice", children: "Changes are buffered in this browser. Keep this tab open until the connection returns, or download your DOCX." }),
699
- error && /* @__PURE__ */ jsxs("div", { className: "native-docx-notice", "data-error": "true", role: "alert", children: [
700
- error,
701
- " ",
702
- /* @__PURE__ */ jsxs(
703
- "button",
704
- {
705
- disabled: dirty,
706
- title: dirty ? "Download your unsaved DOCX before reopening the editor" : void 0,
707
- onClick: () => setRetry((x) => x + 1),
708
- children: [
709
- /* @__PURE__ */ jsx(RefreshCw, { size: 12 }),
710
- "Retry"
711
- ]
712
- }
713
- )
714
- ] }),
715
- !ready && !error && /* @__PURE__ */ jsx("div", { className: "native-docx-error", role: "status", children: "Opening Word document\u2026" }),
716
- ready && /* @__PURE__ */ jsx("div", { className: "native-docx-body docx-editor", children: /* @__PURE__ */ jsx(EditorBoundary, { children: /* @__PURE__ */ jsxs(
717
- DocxEditor.Root,
718
- {
719
- document: ready.document,
720
- modules: ready.modules,
721
- fonts: ready.fonts,
722
- mode: !ready.canEdit || snapshot ? "view" : "suggesting",
723
- reviewDisplayMode: "all-markup",
724
- zoomMode: fixedZoomMode,
725
- author: ready.author,
726
- onReady: editorReady,
727
- onChange: (change) => {
728
- if (change.source !== "load") changed();
729
- },
730
- onFontError: (e) => setError(e.message),
731
- children: [
732
- /* @__PURE__ */ jsx(
733
- DocxEditor.RevisionMarkup,
1002
+ return /* @__PURE__ */ jsxs(
1003
+ "section",
1004
+ {
1005
+ className: "native-docx-viewer",
1006
+ "data-testid": "native-docx-editor",
1007
+ "aria-busy": !!stream?.busy,
1008
+ "data-generation-state": stream ? stream.busy ? "generating" : stream.error ? "stopped" : "persisting" : "ready",
1009
+ children: [
1010
+ /* @__PURE__ */ jsxs("div", { className: "native-docx-actions", children: [
1011
+ /* @__PURE__ */ jsxs("span", { className: "native-docx-status", role: "status", children: [
1012
+ offline ? /* @__PURE__ */ jsx(WifiOff, { size: 14 }) : !dirty && ready ? /* @__PURE__ */ jsx(Check, { size: 14 }) : null,
1013
+ status
1014
+ ] }),
1015
+ /* @__PURE__ */ jsx(DocxParticipants, { session: ready?.room?.session }),
1016
+ /* @__PURE__ */ jsxs("button", { onClick: () => void exportDocument(), disabled: !ready, "aria-label": "Download DOCX", children: [
1017
+ /* @__PURE__ */ jsx(Download, { size: 14 }),
1018
+ "DOCX"
1019
+ ] }),
1020
+ !snapshot && !stream && ready?.canEdit && /* @__PURE__ */ jsxs(
1021
+ "button",
734
1022
  {
735
- insertions: { mark: "underline", color: "blue" },
736
- deletions: { mark: "strikethrough", color: "red" },
737
- movedFrom: { mark: "doubleStrikethrough", color: "green" },
738
- movedTo: { mark: "doubleUnderline", color: "green" },
739
- trackMoves: true
1023
+ className: "native-docx-save",
1024
+ onClick: () => void saveVersion(),
1025
+ disabled: !ready || saving || offline,
1026
+ "data-testid": "native-docx-save",
1027
+ children: [
1028
+ /* @__PURE__ */ jsx(Save, { size: 14 }),
1029
+ saving ? "Saving\u2026" : isDraft ? "Save note" : "Save version"
1030
+ ]
740
1031
  }
741
- ),
742
- /* @__PURE__ */ jsx(DocxRibbon, { onSave: () => void saveVersion() }),
1032
+ )
1033
+ ] }),
1034
+ offline && /* @__PURE__ */ jsx("div", { className: "native-docx-notice", children: "Changes are buffered in this browser. Keep this tab open until the connection returns, or download your DOCX." }),
1035
+ error && /* @__PURE__ */ jsxs("div", { className: "native-docx-notice", "data-error": "true", role: "alert", children: [
1036
+ error,
1037
+ " ",
743
1038
  /* @__PURE__ */ jsxs(
744
- "div",
1039
+ "button",
745
1040
  {
746
- className: "native-docx-stage",
747
- ref: stageRef,
748
- onMouseDownCapture: (event) => {
749
- if (event.button === 2 && ref.current?.getSelectionPlacement())
750
- event.preventDefault();
751
- },
1041
+ disabled: !stream && dirty,
1042
+ title: dirty ? "Download your unsaved DOCX before reopening the editor" : void 0,
1043
+ onClick: () => stream ? (setError(""), setStreamAttempt((x) => x + 1)) : setRetry((x) => x + 1),
752
1044
  children: [
753
- /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
754
- /* @__PURE__ */ jsx(DocxEditor.Content, {}),
755
- /* @__PURE__ */ jsx(
756
- DocxMentions,
757
- {
758
- initialCommentId,
759
- mentions: adapter.mentions,
760
- notify: adapter.notify,
761
- requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
762
- children: /* @__PURE__ */ jsx(DocxEditorReview, {})
763
- }
764
- ),
765
- /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
766
- /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
767
- /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
768
- /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
769
- /* @__PURE__ */ jsx(DocxEditor.ContextMenu, {})
770
- ] }),
771
- /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
1045
+ /* @__PURE__ */ jsx(RefreshCw, { size: 12 }),
1046
+ "Retry"
772
1047
  ]
773
1048
  }
774
1049
  )
775
- ]
776
- }
777
- ) }, retry) })
778
- ] });
1050
+ ] }),
1051
+ !ready && !error && /* @__PURE__ */ jsx("div", { className: "native-docx-error", role: "status", children: "Opening Word document\u2026" }),
1052
+ ready && /* @__PURE__ */ jsx("div", { className: "native-docx-body docx-editor", children: /* @__PURE__ */ jsx(EditorBoundary, { children: /* @__PURE__ */ jsxs(
1053
+ DocxEditor.Root,
1054
+ {
1055
+ document: ready.document,
1056
+ modules: ready.modules,
1057
+ fonts: ready.fonts,
1058
+ mode: stream ? "edit" : !ready.canEdit || snapshot ? "view" : "suggesting",
1059
+ reviewDisplayMode: "all-markup",
1060
+ zoomMode: fixedZoomMode,
1061
+ author: ready.author,
1062
+ onReady: editorReady,
1063
+ onChange: (change) => {
1064
+ if (change.source !== "load") changed();
1065
+ },
1066
+ onFontError: (e) => setError(e.message),
1067
+ children: [
1068
+ /* @__PURE__ */ jsx(
1069
+ DocxEditor.RevisionMarkup,
1070
+ {
1071
+ insertions: { mark: "underline", color: "blue" },
1072
+ deletions: { mark: "strikethrough", color: "red" },
1073
+ movedFrom: { mark: "doubleStrikethrough", color: "green" },
1074
+ movedTo: { mark: "doubleUnderline", color: "green" },
1075
+ trackMoves: true
1076
+ }
1077
+ ),
1078
+ !stream && /* @__PURE__ */ jsx(DocxRibbon, { onSave: () => void saveVersion() }),
1079
+ /* @__PURE__ */ jsxs(
1080
+ "div",
1081
+ {
1082
+ className: "native-docx-stage",
1083
+ ref: stageRef,
1084
+ onMouseDownCapture: (event) => {
1085
+ if (event.button === 2 && ref.current?.getSelectionPlacement())
1086
+ event.preventDefault();
1087
+ },
1088
+ children: [
1089
+ /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
1090
+ /* @__PURE__ */ jsx(DocxEditor.Content, {}),
1091
+ /* @__PURE__ */ jsx(
1092
+ DocxMentions,
1093
+ {
1094
+ initialCommentId,
1095
+ mentions: adapter.mentions,
1096
+ notify: adapter.notify,
1097
+ getAgentStatus: adapter.getAgentStatus,
1098
+ requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
1099
+ children: /* @__PURE__ */ jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsx(DocxCommentActivity, {}) }) })
1100
+ }
1101
+ ),
1102
+ /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
1103
+ /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
1104
+ /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
1105
+ /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
1106
+ /* @__PURE__ */ jsx(DocxEditor.ContextMenu, {})
1107
+ ] }),
1108
+ /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
1109
+ ]
1110
+ }
1111
+ )
1112
+ ]
1113
+ }
1114
+ ) }, retry) })
1115
+ ]
1116
+ }
1117
+ );
779
1118
  }
780
1119
  var loaded = /* @__PURE__ */ new Map();
781
1120
  function loadDocxFonts(baseUrl = "/native-docx/fonts") {
@@ -840,7 +1179,58 @@ async function loadFonts(baseUrl) {
840
1179
  )
841
1180
  };
842
1181
  }
1182
+ function StreamedWordArtifact({
1183
+ adapter,
1184
+ content,
1185
+ title,
1186
+ busy,
1187
+ error,
1188
+ draftId,
1189
+ sourceDocumentId,
1190
+ onSaved
1191
+ }) {
1192
+ const [documentId, setDocumentId] = useState();
1193
+ const saving = useRef(null);
1194
+ async function persist(bytes) {
1195
+ if (saving.current) return saving.current;
1196
+ saving.current = (async () => {
1197
+ const query = new URLSearchParams({ title });
1198
+ if (sourceDocumentId) query.set("parent_ext_id", sourceDocumentId);
1199
+ const response = await adapter.request(`/notes?${query}`, {
1200
+ method: "POST",
1201
+ headers: {
1202
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
1203
+ },
1204
+ body: bytes
1205
+ });
1206
+ if (!response.ok) throw new Error("Could not store the generated Word document.");
1207
+ const result = await response.json();
1208
+ if (!result.document_ext_id) throw new Error("The generated document has no ID.");
1209
+ await Promise.resolve(adapter.onVersionSaved?.(result.document_ext_id)).catch(() => {
1210
+ });
1211
+ setDocumentId(result.document_ext_id);
1212
+ onSaved?.(result.document_ext_id);
1213
+ })();
1214
+ try {
1215
+ await saving.current;
1216
+ } finally {
1217
+ saving.current = null;
1218
+ }
1219
+ }
1220
+ return /* @__PURE__ */ jsx(
1221
+ NativeDocxEditor,
1222
+ {
1223
+ adapter,
1224
+ documentId: documentId || draftId,
1225
+ title,
1226
+ draftContent: documentId ? void 0 : "",
1227
+ stream: documentId ? void 0 : { content, busy, error, onGenerated: persist },
1228
+ onSaved
1229
+ },
1230
+ documentId || draftId
1231
+ );
1232
+ }
843
1233
 
844
- export { DocxMentions, DocxParticipants, DocxRibbon, NativeDocxEditor, loadDocxFonts, normalizeDocxExport };
845
- //# sourceMappingURL=chunk-UOLMFC5K.js.map
846
- //# sourceMappingURL=chunk-UOLMFC5K.js.map
1234
+ export { DocxCommentActivity, DocxMentions, DocxParticipants, DocxRibbon, NativeDocxEditor, StreamedWordArtifact, loadDocxFonts, normalizeDocxExport };
1235
+ //# sourceMappingURL=chunk-6YVI74BC.js.map
1236
+ //# sourceMappingURL=chunk-6YVI74BC.js.map