@arbidocs/blocks 0.4.27 → 0.4.28

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 (103) hide show
  1. package/README.md +17 -0
  2. package/dist/agents/index.cjs +222 -210
  3. package/dist/agents/index.js +4 -4
  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-CQ6CYPV4.js → chunk-3NZR6PAG.js} +7 -119
  15. package/dist/chunk-3NZR6PAG.js.map +1 -0
  16. package/dist/chunk-4XJZ6AAY.js +407 -0
  17. package/dist/chunk-4XJZ6AAY.js.map +1 -0
  18. package/dist/{chunk-UOLMFC5K.js → chunk-6YVI74BC.js} +498 -108
  19. package/dist/chunk-6YVI74BC.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-TL2DHSC5.cjs → chunk-FRRDPYBR.cjs} +5 -120
  25. package/dist/chunk-FRRDPYBR.cjs.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-NSZAHL23.cjs} +156 -37
  31. package/dist/chunk-NSZAHL23.cjs.map +1 -0
  32. package/dist/{chunk-ENY6XXKR.js → chunk-QMUI2MGT.js} +153 -37
  33. package/dist/chunk-QMUI2MGT.js.map +1 -0
  34. package/dist/chunk-S5HZBXZA.cjs +430 -0
  35. package/dist/chunk-S5HZBXZA.cjs.map +1 -0
  36. package/dist/{chunk-JCVZKCXC.cjs → chunk-TLQQV7WW.cjs} +37 -6
  37. package/dist/chunk-TLQQV7WW.cjs.map +1 -0
  38. package/dist/{chunk-NNH7N62N.cjs → chunk-VMCOH3AY.cjs} +497 -105
  39. package/dist/chunk-VMCOH3AY.cjs.map +1 -0
  40. package/dist/{chunk-JLPYOPCX.js → chunk-WSJVHSGN.js} +225 -36
  41. package/dist/chunk-WSJVHSGN.js.map +1 -0
  42. package/dist/{chunk-DCEI37AX.cjs → chunk-YVIOCITQ.cjs} +3 -3
  43. package/dist/{chunk-DCEI37AX.cjs.map → chunk-YVIOCITQ.cjs.map} +1 -1
  44. package/dist/{chunk-J22DXUJY.cjs → chunk-ZHLMDJCO.cjs} +226 -34
  45. package/dist/chunk-ZHLMDJCO.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/files/index.cjs +2 -2
  71. package/dist/files/index.js +2 -2
  72. package/dist/index.cjs +444 -432
  73. package/dist/index.js +7 -7
  74. package/dist/settings/index.cjs +41 -231
  75. package/dist/settings/index.cjs.map +1 -1
  76. package/dist/settings/index.js +6 -202
  77. package/dist/settings/index.js.map +1 -1
  78. package/dist/studio/index.cjs +6 -5
  79. package/dist/studio/index.cjs.map +1 -1
  80. package/dist/studio/index.js +4 -3
  81. package/dist/studio/index.js.map +1 -1
  82. package/dist/workspaces/index.cjs +2 -2
  83. package/dist/workspaces/index.js +2 -2
  84. package/package.json +3 -3
  85. package/src/docx/styles.css +13 -0
  86. package/dist/chunk-3ZXXHROK.cjs.map +0 -1
  87. package/dist/chunk-5TRALRWN.js +0 -42
  88. package/dist/chunk-5TRALRWN.js.map +0 -1
  89. package/dist/chunk-CQ6CYPV4.js.map +0 -1
  90. package/dist/chunk-CQCC7LE7.js +0 -210
  91. package/dist/chunk-CQCC7LE7.js.map +0 -1
  92. package/dist/chunk-ENY6XXKR.js.map +0 -1
  93. package/dist/chunk-EUOGPUMJ.cjs +0 -59
  94. package/dist/chunk-EUOGPUMJ.cjs.map +0 -1
  95. package/dist/chunk-J22DXUJY.cjs.map +0 -1
  96. package/dist/chunk-JCVZKCXC.cjs.map +0 -1
  97. package/dist/chunk-JLPYOPCX.js.map +0 -1
  98. package/dist/chunk-NNH7N62N.cjs.map +0 -1
  99. package/dist/chunk-RFIBUWLF.cjs +0 -232
  100. package/dist/chunk-RFIBUWLF.cjs.map +0 -1
  101. package/dist/chunk-TL2DHSC5.cjs.map +0 -1
  102. package/dist/chunk-UOLMFC5K.js.map +0 -1
  103. package/dist/chunk-ZEIOREJY.js.map +0 -1
