@arbidocs/react 0.4.9 → 0.4.10

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.
@@ -1,7 +1,9 @@
1
1
  import { RedlineWithRegions, RedlineRegion } from './redline-core.cjs';
2
2
  export { RegionKind, buildHybridRedlineMarkdown, buildHybridRedlineWithRegions, revertRedlineRegion } from './redline-core.cjs';
3
3
  import * as _tanstack_react_query from '@tanstack/react-query';
4
+ export { c as cleanDraftMarkdown, p as promoteBoldTitle, r as repairParsedTitle, s as stripAgentPreamble, u as unescapeMarkdown, a as unwrapCitationLinks } from '../stored-markdown-DMwFzWhc.cjs';
4
5
  import { FC, ComponentPropsWithoutRef, ReactNode } from 'react';
6
+ import { ArtifactEvent } from '@arbidocs/sdk/browser';
5
7
 
6
8
  /**
7
9
  * Where a redline is computed. The default runs on the calling thread; a host with large
@@ -87,10 +89,19 @@ declare function toArtifactVersions(chain: readonly VersionedDoc[]): ArtifactVer
87
89
  * (the lineage is just this document, or it is unknown), so unversioned documents stay unlabelled.
88
90
  */
89
91
  declare function versionLabel(versions: readonly ArtifactVersion[], docId: string | null | undefined): string | null;
90
- /** Join parsed chunks into one markdown text (blank line between chunks). */
92
+ /**
93
+ * Join parsed chunks into one markdown text (blank line between chunks).
94
+ *
95
+ * `unescape: true` for an artifact stored from markdown: the backend's parse of a `.md` escapes
96
+ * markdown punctuation, which `unescapeMarkdown` undoes (see its rule), and mangles its title
97
+ * (`repairParsedTitle`). Leave it off for the parse
98
+ * of a PDF or Word file.
99
+ */
91
100
  declare function flattenChunks(chunks: readonly {
92
101
  content?: string | null;
93
- }[] | null | undefined): string;
102
+ }[] | null | undefined, options?: {
103
+ unescape?: boolean;
104
+ }): string;
94
105
  /** Is `id` a real, saved document id? Local drafts have placeholder ids and no backend document. */
95
106
  declare function isDocumentId(id: string | null | undefined): id is string;
96
107
 