@@ -4,8 +4,11 @@
4
4
  var chunk7UTB4PHM_cjs = require('./chunk-7UTB4PHM.cjs');
5
5
  var fflate = require('fflate');
6
6
  var React2 = require('react');
7
+ var reactDom = require('react-dom');
7
8
  var react = require('@docx-editor.dev/pro/react');
9
+ var ui = require('@arbidocs/react/ui');
8
10
  var jsxRuntime = require('react/jsx-runtime');
11
+ var server = require('react-dom/server');
9
12
  var Y = require('yjs');
10
13
  var react$1 = require('@docx-editor.dev/react');
11
14
  var pro = require('@docx-editor.dev/pro');
@@ -115,14 +118,130 @@ function mentionCandidates(mentions) {
115
118
  function mentionedDocxRecipients(text, mentions) {
116
119
  return chunk7UTB4PHM_cjs.extractMentions(text, mentionCandidates(mentions)).map((person) => person.id);
117
120
  }
121
+ var ActivityContext = React2.createContext({});
122
+ function DocxCommentActivity() {
123
+ const item = react.useReviewItem();
124
+ const review = react.useReview();
125
+ const activity = React2.useContext(ActivityContext);
126
+ const [reading, setReading] = React2.useState(false);
127
+ if (!item || item.kind !== "comment") return null;
128
+ const commentId = item.parentId || item.item.id;
129
+ const thread = review.items.filter(
130
+ (entry) => entry.kind === "comment" && (entry.item.id === commentId || entry.parentId === commentId)
131
+ );
132
+ const statuses = Object.entries(activity).filter(([, run]) => run.commentId === commentId);
133
+ const labels = {
134
+ delivering: "Delivering mention\u2026",
135
+ queued: "Queued\u2026",
136
+ in_progress: "Working\u2026",
137
+ completed: "Finished",
138
+ failed: "Failed",
139
+ cancelled: "Stopped",
140
+ incomplete: "Incomplete",
141
+ unknown: "Status unavailable"
142
+ };
143
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-comment-extra", "data-review-selectable": "", children: [
144
+ statuses.map(([id, run]) => /* @__PURE__ */ jsxRuntime.jsxs(
145
+ "div",
146
+ {
147
+ role: "status",
148
+ "data-testid": "docx-comment-agent-status",
149
+ "data-status": run.status,
150
+ className: "native-docx-comment-activity",
151
+ children: [
152
+ ["delivering", "queued", "in_progress"].includes(run.status) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-agent-spinner", "aria-hidden": "true" }),
153
+ run.label,
154
+ ": ",
155
+ labels[run.status]
156
+ ]
157
+ },
158
+ id
159
+ )),
160
+ /* @__PURE__ */ jsxRuntime.jsx(
161
+ "button",
162
+ {
163
+ type: "button",
164
+ "data-testid": "docx-read-full-thread",
165
+ onClick: (event) => {
166
+ event.stopPropagation();
167
+ setReading(true);
168
+ },
169
+ children: "Read full thread"
170
+ }
171
+ ),
172
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { open: reading, onOpenChange: setReading, children: /* @__PURE__ */ jsxRuntime.jsxs(
173
+ ui.DialogContent,
174
+ {
175
+ className: "max-h-[85dvh] overflow-y-auto",
176
+ onClick: (event) => event.stopPropagation(),
177
+ children: [
178
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.DialogTitle, { children: "Document comment" }) }),
179
+ thread.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 border-b pb-4 last:border-0", children: [
180
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: entry.author || "Reviewer" }),
181
+ /* @__PURE__ */ jsxRuntime.jsx(
182
+ "p",
183
+ {
184
+ className: "whitespace-pre-wrap break-words text-sm",
185
+ "data-testid": "docx-full-comment-text",
186
+ children: entry.text
187
+ }
188
+ )
189
+ ] }, entry.key)),
190
+ statuses.map(([id, run]) => /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "status", className: "text-sm", children: [
191
+ run.label,
192
+ ": ",
193
+ labels[run.status]
194
+ ] }, id))
195
+ ]
196
+ }
197
+ ) })
198
+ ] });
199
+ }
118
200
  function DocxMentions({
119
201
  children,
120
202
  mentions = [],
121
203
  initialCommentId,
122
204
  requestAgent,
205
+ getAgentStatus,
123
206
  notify
124
207
  }) {
125
208
  const review = react.useReview();
209
+ const [activity, setActivity] = React2.useState({});
210
+ const root = React2.useRef(null);
211
+ const [composers, setComposers] = React2.useState([]);
212
+ React2.useEffect(() => {
213
+ const element = root.current;
214
+ if (!element || !requestAgent) return;
215
+ const discover = () => {
216
+ const fields = Array.from(
217
+ element.querySelectorAll(
218
+ '[data-testid="review-draft-input"], [data-testid="review-reply-input"]'
219
+ )
220
+ ).filter((field) => !field.readOnly && !!field.closest("form"));
221
+ setComposers(
222
+ (previous) => previous.length === fields.length && previous.every((field, index) => field === fields[index]) ? previous : fields
223
+ );
224
+ };
225
+ discover();
226
+ const observer = new MutationObserver(discover);
227
+ observer.observe(element, { childList: true, subtree: true });
228
+ return () => observer.disconnect();
229
+ }, [requestAgent]);
230
+ const prefixMention = (field, name = "ARBI") => {
231
+ const value = `@${name} ${field.value}`;
232
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(field, value);
233
+ field.dispatchEvent(new Event("input", { bubbles: true }));
234
+ field.focus();
235
+ field.setSelectionRange(value.length, value.length);
236
+ setSuggestions(null);
237
+ };
238
+ const alive = React2.useRef(true);
239
+ React2.useEffect(() => {
240
+ alive.current = true;
241
+ return () => {
242
+ alive.current = false;
243
+ };
244
+ }, []);
126
245
  const activated = React2.useRef(void 0);
127
246
  React2.useEffect(() => {
128
247
  if (!initialCommentId || activated.current === initialCommentId || !review.ready) return;
@@ -151,18 +270,50 @@ function DocxMentions({
151
270
  if (!created || created.kind !== "comment") return;
152
271
  pending.current = null;
153
272
  const commentId = created.parentId || created.item.id;
273
+ const label = submission.recipientIds.includes("arbi") ? "ARBI" : "Mentioned agent";
274
+ const update = (status) => {
275
+ if (alive.current)
276
+ setActivity((previous) => ({
277
+ ...previous,
278
+ [submission.requestId]: { commentId, label, status }
279
+ }));
280
+ };
281
+ const hasAgent = submission.recipientIds.some(
282
+ (id) => id === "arbi" || mentions.some((person) => person.id === id && person.kind === "agent")
283
+ );
284
+ if (hasAgent) update("delivering");
154
285
  void requestAgent({
155
286
  commentId,
156
287
  instruction: submission.text,
157
288
  requestId: submission.requestId,
158
289
  recipientIds: submission.recipientIds
290
+ }).then(async (delivery) => {
291
+ if (!delivery?.responseIds.length || !getAgentStatus) {
292
+ if (hasAgent) update("unknown");
293
+ return;
294
+ }
295
+ update("queued");
296
+ while (alive.current) {
297
+ try {
298
+ const states = await Promise.all(delivery.responseIds.map((id) => getAgentStatus(id)));
299
+ if (!alive.current) return;
300
+ const active = states.find((status2) => status2 === "in_progress") || states.find((status2) => status2 === "queued");
301
+ const status = active || states.find((status2) => status2 !== "completed") || "completed";
302
+ update(status);
303
+ if (!active) return;
304
+ } catch {
305
+ update("unknown");
306
+ }
307
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
308
+ }
159
309
  }).catch((error) => {
310
+ if (hasAgent) update("failed");
160
311
  notify?.(
161
312
  error instanceof Error ? error.message : "Could not start the document agent.",
162
313
  "error"
163
314
  );
164
315
  });
165
- }, [review.items, requestAgent, notify]);
316
+ }, [review.items, requestAgent, getAgentStatus, mentions, notify]);
166
317
  const choose = (name) => {
167
318
  const field = input.current;
168
319
  if (!field || !suggestions) return;
@@ -209,10 +360,31 @@ function DocxMentions({
209
360
  }
210
361
  return false;
211
362
  };
212
- return /* @__PURE__ */ jsxRuntime.jsxs(
363
+ return /* @__PURE__ */ jsxRuntime.jsx(ActivityContext.Provider, { value: activity, children: /* @__PURE__ */ jsxRuntime.jsxs(
213
364
  "div",
214
365
  {
215
366
  className: "native-docx-mentions",
367
+ ref: root,
368
+ onFocusCapture: (event) => {
369
+ const field = event.target;
370
+ if (!(field instanceof HTMLInputElement) || field.dataset.testid !== "review-reply-input" || field.value || !requestAgent)
371
+ return;
372
+ const target = review.items.find(
373
+ (entry) => entry.key === review.activeKey && entry.kind === "comment"
374
+ );
375
+ if (!target || target.kind !== "comment") return;
376
+ const commentId = target.parentId || target.item.id;
377
+ const replies = review.items.filter(
378
+ (entry) => entry.kind === "comment" && entry.parentId === commentId
379
+ );
380
+ const author = replies.at(-1)?.author || target.author;
381
+ const agent = mentions.find((person) => person.kind === "agent" && person.name === author);
382
+ if (agent || author === "ARBI" || author === "Assistant")
383
+ prefixMention(
384
+ field,
385
+ author === "ARBI" || author === "Assistant" ? "ARBI" : agent?.name || "ARBI"
386
+ );
387
+ },
216
388
  onInputCapture: (event) => {
217
389
  const field = event.target;
218
390
  if (!(field instanceof HTMLInputElement) || !field.dataset.testid?.startsWith("review-"))
@@ -273,6 +445,28 @@ function DocxMentions({
273
445
  },
274
446
  children: [
275
447
  children,
448
+ composers.filter((field) => field.isConnected && field.closest("form")).map(
449
+ (field, index) => reactDom.createPortal(
450
+ /* @__PURE__ */ jsxRuntime.jsx(
451
+ "button",
452
+ {
453
+ type: "button",
454
+ className: "native-docx-include-arbi",
455
+ "data-testid": "docx-include-arbi",
456
+ onMouseDown: (event) => event.preventDefault(),
457
+ onClick: (event) => {
458
+ event.stopPropagation();
459
+ if (!mentionedDocxRecipients(field.value, mentions).includes("arbi"))
460
+ prefixMention(field);
461
+ else field.focus();
462
+ },
463
+ children: "Include ARBI"
464
+ }
465
+ ),
466
+ field.closest("form"),
467
+ `mention-${index}`
468
+ )
469
+ ),
276
470
  suggestions && candidates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
277
471
  "div",
278
472
  {
@@ -305,8 +499,95 @@ function DocxMentions({
305
499
  )
306
500
  ]
307
501
  }
308
- );
502
+ ) });
503
+ }
504
+ function wordDraftHtml(markdown) {
505
+ const template = document.createElement("template");
506
+ template.innerHTML = server.renderToStaticMarkup(/* @__PURE__ */ jsxRuntime.jsx(ui.AiMarkdown, { children: markdown }));
507
+ template.content.querySelectorAll('button, svg, [data-testid="code-block-header"]').forEach((node) => node.remove());
508
+ template.content.querySelectorAll('[data-testid="markdown-code-block"]').forEach((node) => {
509
+ const pre = node.querySelector("pre");
510
+ if (pre) node.replaceWith(pre);
511
+ });
512
+ return template.innerHTML;
309
513
  }
514
+ function requireEdit(result) {
515
+ if (!result.ok) throw new Error(result.reason);
516
+ }
517
+ var WordDraftStream = class {
518
+ constructor(editor) {
519
+ this.editor = editor;
520
+ }
521
+ editor;
522
+ committed = [];
523
+ pending;
524
+ resetAfterFailure = false;
525
+ update(content, complete) {
526
+ const blocks = ui.splitMarkdownBlocks(content);
527
+ const completeCount = complete ? blocks.length : Math.max(0, blocks.length - 1);
528
+ requireEdit(this.editor.setEditingMode("editing"));
529
+ try {
530
+ if (this.resetAfterFailure || this.committed.some((block, index) => blocks[index] !== block)) {
531
+ requireEdit(this.editor.exec({ type: "selectAll" }));
532
+ requireEdit(this.editor.exec({ type: "deleteText" }));
533
+ this.committed = [];
534
+ this.pending = void 0;
535
+ this.resetAfterFailure = false;
536
+ }
537
+ for (let index = this.committed.length; index < blocks.length; index++) {
538
+ const tail = this.editor.query({ type: "paragraphs" }).at(-1);
539
+ const start = this.pending?.start || tail?.paraId;
540
+ if (!start) throw new Error("The draft insertion paragraph is unavailable.");
541
+ requireEdit(
542
+ this.editor.exec(
543
+ this.pending ? {
544
+ type: "setSelection",
545
+ range: { from: { paraId: start }, to: { paraId: this.pending.end } }
546
+ } : { type: "setSelection", anchor: { paraId: start } }
547
+ )
548
+ );
549
+ const html = wordDraftHtml(blocks[index]) + "<p></p>";
550
+ const element = document.createElement("div");
551
+ element.innerHTML = html;
552
+ const startIndex = this.editor.query({ type: "paragraphs" }).findIndex((p) => p.paraId === start);
553
+ requireEdit(this.editor.exec({ type: "paste", html, text: element.textContent || "" }));
554
+ const inserted = this.editor.query({ type: "paragraphs" }).slice(startIndex);
555
+ let headingIndex = 0;
556
+ for (const heading of Array.from(element.querySelectorAll("h1,h2,h3,h4,h5,h6"))) {
557
+ const match = inserted.findIndex(
558
+ (p, i) => i >= headingIndex && p.text === heading.textContent
559
+ );
560
+ if (match >= 0 && inserted[match].paraId) {
561
+ requireEdit(
562
+ this.editor.exec({
563
+ type: "setSelection",
564
+ anchor: { paraId: inserted[match].paraId }
565
+ })
566
+ );
567
+ requireEdit(
568
+ this.editor.exec({
569
+ type: "setParagraphStyle",
570
+ styleId: `Heading${heading.tagName.slice(1)}`
571
+ })
572
+ );
573
+ headingIndex = match + 1;
574
+ }
575
+ }
576
+ const end = this.editor.query({ type: "paragraphs" }).at(-1)?.paraId;
577
+ if (!end) throw new Error("The draft continuation paragraph is unavailable.");
578
+ if (index < completeCount) {
579
+ this.committed.push(blocks[index]);
580
+ this.pending = void 0;
581
+ } else this.pending = { start, end };
582
+ }
583
+ } catch (error) {
584
+ this.resetAfterFailure = true;
585
+ throw error;
586
+ } finally {
587
+ requireEdit(this.editor.setEditingMode("viewing"));
588
+ }
589
+ }
590
+ };
310
591
  var drafts = /* @__PURE__ */ new Map();
311
592
  var stateBytes = (room) => Y__namespace.encodeStateAsUpdate(room.ydoc);
312
593
  var encodeState = (room) => {
@@ -456,6 +737,7 @@ function NativeDocxEditor({
456
737
  initialCommentId,
457
738
  snapshot = false,
458
739
  onSaved,
740
+ stream,
459
741
  adapter
460
742
  }) {
461
743
  const [ready, setReady] = React2.useState(), [error, setError] = React2.useState(""), [retry, setRetry] = React2.useState(0);
@@ -464,6 +746,49 @@ function NativeDocxEditor({
464
746
  const stageRef = React2.useRef(null), fitCleanup = React2.useRef(void 0);
465
747
  const ref = React2.useRef(null), dirtyRef = React2.useRef(false), draftTimer = React2.useRef(void 0);
466
748
  const isDraft = draftContent !== void 0;
749
+ const streamRef = React2.useRef(stream);
750
+ streamRef.current = stream;
751
+ const streamWriter = React2.useRef(null);
752
+ const streamFinished = React2.useRef(false);
753
+ const streamTimer = React2.useRef(void 0);
754
+ const [streamAttempt, setStreamAttempt] = React2.useState(0);
755
+ function updateStream() {
756
+ const current = streamRef.current;
757
+ if (!current || !streamWriter.current || streamFinished.current) return;
758
+ try {
759
+ streamWriter.current.update(current.content, !current.busy);
760
+ setStatus(
761
+ current.busy ? "Generating \xB7 editing is locked" : current.error ? "Generation stopped \xB7 draft retained" : "Saving generated Word document\u2026"
762
+ );
763
+ if (!current.busy && !current.error && current.content.trim()) {
764
+ streamFinished.current = true;
765
+ void ref.current.save().then(current.onGenerated).catch((failure) => {
766
+ streamFinished.current = false;
767
+ setError(
768
+ failure instanceof Error ? failure.message : "Could not store the generated document."
769
+ );
770
+ });
771
+ }
772
+ } catch (failure) {
773
+ setError(
774
+ failure instanceof Error ? failure.message : "Could not import the generated document."
775
+ );
776
+ }
777
+ }
778
+ React2.useEffect(() => {
779
+ if (!streamRef.current) return;
780
+ if (!streamRef.current.busy) {
781
+ clearTimeout(streamTimer.current);
782
+ streamTimer.current = void 0;
783
+ updateStream();
784
+ } else if (!streamTimer.current) {
785
+ streamTimer.current = setTimeout(() => {
786
+ streamTimer.current = void 0;
787
+ updateStream();
788
+ }, 120);
789
+ }
790
+ }, [stream?.content, stream?.busy, stream?.error, streamAttempt]);
791
+ React2.useEffect(() => () => clearTimeout(streamTimer.current), []);
467
792
  React2.useEffect(() => {
468
793
  let cancelled = false;
469
794
  let room;
@@ -476,7 +801,7 @@ function NativeDocxEditor({
476
801
  async function start() {
477
802
  const fonts = await adapter.loadFonts();
478
803
  if (isDraft || snapshot) {
479
- const data = isDraft ? drafts.get(documentId) || await (await adapter.request("/prepare", {
804
+ const data = isDraft ? !streamRef.current && drafts.get(documentId) || await (await adapter.request("/prepare", {
480
805
  method: "POST",
481
806
  headers: { "Content-Type": "application/json" },
482
807
  body: JSON.stringify({ content: draftContent })
@@ -624,7 +949,7 @@ function NativeDocxEditor({
624
949
  };
625
950
  }, [documentId, isDraft, ready]);
626
951
  async function saveVersion() {
627
- if (!ready || saving) return;
952
+ if (!ready || saving || stream) return;
628
953
  setSaving(true);
629
954
  const atRevision = revision.current;
630
955
  try {
@@ -657,6 +982,10 @@ function NativeDocxEditor({
657
982
  }
658
983
  function editorReady(editor) {
659
984
  ref.current = editor;
985
+ if (streamRef.current) {
986
+ streamWriter.current = new WordDraftStream(editor);
987
+ updateStream();
988
+ }
660
989
  fitCleanup.current?.();
661
990
  const viewport = stageRef.current?.querySelector(".native-docx-viewport");
662
991
  if (!viewport) return;
@@ -695,112 +1024,122 @@ function NativeDocxEditor({
695
1024
  }, 200);
696
1025
  }
697
1026
  }
698
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "native-docx-viewer", "data-testid": "native-docx-editor", children: [
699
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-actions", children: [
700
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "native-docx-status", role: "status", children: [
701
- offline ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { size: 14 }) : !dirty && ready ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14 }) : null,
702
- status
703
- ] }),
704
- /* @__PURE__ */ jsxRuntime.jsx(DocxParticipants, { session: ready?.room?.session }),
705
- /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => void exportDocument(), disabled: !ready, "aria-label": "Download DOCX", children: [
706
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 14 }),
707
- "DOCX"
708
- ] }),
709
- !snapshot && ready?.canEdit && /* @__PURE__ */ jsxRuntime.jsxs(
710
- "button",
711
- {
712
- className: "native-docx-save",
713
- onClick: () => void saveVersion(),
714
- disabled: !ready || saving || offline,
715
- "data-testid": "native-docx-save",
716
- children: [
717
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 14 }),
718
- saving ? "Saving\u2026" : isDraft ? "Save note" : "Save version"
719
- ]
720
- }
721
- )
722
- ] }),
723
- offline && /* @__PURE__ */ jsxRuntime.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." }),
724
- error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-notice", "data-error": "true", role: "alert", children: [
725
- error,
726
- " ",
727
- /* @__PURE__ */ jsxRuntime.jsxs(
728
- "button",
729
- {
730
- disabled: dirty,
731
- title: dirty ? "Download your unsaved DOCX before reopening the editor" : void 0,
732
- onClick: () => setRetry((x) => x + 1),
733
- children: [
734
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 12 }),
735
- "Retry"
736
- ]
737
- }
738
- )
739
- ] }),
740
- !ready && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-error", role: "status", children: "Opening Word document\u2026" }),
741
- ready && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-body docx-editor", children: /* @__PURE__ */ jsxRuntime.jsx(EditorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsxs(
742
- react$1.DocxEditor.Root,
743
- {
744
- document: ready.document,
745
- modules: ready.modules,
746
- fonts: ready.fonts,
747
- mode: !ready.canEdit || snapshot ? "view" : "suggesting",
748
- reviewDisplayMode: "all-markup",
749
- zoomMode: fixedZoomMode,
750
- author: ready.author,
751
- onReady: editorReady,
752
- onChange: (change) => {
753
- if (change.source !== "load") changed();
754
- },
755
- onFontError: (e) => setError(e.message),
756
- children: [
757
- /* @__PURE__ */ jsxRuntime.jsx(
758
- react$1.DocxEditor.RevisionMarkup,
1027
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1028
+ "section",
1029
+ {
1030
+ className: "native-docx-viewer",
1031
+ "data-testid": "native-docx-editor",
1032
+ "aria-busy": !!stream?.busy,
1033
+ "data-generation-state": stream ? stream.busy ? "generating" : stream.error ? "stopped" : "persisting" : "ready",
1034
+ children: [
1035
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-actions", children: [
1036
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "native-docx-status", role: "status", children: [
1037
+ offline ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { size: 14 }) : !dirty && ready ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14 }) : null,
1038
+ status
1039
+ ] }),
1040
+ /* @__PURE__ */ jsxRuntime.jsx(DocxParticipants, { session: ready?.room?.session }),
1041
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => void exportDocument(), disabled: !ready, "aria-label": "Download DOCX", children: [
1042
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 14 }),
1043
+ "DOCX"
1044
+ ] }),
1045
+ !snapshot && !stream && ready?.canEdit && /* @__PURE__ */ jsxRuntime.jsxs(
1046
+ "button",
759
1047
  {
760
- insertions: { mark: "underline", color: "blue" },
761
- deletions: { mark: "strikethrough", color: "red" },
762
- movedFrom: { mark: "doubleStrikethrough", color: "green" },
763
- movedTo: { mark: "doubleUnderline", color: "green" },
764
- trackMoves: true
1048
+ className: "native-docx-save",
1049
+ onClick: () => void saveVersion(),
1050
+ disabled: !ready || saving || offline,
1051
+ "data-testid": "native-docx-save",
1052
+ children: [
1053
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 14 }),
1054
+ saving ? "Saving\u2026" : isDraft ? "Save note" : "Save version"
1055
+ ]
765
1056
  }
766
- ),
767
- /* @__PURE__ */ jsxRuntime.jsx(DocxRibbon, { onSave: () => void saveVersion() }),
1057
+ )
1058
+ ] }),
1059
+ offline && /* @__PURE__ */ jsxRuntime.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." }),
1060
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-notice", "data-error": "true", role: "alert", children: [
1061
+ error,
1062
+ " ",
768
1063
  /* @__PURE__ */ jsxRuntime.jsxs(
769
- "div",
1064
+ "button",
770
1065
  {
771
- className: "native-docx-stage",
772
- ref: stageRef,
773
- onMouseDownCapture: (event) => {
774
- if (event.button === 2 && ref.current?.getSelectionPlacement())
775
- event.preventDefault();
776
- },
1066
+ disabled: !stream && dirty,
1067
+ title: dirty ? "Download your unsaved DOCX before reopening the editor" : void 0,
1068
+ onClick: () => stream ? (setError(""), setStreamAttempt((x) => x + 1)) : setRetry((x) => x + 1),
777
1069
  children: [
778
- /* @__PURE__ */ jsxRuntime.jsxs(react$1.DocxEditor.Viewport, { className: "native-docx-viewport", children: [
779
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.Content, {}),
780
- /* @__PURE__ */ jsxRuntime.jsx(
781
- DocxMentions,
782
- {
783
- initialCommentId,
784
- mentions: adapter.mentions,
785
- notify: adapter.notify,
786
- requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
787
- children: /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditorReview, {})
788
- }
789
- ),
790
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.HeaderFooterChrome, {}),
791
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.NotesChrome, {}),
792
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.Equation, {}),
793
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.HyperLink, {}),
794
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.ContextMenu, {})
795
- ] }),
796
- /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.PageNumber, {})
1070
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 12 }),
1071
+ "Retry"
797
1072
  ]