@@ -173,13 +184,56 @@ interface ArtifactEditSession {
173
184
  */
174
185
  declare function useArtifactEditSession(options: ArtifactEditSessionOptions): ArtifactEditSession;
175
186
 
176
- /** The saved markdown of a document (its parsed chunks, joined). */
177
- declare function useArtifactText(docId: string | null | undefined, options?: {
178
- enabled?: boolean;
179
- }): {
187
+ /**
188
+ * Where the text of a stored artifact is:
189
+ * - `loading`: the first request is in flight (or there is no document yet);
190
+ * - `parsing`: the backend has not finished parsing the upload (the endpoint answers 404, 422 or
191
+ * 500, or an empty parse, for a few seconds after an upload) and the hook is retrying;
192
+ * - `ready`: the text is here (an empty document is `ready` with `text: null`);
193
+ * - `error`: the retries ran out, or the request was refused (401/403).
194
+ */
195
+ type ArtifactTextStatus = 'loading' | 'parsing' | 'ready' | 'error';
196
+ /** How long `useArtifactText` keeps asking while a document is still being parsed. */
197
+ interface ArtifactTextRetry {
198
+ /** Retries after the first request. Default 12. */
199
+ attempts?: number;
200
+ /** Delay before the first retry; it grows by 1.6x each time. Default 1000 ms. */
201
+ baseDelayMs?: number;
202
+ /** Cap for one delay. Default 5000 ms (12 retries are about 50 s in all). */
203
+ maxDelayMs?: number;
204
+ }
205
+ declare const DEFAULT_ARTIFACT_TEXT_RETRY: Required<ArtifactTextRetry>;
206
+ interface ArtifactText {
207
+ /**
208
+ * The markdown, clean: `undefined` while `loading` or `parsing`, `null` when there is none (an
209
+ * empty document, or `error`).
210
+ */
180
211
  text: string | null | undefined;
212
+ /** `status` is `loading` or `parsing`. */
181
213
  isLoading: boolean;
182
- };
214
+ status: ArtifactTextStatus;
215
+ /** The last request error while `parsing` or after `error`. */
216
+ error: Error | null;
217
+ /** Ask again now (after an `error`). */
218
+ retry: () => void;
219
+ }
220
+ /**
221
+ * The saved markdown of an artifact version (its parsed chunks, joined and unescaped).
222
+ *
223
+ * A freshly uploaded document is parsed asynchronously, and for roughly 3 to 20 seconds the parsed
224
+ * content endpoint answers 404, 422 or 500 (or an empty parse). The hook therefore retries with a
225
+ * growing delay (see `ArtifactTextRetry`) and reports `status: 'parsing'` meanwhile, so a host can
226
+ * show "Preparing editor..." instead of opening an editor on an empty baseline. After the last
227
+ * retry the status is `error`; `retry()` starts a new round.
228
+ *
229
+ * The text is run through `unescapeMarkdown`: an artifact is always stored from markdown, and the
230
+ * backend's parse of a `.md` escapes its punctuation.
231
+ */
232
+ declare function useArtifactText(docId: string | null | undefined, options?: {
233
+ enabled?: boolean;
234
+ retry?: ArtifactTextRetry;
235
+ }): ArtifactText;
236
+
183
237
  /** The version lineage of an artifact in a workspace, oldest first. */
184
238
  declare function useArtifactVersions(workspaceId: string | undefined, docId: string | null | undefined): ArtifactVersion[];
185
239
  interface SaveArtifactVersionInput {
@@ -236,6 +290,13 @@ interface ArtifactSessionView {
236
290
  onCompare: () => void;
237
291
  onStopCompare: () => void;
238
292
  onToggleTrackChanges: () => void;
293
+ /**
294
+ * Whether the text is ready. `ArtifactEditor` renders "Preparing editor..." while it is
295
+ * `loading` or `parsing` (a fresh upload is parsed for a few seconds) and an error with Retry.
296
+ */
297
+ status: ArtifactTextStatus;
298
+ /** Ask for the text again after an `error`. */
299
+ onRetry: () => void;
239
300
  }
240
301
  interface ArtifactSession extends ArtifactEditSession {
241
302
  /** The open document: the new real id once a local draft has been saved. */
@@ -243,6 +304,8 @@ interface ArtifactSession extends ArtifactEditSession {
243
304
  /** The open version is an unsaved local draft (no backend document yet). */
244
305
  isLocal: boolean;
245
306
  isLoading: boolean;
307
+ /** See `ArtifactTextStatus`. */
308
+ status: ArtifactTextStatus;
246
309
  versions: ArtifactVersion[];
247
310
  /** "v2" for any version in the lineage. */
248
311
  labelOf: (id: string | null | undefined) => string | null;
@@ -289,4 +352,53 @@ interface ArtifactRedlineComponents {
289
352
  */
290
353
  declare function renderArtifactMarkdown(markdown: string, components?: ArtifactRedlineComponents): ReactNode;
291
354
 
292
- export { type ArtifactEditSession, type ArtifactEditSessionOptions, type ArtifactRedlineComponents, type ArtifactSaveResult, type ArtifactSession, type ArtifactSessionOptions, type ArtifactSessionView, type ArtifactVersion, type RedlineEngine, RedlineRegion, type RedlineState, RedlineWithRegions, type SaveArtifactVersionInput, type UseRedlineOptions, type UseRedlineResult, type VersionedDoc, buildVersionChain, flattenChunks, isDocumentId, mainThreadRedlineEngine, renderArtifactMarkdown, toArtifactVersions, useArtifactEditSession, useArtifactSession, useArtifactText, useArtifactVersions, useRedline, useSaveArtifactVersion, versionLabel };
355
+ /**
356
+ * Where a drafted artifact is in its STREAM -> PERSIST -> EDIT life:
357
+ * - `streaming`: the agent is still working (or has produced nothing final): show the live canvas;
358
+ * - `persisting`: the finished draft is being stored as version 1 of the artifact;
359
+ * - `parsing`: stored; the backend is still parsing it, so there is no editable text yet;
360
+ * - `ready`: the text is readable: mount the editor on `docId`;
361
+ * - `error`: storing failed or the parse never produced text (`retry()` starts over).
362
+ */
363
+ type StreamedArtifactPhase = 'streaming' | 'persisting' | 'parsing' | 'ready' | 'error';
364
+ interface UseStreamedArtifactOptions {
365
+ /** The artifacts of the agent run (`useConnectedAiTask().artifacts`). */
366
+ artifacts: ArtifactEvent[];
367
+ workspaceId: string | undefined;
368
+ /**
369
+ * The run has stopped: not busy and not failed. Nothing is stored before this, so a draft that
370
+ * is still streaming is never persisted half-written.
371
+ */
372
+ settled: boolean;
373
+ /** Make the stored markdown from the streamed content. Defaults to `cleanDraftMarkdown`. */
374
+ clean?: (streamed: string) => string;
375
+ /** Processing config for the stored version. */
376
+ configId?: string | null;
377
+ /** How long to wait for the parse (see `useArtifactText`). */
378
+ parseRetry?: ArtifactTextRetry;
379
+ }
380
+ interface StreamedArtifact {
381
+ phase: StreamedArtifactPhase;
382
+ /** The finished streamed artifact (its `title` names the stored versions). */
383
+ artifact: ArtifactEvent | undefined;
384
+ /** The backend document to edit, once there is one (while `parsing` and `ready`). */
385
+ docId: string | undefined;
386
+ error: Error | null;
387
+ /** After an `error`: store again or ask for the parse again. */
388
+ retry: () => void;
389
+ }
390
+ /**
391
+ * The lifecycle of an agent-drafted artifact, so a host passes the stream and renders the phase.
392
+ *
393
+ * While the agent streams, the draft lives only on the canvas. Once the run is `settled` and the
394
+ * draft is complete, a draft that is not yet a backend document is cleaned (`cleanDraftMarkdown`)
395
+ * and stored ONCE as the artifact's first version (`wpType: 'artifact'`); a draft that already is a
396
+ * backend document passes through. The hook then waits for the parse (`useArtifactText`, which
397
+ * retries) and reports `ready` with the `docId` to open in an editor.
398
+ *
399
+ * Needs an `<ArbiProvider>` and a workspace. `@arbidocs/blocks/connect` has the whole thing as a
400
+ * component (`StreamedArtifact`).
401
+ */
402
+ declare function useStreamedArtifact({ artifacts, workspaceId, settled, clean, configId, parseRetry, }: UseStreamedArtifactOptions): StreamedArtifact;
403
+
404
+ export { type ArtifactEditSession, type ArtifactEditSessionOptions, type ArtifactRedlineComponents, type ArtifactSaveResult, type ArtifactSession, type ArtifactSessionOptions, type ArtifactSessionView, type ArtifactText, type ArtifactTextRetry, type ArtifactTextStatus, type ArtifactVersion, DEFAULT_ARTIFACT_TEXT_RETRY, type RedlineEngine, RedlineRegion, type RedlineState, RedlineWithRegions, type SaveArtifactVersionInput, type StreamedArtifact, type StreamedArtifactPhase, type UseRedlineOptions, type UseRedlineResult, type UseStreamedArtifactOptions, type VersionedDoc, buildVersionChain, flattenChunks, isDocumentId, mainThreadRedlineEngine, renderArtifactMarkdown, toArtifactVersions, useArtifactEditSession, useArtifactSession, useArtifactText, useArtifactVersions, useRedline, useSaveArtifactVersion, useStreamedArtifact, versionLabel };
@@ -1,7 +1,9 @@
1
1
  import { RedlineWithRegions, RedlineRegion } from './redline-core.js';
2
2
  export { RegionKind, buildHybridRedlineMarkdown, buildHybridRedlineWithRegions, revertRedlineRegion } from './redline-core.js';
3
3
  import * as _tanstack_react_query from '@tanstack/react-query';
4
+ export { c as cleanDraftMarkdown, p as promoteBoldTitle, r as repairParsedTitle, s as stripAgentPreamble, u as unescapeMarkdown, a as unwrapCitationLinks } from '../stored-markdown-DMwFzWhc.js';
4
5
  import { FC, ComponentPropsWithoutRef, ReactNode } from 'react';
6
+ import { ArtifactEvent } from '@arbidocs/sdk/browser';
5
7
 
6
8
  /**
7
9
  * Where a redline is computed. The default runs on the calling thread; a host with large
@@ -87,10 +89,19 @@ declare function toArtifactVersions(chain: readonly VersionedDoc[]): ArtifactVer
87
89
  * (the lineage is just this document, or it is unknown), so unversioned documents stay unlabelled.
88
90
  */
89
91
  declare function versionLabel(versions: readonly ArtifactVersion[], docId: string | null | undefined): string | null;
90
- /** Join parsed chunks into one markdown text (blank line between chunks). */
92
+ /**
93
+ * Join parsed chunks into one markdown text (blank line between chunks).
94
+ *
95
+ * `unescape: true` for an artifact stored from markdown: the backend's parse of a `.md` escapes
96
+ * markdown punctuation, which `unescapeMarkdown` undoes (see its rule), and mangles its title
97
+ * (`repairParsedTitle`). Leave it off for the parse
98
+ * of a PDF or Word file.
99
+ */
91
100
  declare function flattenChunks(chunks: readonly {
92
101
  content?: string | null;
93
- }[] | null | undefined): string;
102
+ }[] | null | undefined, options?: {
103
+ unescape?: boolean;
104
+ }): string;
94
105
  /** Is `id` a real, saved document id? Local drafts have placeholder ids and no backend document. */
95
106
  declare function isDocumentId(id: string | null | undefined): id is string;
96
107
 
@@ -173,13 +184,56 @@ interface ArtifactEditSession {
173
184
  */
174
185
  declare function useArtifactEditSession(options: ArtifactEditSessionOptions): ArtifactEditSession;
175
186
 
176
- /** The saved markdown of a document (its parsed chunks, joined). */
177
- declare function useArtifactText(docId: string | null | undefined, options?: {
178
- enabled?: boolean;
179
- }): {
187
+ /**
188
+ * Where the text of a stored artifact is:
189
+ * - `loading`: the first request is in flight (or there is no document yet);
190
+ * - `parsing`: the backend has not finished parsing the upload (the endpoint answers 404, 422 or
191
+ * 500, or an empty parse, for a few seconds after an upload) and the hook is retrying;
192
+ * - `ready`: the text is here (an empty document is `ready` with `text: null`);
193
+ * - `error`: the retries ran out, or the request was refused (401/403).
194
+ */
195
+ type ArtifactTextStatus = 'loading' | 'parsing' | 'ready' | 'error';
196
+ /** How long `useArtifactText` keeps asking while a document is still being parsed. */
197
+ interface ArtifactTextRetry {
198
+ /** Retries after the first request. Default 12. */
199
+ attempts?: number;
200
+ /** Delay before the first retry; it grows by 1.6x each time. Default 1000 ms. */
201
+ baseDelayMs?: number;
202
+ /** Cap for one delay. Default 5000 ms (12 retries are about 50 s in all). */
203
+ maxDelayMs?: number;
204
+ }
205
+ declare const DEFAULT_ARTIFACT_TEXT_RETRY: Required<ArtifactTextRetry>;
206
+ interface ArtifactText {
207
+ /**
208
+ * The markdown, clean: `undefined` while `loading` or `parsing`, `null` when there is none (an
209
+ * empty document, or `error`).
210
+ */
180
211
  text: string | null | undefined;
212
+ /** `status` is `loading` or `parsing`. */
181
213
  isLoading: boolean;
182
- };
214
+ status: ArtifactTextStatus;
215
+ /** The last request error while `parsing` or after `error`. */
216
+ error: Error | null;
217
+ /** Ask again now (after an `error`). */
218
+ retry: () => void;
219
+ }
220
+ /**
221
+ * The saved markdown of an artifact version (its parsed chunks, joined and unescaped).
222
+ *
223
+ * A freshly uploaded document is parsed asynchronously, and for roughly 3 to 20 seconds the parsed
224
+ * content endpoint answers 404, 422 or 500 (or an empty parse). The hook therefore retries with a
225
+ * growing delay (see `ArtifactTextRetry`) and reports `status: 'parsing'` meanwhile, so a host can
226
+ * show "Preparing editor..." instead of opening an editor on an empty baseline. After the last
227
+ * retry the status is `error`; `retry()` starts a new round.
228
+ *
229
+ * The text is run through `unescapeMarkdown`: an artifact is always stored from markdown, and the
230
+ * backend's parse of a `.md` escapes its punctuation.
231
+ */
232
+ declare function useArtifactText(docId: string | null | undefined, options?: {
233
+ enabled?: boolean;
234
+ retry?: ArtifactTextRetry;
235
+ }): ArtifactText;
236
+
183
237
  /** The version lineage of an artifact in a workspace, oldest first. */
184
238
  declare function useArtifactVersions(workspaceId: string | undefined, docId: string | null | undefined): ArtifactVersion[];
185
239
  interface SaveArtifactVersionInput {
@@ -236,6 +290,13 @@ interface ArtifactSessionView {
236
290
  onCompare: () => void;
237
291
  onStopCompare: () => void;
238
292
  onToggleTrackChanges: () => void;
293
+ /**
294
+ * Whether the text is ready. `ArtifactEditor` renders "Preparing editor..." while it is
295
+ * `loading` or `parsing` (a fresh upload is parsed for a few seconds) and an error with Retry.
296
+ */
297
+ status: ArtifactTextStatus;
298
+ /** Ask for the text again after an `error`. */
299
+ onRetry: () => void;
239
300
  }
240
301
  interface ArtifactSession extends ArtifactEditSession {
241
302
  /** The open document: the new real id once a local draft has been saved. */
@@ -243,6 +304,8 @@ interface ArtifactSession extends ArtifactEditSession {
243
304
  /** The open version is an unsaved local draft (no backend document yet). */
244
305
  isLocal: boolean;
245
306
  isLoading: boolean;
307
+ /** See `ArtifactTextStatus`. */
308
+ status: ArtifactTextStatus;
246
309
  versions: ArtifactVersion[];
247
310
  /** "v2" for any version in the lineage. */
248
311
  labelOf: (id: string | null | undefined) => string | null;
@@ -289,4 +352,53 @@ interface ArtifactRedlineComponents {
289
352
  */
290
353
  declare function renderArtifactMarkdown(markdown: string, components?: ArtifactRedlineComponents): ReactNode;
291
354
 
292
- export { type ArtifactEditSession, type ArtifactEditSessionOptions, type ArtifactRedlineComponents, type ArtifactSaveResult, type ArtifactSession, type ArtifactSessionOptions, type ArtifactSessionView, type ArtifactVersion, type RedlineEngine, RedlineRegion, type RedlineState, RedlineWithRegions, type SaveArtifactVersionInput, type UseRedlineOptions, type UseRedlineResult, type VersionedDoc, buildVersionChain, flattenChunks, isDocumentId, mainThreadRedlineEngine, renderArtifactMarkdown, toArtifactVersions, useArtifactEditSession, useArtifactSession, useArtifactText, useArtifactVersions, useRedline, useSaveArtifactVersion, versionLabel };
355
+ /**
356
+ * Where a drafted artifact is in its STREAM -> PERSIST -> EDIT life:
357
+ * - `streaming`: the agent is still working (or has produced nothing final): show the live canvas;
358
+ * - `persisting`: the finished draft is being stored as version 1 of the artifact;
359
+ * - `parsing`: stored; the backend is still parsing it, so there is no editable text yet;
360
+ * - `ready`: the text is readable: mount the editor on `docId`;
361
+ * - `error`: storing failed or the parse never produced text (`retry()` starts over).
362
+ */
363
+ type StreamedArtifactPhase = 'streaming' | 'persisting' | 'parsing' | 'ready' | 'error';
364
+ interface UseStreamedArtifactOptions {
365
+ /** The artifacts of the agent run (`useConnectedAiTask().artifacts`). */
366
+ artifacts: ArtifactEvent[];
367
+ workspaceId: string | undefined;
368
+ /**
369
+ * The run has stopped: not busy and not failed. Nothing is stored before this, so a draft that
370
+ * is still streaming is never persisted half-written.
371
+ */
372
+ settled: boolean;
373
+ /** Make the stored markdown from the streamed content. Defaults to `cleanDraftMarkdown`. */
374
+ clean?: (streamed: string) => string;
375
+ /** Processing config for the stored version. */
376
+ configId?: string | null;
377
+ /** How long to wait for the parse (see `useArtifactText`). */
378
+ parseRetry?: ArtifactTextRetry;
379
+ }
380
+ interface StreamedArtifact {
381
+ phase: StreamedArtifactPhase;
382
+ /** The finished streamed artifact (its `title` names the stored versions). */
383
+ artifact: ArtifactEvent | undefined;
384
+ /** The backend document to edit, once there is one (while `parsing` and `ready`). */
385
+ docId: string | undefined;
386
+ error: Error | null;
387
+ /** After an `error`: store again or ask for the parse again. */
388
+ retry: () => void;
389
+ }
390
+ /**
391
+ * The lifecycle of an agent-drafted artifact, so a host passes the stream and renders the phase.
392
+ *
393
+ * While the agent streams, the draft lives only on the canvas. Once the run is `settled` and the
394
+ * draft is complete, a draft that is not yet a backend document is cleaned (`cleanDraftMarkdown`)
395
+ * and stored ONCE as the artifact's first version (`wpType: 'artifact'`); a draft that already is a
396
+ * backend document passes through. The hook then waits for the parse (`useArtifactText`, which
397
+ * retries) and reports `ready` with the `docId` to open in an editor.
398
+ *
399
+ * Needs an `<ArbiProvider>` and a workspace. `@arbidocs/blocks/connect` has the whole thing as a
400
+ * component (`StreamedArtifact`).
401
+ */
402
+ declare function useStreamedArtifact({ artifacts, workspaceId, settled, clean, configId, parseRetry, }: UseStreamedArtifactOptions): StreamedArtifact;
403
+
404
+ export { type ArtifactEditSession, type ArtifactEditSessionOptions, type ArtifactRedlineComponents, type ArtifactSaveResult, type ArtifactSession, type ArtifactSessionOptions, type ArtifactSessionView, type ArtifactText, type ArtifactTextRetry, type ArtifactTextStatus, type ArtifactVersion, DEFAULT_ARTIFACT_TEXT_RETRY, type RedlineEngine, RedlineRegion, type RedlineState, RedlineWithRegions, type SaveArtifactVersionInput, type StreamedArtifact, type StreamedArtifactPhase, type UseRedlineOptions, type UseRedlineResult, type UseStreamedArtifactOptions, type VersionedDoc, buildVersionChain, flattenChunks, isDocumentId, mainThreadRedlineEngine, renderArtifactMarkdown, toArtifactVersions, useArtifactEditSession, useArtifactSession, useArtifactText, useArtifactVersions, useRedline, useSaveArtifactVersion, useStreamedArtifact, versionLabel };
@@ -1,11 +1,12 @@
1
- import { useParsedDocument, useDocuments, arbiQueryKeys } from '../chunk-R5SR7R6W.js';
1
+ import { repairParsedTitle, unescapeMarkdown, arbiQueryKeys, useDocuments, cleanDraftMarkdown } from '../chunk-UV5ZEUNV.js';
2
+ export { cleanDraftMarkdown, promoteBoldTitle, repairParsedTitle, stripAgentPreamble, unescapeMarkdown, unwrapCitationLinks } from '../chunk-UV5ZEUNV.js';
2
3
  import { revertRedlineRegion, buildHybridRedlineWithRegions, buildHybridRedlineMarkdown } from '../chunk-RKWNX32A.js';
3
4
  export { buildHybridRedlineMarkdown, buildHybridRedlineWithRegions, revertRedlineRegion } from '../chunk-RKWNX32A.js';
5
+ import { useArbi } from '../chunk-J4MX2W4H.js';
4
6
  import { MarkdownRenderer } from '../chunk-TGGQGFS3.js';
5
7
  import '../chunk-SN5YV6FF.js';
6
- import { useArbi } from '../chunk-J4MX2W4H.js';
7
8
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
8
- import { useQueryClient, useMutation } from '@tanstack/react-query';
9
+ import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
9
10
  import { jsx } from 'react/jsx-runtime';
10
11
 
11
12
  // src/artifacts/redline-engine.ts
@@ -129,8 +130,9 @@ function versionLabel(versions, docId) {
129
130
  const index = versions.findIndex((v) => v.id === docId);
130
131
  return index === -1 ? null : `v${index + 1}`;
131
132
  }
132
- function flattenChunks(chunks) {
133
- return (chunks ?? []).map((c) => (c.content ?? "").trim()).join("\n\n");
133
+ function flattenChunks(chunks, options) {
134
+ const text = (chunks ?? []).map((c) => (c.content ?? "").trim()).join("\n\n");
135
+ return options?.unescape ? repairParsedTitle(unescapeMarkdown(text)) : text;
134
136
  }
135
137
  var DOC_ID = /^doc/;
136
138
  function isDocumentId(id) {
@@ -311,19 +313,71 @@ function useArtifactEditSession(options) {
311
313
  ]
312
314
  );
313
315
  }
316
+ var DEFAULT_ARTIFACT_TEXT_RETRY = {
317
+ attempts: 12,
318
+ baseDelayMs: 1e3,
319
+ maxDelayMs: 5e3
320
+ };
321
+ var NotParsedYetError = class extends Error {
322
+ constructor() {
323
+ super("The document has no parsed text yet");
324
+ }
325
+ };
326
+ var isRefusal = (error) => {
327
+ const status = error;
328
+ const code = status?.status ?? status?.response?.status;
329
+ return code === 401 || code === 403;
330
+ };
314
331
  function useArtifactText(docId, options) {
315
- const real = isDocumentId(docId);
316
- const { data, isLoading, isError } = useParsedDocument(real ? docId : void 0, "marker", {
317
- enabled: (options?.enabled ?? true) && real
332
+ const arbi = useArbi();
333
+ const { attempts, baseDelayMs, maxDelayMs } = {
334
+ ...DEFAULT_ARTIFACT_TEXT_RETRY,
335
+ ...options?.retry
336
+ };
337
+ const enabled = (options?.enabled ?? true) && isDocumentId(docId) && arbi.hasWorkspace;
338
+ const query = useQuery({
339
+ // Under the shared `parsedDoc` key, so a document's invalidation reaches it too.
340
+ queryKey: [...arbiQueryKeys.parsedDoc(docId ?? "", "marker"), "artifact-text"],
341
+ queryFn: async () => {
342
+ const parsed = await arbi.documents.getParsedContent(docId, "marker");
343
+ if (!parsed.chunks?.some((c) => c.content?.trim())) throw new NotParsedYetError();
344
+ return flattenChunks(parsed.chunks, { unescape: true });
345
+ },
346
+ enabled,
347
+ retry: (failureCount, error) => !isRefusal(error) && failureCount < attempts,
348
+ retryDelay: (failureCount) => Math.min(baseDelayMs * 1.6 ** failureCount, maxDelayMs)
318
349
  });
319
- const text = useMemo(() => {
320
- if (!real) return void 0;
321
- if (isError) return null;
322
- if (!data) return void 0;
323
- return data.chunks?.length ? flattenChunks(data.chunks) : null;
324
- }, [real, data, isError]);
325
- return { text, isLoading: real && isLoading };
350
+ const { refetch } = query;
351
+ const retry = useCallback(() => void refetch(), [refetch]);
352
+ return useMemo(() => {
353
+ const error = query.error;
354
+ const neverParsed = error instanceof NotParsedYetError;
355
+ let status = "loading";
356
+ if (!enabled) status = "loading";
357
+ else if (query.isSuccess || query.isError && neverParsed) status = "ready";
358
+ else if (query.isError) status = "error";
359
+ else if (query.failureCount > 0) status = "parsing";
360
+ const text = !enabled ? void 0 : query.isSuccess ? query.data : status === "ready" || status === "error" ? null : void 0;
361
+ return {
362
+ text,
363
+ isLoading: enabled && (status === "loading" || status === "parsing"),
364
+ status,
365
+ error: status === "ready" ? null : error ?? query.failureReason ?? null,
366
+ retry
367
+ };
368
+ }, [
369
+ enabled,
370
+ query.isSuccess,
371
+ query.isError,
372
+ query.data,
373
+ query.error,
374
+ query.failureCount,
375
+ query.failureReason,
376
+ retry
377
+ ]);
326
378
  }
379
+
380
+ // src/artifacts/useArtifactSession.ts
327
381
  function useArtifactVersions(workspaceId, docId) {
328
382
  const { data: documents } = useDocuments(workspaceId);
329
383
  return useMemo(
@@ -378,7 +432,7 @@ function useArtifactSession(options) {
378
432
  },
379
433
  [onCompareChange]
380
434
  );
381
- const { text, isLoading } = useArtifactText(docId);
435
+ const { text, isLoading, status, retry } = useArtifactText(docId);
382
436
  const versions = useArtifactVersions(workspaceId, isLocal ? null : docId);
383
437
  const { text: compareText, isLoading: compareLoading } = useArtifactText(activeCompareId, {
384
438
  enabled: !!activeCompareId
@@ -427,6 +481,7 @@ function useArtifactSession(options) {
427
481
  docId,
428
482
  isLocal,
429
483
  isLoading,
484
+ status,
430
485
  versions,
431
486
  labelOf: (id) => versionLabel(versions, id),
432
487
  view: {
@@ -445,7 +500,10 @@ function useArtifactSession(options) {
445
500
  onSave: () => void session.save(),
446
501
  onCompare: () => session.compare(),
447
502
  onStopCompare: session.stopCompare,
448
- onToggleTrackChanges: () => session.setShowTrackChanges(!session.showTrackChanges)
503
+ onToggleTrackChanges: () => session.setShowTrackChanges(!session.showTrackChanges),
504
+ // A local draft has its text already: it is ready.
505
+ status: isLocal ? "ready" : status,
506
+ onRetry: retry
449
507
  },
450
508
  editorProps: {
451
509
  markdown: session.content,
@@ -457,13 +515,64 @@ function useArtifactSession(options) {
457
515
  onRevert: (regionId) => void session.revertRegion(regionId)
458
516
  }
459
517
  }),
460
- [session, redline, isLoading, isLocal, versions, docId]
518
+ [session, redline, isLoading, status, retry, isLocal, versions, docId]
461
519
  );
462
520
  }
463
521
  function renderArtifactMarkdown(markdown, components) {
464
522
  return /* @__PURE__ */ jsx(MarkdownRenderer, { components, children: markdown });
465
523
  }
524
+ function useStreamedArtifact({
525
+ artifacts,
526
+ workspaceId,
527
+ settled,
528
+ clean = cleanDraftMarkdown,
529
+ configId,
530
+ parseRetry
531
+ }) {
532
+ const artifact = artifacts.find((a) => a.is_complete);
533
+ const { mutateAsync } = useSaveArtifactVersion();
534
+ const [saved, setSaved] = useState(null);
535
+ const [saveError, setSaveError] = useState(null);
536
+ const [attempt, setAttempt] = useState(0);
537
+ const started = useRef(null);
538
+ const content = artifact?.content ?? "";
539
+ const title = artifact?.title ?? "Draft";
540
+ const passThrough = !!artifact && isDocumentId(artifact.doc_ext_id);
541
+ const needsSave = settled && !!artifact && !passThrough && !!content.trim() && !!workspaceId;
542
+ useEffect(() => {
543
+ const key = `${attempt}:${content}`;
544
+ if (!needsSave || started.current === key) return;
545
+ started.current = key;
546
+ setSaveError(null);
547
+ mutateAsync({
548
+ title,
549
+ content: clean(content),
550
+ workspaceId,
551
+ configId,
552
+ wpType: "artifact"
553
+ }).then((r) => {
554
+ if (r.docId) setSaved({ content, docId: r.docId });
555
+ else setSaveError(new Error("The draft was stored but the server returned no document id"));
556
+ }).catch((e) => setSaveError(e instanceof Error ? e : new Error(String(e))));
557
+ }, [needsSave, content, title, workspaceId, configId, attempt, mutateAsync]);
558
+ const docId = !artifact ? void 0 : passThrough ? artifact.doc_ext_id : saved?.content === content ? saved.docId : void 0;
559
+ const text = useArtifactText(docId, { enabled: settled, retry: parseRetry });
560
+ const { retry: retryText } = text;
561
+ const retry = useCallback(() => {
562
+ if (saveError) setAttempt((n) => n + 1);
563
+ else retryText();
564
+ }, [saveError, retryText]);
565
+ let phase;
566
+ if (!settled || !artifact) phase = "streaming";
567
+ else if (saveError) phase = "error";
568
+ else if (!docId) phase = needsSave ? "persisting" : "streaming";
569
+ else if (text.status === "ready" && text.text) phase = "ready";
570
+ else if (text.status === "error" || text.status === "ready") phase = "error";
571
+ else phase = "parsing";
572
+ const error = phase !== "error" ? null : saveError ?? text.error ?? new Error("The document has no text to edit");
573
+ return { phase, artifact, docId, error, retry };
574
+ }
466
575
 
467
- export { buildVersionChain, flattenChunks, isDocumentId, mainThreadRedlineEngine, renderArtifactMarkdown, toArtifactVersions, useArtifactEditSession, useArtifactSession, useArtifactText, useArtifactVersions, useRedline, useSaveArtifactVersion, versionLabel };
576
+ export { DEFAULT_ARTIFACT_TEXT_RETRY, buildVersionChain, flattenChunks, isDocumentId, mainThreadRedlineEngine, renderArtifactMarkdown, toArtifactVersions, useArtifactEditSession, useArtifactSession, useArtifactText, useArtifactVersions, useRedline, useSaveArtifactVersion, useStreamedArtifact, versionLabel };
468
577
  //# sourceMappingURL=index.js.map
469
578
  //# sourceMappingURL=index.js.map