798
1073
  }
799
1074
  )
800
- ]
801
- }
802
- ) }, retry) })
803
- ] });
1075
+ ] }),
1076
+ !ready && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-error", role: "status", children: "Opening Word document\u2026" }),
1077
+ ready && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-body docx-editor", children: /* @__PURE__ */ jsxRuntime.jsx(EditorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsxs(
1078
+ react$1.DocxEditor.Root,
1079
+ {
1080
+ document: ready.document,
1081
+ modules: ready.modules,
1082
+ fonts: ready.fonts,
1083
+ mode: stream ? "edit" : !ready.canEdit || snapshot ? "view" : "suggesting",
1084
+ reviewDisplayMode: "all-markup",
1085
+ zoomMode: fixedZoomMode,
1086
+ author: ready.author,
1087
+ onReady: editorReady,
1088
+ onChange: (change) => {
1089
+ if (change.source !== "load") changed();
1090
+ },
1091
+ onFontError: (e) => setError(e.message),
1092
+ children: [
1093
+ /* @__PURE__ */ jsxRuntime.jsx(
1094
+ react$1.DocxEditor.RevisionMarkup,
1095
+ {
1096
+ insertions: { mark: "underline", color: "blue" },
1097
+ deletions: { mark: "strikethrough", color: "red" },
1098
+ movedFrom: { mark: "doubleStrikethrough", color: "green" },
1099
+ movedTo: { mark: "doubleUnderline", color: "green" },
1100
+ trackMoves: true
1101
+ }
1102
+ ),
1103
+ !stream && /* @__PURE__ */ jsxRuntime.jsx(DocxRibbon, { onSave: () => void saveVersion() }),
1104
+ /* @__PURE__ */ jsxRuntime.jsxs(
1105
+ "div",
1106
+ {
1107
+ className: "native-docx-stage",
1108
+ ref: stageRef,
1109
+ onMouseDownCapture: (event) => {
1110
+ if (event.button === 2 && ref.current?.getSelectionPlacement())
1111
+ event.preventDefault();
1112
+ },
1113
+ children: [
1114
+ /* @__PURE__ */ jsxRuntime.jsxs(react$1.DocxEditor.Viewport, { className: "native-docx-viewport", children: [
1115
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.Content, {}),
1116
+ /* @__PURE__ */ jsxRuntime.jsx(
1117
+ DocxMentions,
1118
+ {
1119
+ initialCommentId,
1120
+ mentions: adapter.mentions,
1121
+ notify: adapter.notify,
1122
+ getAgentStatus: adapter.getAgentStatus,
1123
+ requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
1124
+ children: /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditorReview, { children: () => /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditorReview.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(DocxCommentActivity, {}) }) })
1125
+ }
1126
+ ),
1127
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.HeaderFooterChrome, {}),
1128
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.NotesChrome, {}),
1129
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.Equation, {}),
1130
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.HyperLink, {}),
1131
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.ContextMenu, {})
1132
+ ] }),
1133
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.DocxEditor.PageNumber, {})
1134
+ ]
1135
+ }
1136
+ )
1137
+ ]
1138
+ }
1139
+ ) }, retry) })
1140
+ ]
1141
+ }
1142
+ );
804
1143
  }
805
1144
  var loaded = /* @__PURE__ */ new Map();
806
1145
  function loadDocxFonts(baseUrl = "/native-docx/fonts") {
@@ -865,12 +1204,65 @@ async function loadFonts(baseUrl) {
865
1204
  )
866
1205
  };
867
1206
  }
1207
+ function StreamedWordArtifact({
1208
+ adapter,
1209
+ content,
1210
+ title,
1211
+ busy,
1212
+ error,
1213
+ draftId,
1214
+ sourceDocumentId,
1215
+ onSaved
1216
+ }) {
1217
+ const [documentId, setDocumentId] = React2.useState();
1218
+ const saving = React2.useRef(null);
1219
+ async function persist(bytes) {
1220
+ if (saving.current) return saving.current;
1221
+ saving.current = (async () => {
1222
+ const query = new URLSearchParams({ title });
1223
+ if (sourceDocumentId) query.set("parent_ext_id", sourceDocumentId);
1224
+ const response = await adapter.request(`/notes?${query}`, {
1225
+ method: "POST",
1226
+ headers: {
1227
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
1228
+ },
1229
+ body: bytes
1230
+ });
1231
+ if (!response.ok) throw new Error("Could not store the generated Word document.");
1232
+ const result = await response.json();
1233
+ if (!result.document_ext_id) throw new Error("The generated document has no ID.");
1234
+ await Promise.resolve(adapter.onVersionSaved?.(result.document_ext_id)).catch(() => {
1235
+ });
1236
+ setDocumentId(result.document_ext_id);
1237
+ onSaved?.(result.document_ext_id);
1238
+ })();
1239
+ try {
1240
+ await saving.current;
1241
+ } finally {
1242
+ saving.current = null;
1243
+ }
1244
+ }
1245
+ return /* @__PURE__ */ jsxRuntime.jsx(
1246
+ NativeDocxEditor,
1247
+ {
1248
+ adapter,
1249
+ documentId: documentId || draftId,
1250
+ title,
1251
+ draftContent: documentId ? void 0 : "",
1252
+ stream: documentId ? void 0 : { content, busy, error, onGenerated: persist },
1253
+ onSaved
1254
+ },
1255
+ documentId || draftId
1256
+ );
1257
+ }
868
1258
 
1259
+ exports.DocxCommentActivity = DocxCommentActivity;
869
1260
  exports.DocxMentions = DocxMentions;
870
1261
  exports.DocxParticipants = DocxParticipants;
871
1262
  exports.DocxRibbon = DocxRibbon;
872
1263
  exports.NativeDocxEditor = NativeDocxEditor;
1264
+ exports.StreamedWordArtifact = StreamedWordArtifact;
873
1265
  exports.loadDocxFonts = loadDocxFonts;
874
1266
  exports.normalizeDocxExport = normalizeDocxExport;
875
- //# sourceMappingURL=chunk-NNH7N62N.cjs.map
876
- //# sourceMappingURL=chunk-NNH7N62N.cjs.map
1267
+ //# sourceMappingURL=chunk-VMCOH3AY.cjs.map
1268
+ //# sourceMappingURL=chunk-VMCOH3AY.cjs.map