@arbidocs/react 0.5.124 → 0.5.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifacts/index.cjs +81 -38
- package/dist/artifacts/index.cjs.map +1 -1
- package/dist/artifacts/index.d.ts +3 -0
- package/dist/artifacts/useArtifactSession.js +48 -4
- package/dist/artifacts/useArtifactSession.js.map +2 -2
- package/dist/artifacts/versions.js +2 -1
- package/dist/artifacts/versions.js.map +2 -2
- package/dist/calls/livekit/index.cjs +42 -42
- package/dist/chat/turn-flush.js +3 -3
- package/dist/chat/turn-flush.js.map +2 -2
- package/dist/{chunk-AB2A6F6T.cjs → chunk-4NGWDVHD.cjs} +15 -6
- package/dist/chunk-4NGWDVHD.cjs.map +1 -0
- package/dist/{chunk-WUYSVTKE.cjs → chunk-6RRKO4ZK.cjs} +4 -4
- package/dist/{chunk-WUYSVTKE.cjs.map → chunk-6RRKO4ZK.cjs.map} +1 -1
- package/dist/{chunk-FAJLJDWS.cjs → chunk-FOBB5U4Y.cjs} +5 -5
- package/dist/{chunk-FAJLJDWS.cjs.map → chunk-FOBB5U4Y.cjs.map} +1 -1
- package/dist/{chunk-3JAEX57E.cjs → chunk-G5DYDBQN.cjs} +4 -4
- package/dist/{chunk-3JAEX57E.cjs.map → chunk-G5DYDBQN.cjs.map} +1 -1
- package/dist/{chunk-7L7QRIQ2.cjs → chunk-H3XHBIZ6.cjs} +5 -5
- package/dist/{chunk-7L7QRIQ2.cjs.map → chunk-H3XHBIZ6.cjs.map} +1 -1
- package/dist/{chunk-NDIKMOBJ.cjs → chunk-KULS4YWP.cjs} +4 -3
- package/dist/chunk-KULS4YWP.cjs.map +1 -0
- package/dist/{chunk-GKGAPU6T.cjs → chunk-NK5KFHXP.cjs} +17 -2
- package/dist/chunk-NK5KFHXP.cjs.map +1 -0
- package/dist/{chunk-K6ELFSG6.cjs → chunk-TNJ5T2MV.cjs} +25 -25
- package/dist/{chunk-K6ELFSG6.cjs.map → chunk-TNJ5T2MV.cjs.map} +1 -1
- package/dist/document-tools/core.cjs +97 -97
- package/dist/document-tools/core.d.ts +1 -0
- package/dist/document-tools/index.cjs +99 -99
- package/dist/entities/EntityPill.js +12 -4
- package/dist/entities/EntityPill.js.map +2 -2
- package/dist/entities/SharingEntity.js +1 -0
- package/dist/entities/SharingEntity.js.map +2 -2
- package/dist/index.cjs +1013 -1020
- package/dist/index.cjs.map +1 -1
- package/dist/{index.d-CXjQ1akb.d.ts → index.d-DbcrFwS6.d.ts} +2 -0
- package/dist/index.d.ts +15 -13
- package/dist/shell/index.cjs +38 -38
- package/dist/ui/index.cjs +151 -151
- package/dist/ui/index.d.ts +1 -1
- package/package.json +3 -3
- package/dist/chunk-AB2A6F6T.cjs.map +0 -1
- package/dist/chunk-GKGAPU6T.cjs.map +0 -1
- package/dist/chunk-NDIKMOBJ.cjs.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerDocumentTurnFlush } from "../chat/turn-flush.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
3
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
4
|
import { useArbi } from "../useArbi.js";
|
|
4
5
|
import { arbiQueryKeys, useDocumentVersionList } from "../hooks.js";
|
|
@@ -34,13 +35,14 @@ function useSaveArtifactVersion() {
|
|
|
34
35
|
const arbi = useArbi();
|
|
35
36
|
const queryClient = useQueryClient();
|
|
36
37
|
return useMutation({
|
|
37
|
-
mutationFn: async ({ title, content, versionOf, configId, wpType }) => {
|
|
38
|
+
mutationFn: async ({ title, content, versionOf, configId, wpType, autosave }) => {
|
|
38
39
|
const fileName = /\.md$/i.test(title) ? title : `${title}.md`;
|
|
39
40
|
const result = await arbi.documents.uploadFile(
|
|
40
41
|
new Blob([content], { type: "text/markdown" }),
|
|
41
42
|
fileName,
|
|
42
43
|
{
|
|
43
44
|
wpType: wpType ?? "artifact",
|
|
45
|
+
autosave,
|
|
44
46
|
versionOf: versionOf ? parseVersionRef(versionOf).docId : void 0,
|
|
45
47
|
configExtId: configId ?? void 0
|
|
46
48
|
}
|
|
@@ -85,6 +87,7 @@ function useArtifactSession(options) {
|
|
|
85
87
|
enabled: !!activeCompareId
|
|
86
88
|
});
|
|
87
89
|
const saveVersion = useSaveArtifactVersion();
|
|
90
|
+
const autosaveVersion = saveVersion.mutateAsync;
|
|
88
91
|
const current = findVersion(versions, docId);
|
|
89
92
|
const save = useCallback(
|
|
90
93
|
async (content) => {
|
|
@@ -121,6 +124,36 @@ function useArtifactSession(options) {
|
|
|
121
124
|
save,
|
|
122
125
|
onSaved: handleSaved
|
|
123
126
|
});
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (isLocal || !session.hasChanges || session.isSaving) return;
|
|
129
|
+
const timer = setTimeout(() => {
|
|
130
|
+
void autosaveVersion({
|
|
131
|
+
title: title ?? current?.name ?? "Untitled",
|
|
132
|
+
content: session.content,
|
|
133
|
+
versionOf: docId,
|
|
134
|
+
configId,
|
|
135
|
+
autosave: true
|
|
136
|
+
}).catch(() => void 0);
|
|
137
|
+
}, 2500);
|
|
138
|
+
return () => clearTimeout(timer);
|
|
139
|
+
}, [
|
|
140
|
+
isLocal,
|
|
141
|
+
session.content,
|
|
142
|
+
session.hasChanges,
|
|
143
|
+
session.isSaving,
|
|
144
|
+
docId,
|
|
145
|
+
title,
|
|
146
|
+
current?.name,
|
|
147
|
+
configId,
|
|
148
|
+
autosaveVersion
|
|
149
|
+
]);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (!docId) return;
|
|
152
|
+
return registerDocumentTurnFlush(docId, async () => {
|
|
153
|
+
if (session.hasChanges && !await session.save())
|
|
154
|
+
throw new Error("Save the document before sending your message.");
|
|
155
|
+
});
|
|
156
|
+
}, [docId, session]);
|
|
124
157
|
const { redline } = session;
|
|
125
158
|
return useMemo(
|
|
126
159
|
() => ({
|
|
@@ -134,7 +167,7 @@ function useArtifactSession(options) {
|
|
|
134
167
|
view: {
|
|
135
168
|
content: session.baseline,
|
|
136
169
|
isEditing: session.isEditing,
|
|
137
|
-
hasChanges: session.hasChanges,
|
|
170
|
+
hasChanges: session.hasChanges || !!current?.autosave,
|
|
138
171
|
isSaving: session.isSaving,
|
|
139
172
|
canCompare: session.canCompare,
|
|
140
173
|
comparing: !!session.compareId,
|
|
@@ -162,7 +195,18 @@ function useArtifactSession(options) {
|
|
|
162
195
|
onRevert: (regionId) => void session.revertRegion(regionId)
|
|
163
196
|
}
|
|
164
197
|
}),
|
|
165
|
-
[
|
|
198
|
+
[
|
|
199
|
+
session,
|
|
200
|
+
redline,
|
|
201
|
+
isLoading,
|
|
202
|
+
status,
|
|
203
|
+
retry,
|
|
204
|
+
isLocal,
|
|
205
|
+
versions,
|
|
206
|
+
docId,
|
|
207
|
+
current?.id,
|
|
208
|
+
current?.autosave
|
|
209
|
+
]
|
|
166
210
|
);
|
|
167
211
|
}
|
|
168
212
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/artifacts/useArtifactSession.ts"],
|
|
4
|
-
"sourcesContent": ["import { useCallback, useMemo, useState } from 'react'\nimport { useMutation, useQueryClient } from '@tanstack/react-query'\nimport { useArbi } from '../useArbi'\nimport { arbiQueryKeys, useDocumentVersionList } from '../hooks'\nimport { refreshDocuments, useDocuments } from '../documents/documents-data'\nimport { useArtifactText, type ArtifactTextStatus } from './useArtifactText'\nimport {\n documentVersions,\n findVersion,\n isDocumentId,\n parseVersionRef,\n toArtifactVersions,\n versionLabel,\n type ArtifactVersion,\n} from './versions'\nimport {\n useArtifactEditSession,\n type ArtifactEditSession,\n type ArtifactEditSessionOptions,\n type ArtifactSaveResult,\n} from './useArtifactEditSession'\n\n/**\n * The versions of an artifact, oldest first. `docId` is a document id or a version reference\n * (`doc-\u2026@v2`). Every listed document carries its versions; a document that is not in the\n * workspace list asks for its version list.\n */\nexport function useArtifactVersions(\n workspaceId: string | undefined,\n docId: string | null | undefined\n): ArtifactVersion[] {\n const plainId = docId ? parseVersionRef(docId).docId : null\n const { data: documents } = useDocuments(workspaceId)\n const listed = useMemo(\n () => (documents ?? []).find((d) => d.external_id === plainId),\n [documents, plainId]\n )\n // Asked only once the list has loaded and the document is not in it.\n const { data: history } = useDocumentVersionList(plainId, {\n enabled: !!plainId && documents !== undefined && !listed,\n })\n return useMemo(\n () => (listed ? documentVersions(listed) : toArtifactVersions(plainId, history)),\n [listed, history, plainId]\n )\n}\n\nexport interface SaveArtifactVersionInput {\n /** File name of the new version (`.md` is added when missing). */\n title: string\n content: string\n /** Save as the next version of this document (it keeps its id); omit for a new document. */\n versionOf?: string | null\n /** The processing config (auto-title, parsing) to run the upload with. */\n configId?: string | null\n /** `artifact` (default) for AI-style drafts; `source` for a document the user typed. */\n wpType?: 'artifact' | 'source'\n}\n\n/**\n * Save markdown as a NEW immutable version (the next version of `versionOf`'s document, which keeps\n * its id) and refresh the workspace document list, version lists and the document's text.\n */\nexport function useSaveArtifactVersion() {\n const arbi = useArbi()\n const queryClient = useQueryClient()\n return useMutation<ArtifactSaveResult, Error, SaveArtifactVersionInput>({\n mutationFn: async ({ title, content, versionOf, configId, wpType }) => {\n // The backend classifies uploads by extension: a title without one is silently dropped.\n const fileName = /\\.md$/i.test(title) ? title : `${title}.md`\n const result = await arbi.documents.uploadFile(\n new Blob([content], { type: 'text/markdown' }),\n fileName,\n {\n wpType: wpType ?? 'artifact',\n versionOf: versionOf ? parseVersionRef(versionOf).docId : undefined,\n configExtId: configId ?? undefined,\n }\n )\n const docId = result.doc_ext_ids?.[0]\n // The document's row moves to the new version (its version lists re-read with it).\n if (docId) await refreshDocuments(queryClient, arbi, [docId])\n await queryClient.invalidateQueries({ queryKey: arbiQueryKeys.allDocumentVersions() })\n // The document's current text is the new version's now.\n if (docId)\n await queryClient.invalidateQueries({ queryKey: arbiQueryKeys.parsedDocument(docId) })\n return { docId, version: result.version ?? undefined }\n },\n })\n}\n\nexport interface ArtifactSessionOptions extends Pick<\n ArtifactEditSessionOptions,\n | 'onSaved'\n | 'onSaveError'\n | 'onCompareChange'\n | 'compareId'\n | 'compareOpensEditor'\n | 'redlineEngine'\n | 'onDirtyChange'\n> {\n workspaceId: string | undefined\n /**\n * The open document (its current version), a version reference `doc-\u2026@v{n}`, or the placeholder\n * id of an unsaved local draft (anything that is not a backend document id, i.e. does not start\n * with `doc`).\n */\n docId: string | null | undefined\n /**\n * The text of a local draft (nothing is fetched for it). The FIRST save uploads it as a real\n * document with no parent and the session switches to the new id; later saves chain from it.\n */\n draftContent?: string\n /** Name for saved versions. Defaults to the open version's file name, else \"Untitled\". */\n title?: string\n /** Processing config for saved versions. */\n configId?: string | null\n}\n\n/** Everything `ArtifactEditor` (`@arbidocs/blocks/artifacts`) takes from a session, flat. */\nexport interface ArtifactSessionView {\n content: string\n isEditing: boolean\n hasChanges: boolean\n isSaving: boolean\n canCompare: boolean\n comparing: boolean\n compareId: string | null\n showTrackChanges: boolean\n versions: ArtifactVersion[]\n currentVersionId: string\n onStartEdit: () => void\n onCancel: () => void\n onSave: () => void\n onCompare: () => void\n onStopCompare: () => void\n onToggleTrackChanges: () => void\n /**\n * Whether the text is ready. `ArtifactEditor` renders \"Preparing editor...\" while it is\n * `loading` or `parsing` (a fresh upload is parsed for a few seconds) and an error with Retry.\n */\n status: ArtifactTextStatus\n /** Ask for the text again after an `error`. */\n onRetry: () => void\n}\n\nexport interface ArtifactSession extends ArtifactEditSession {\n /** The open document: the new real id once a local draft has been saved. */\n docId: string | null | undefined\n /** The open version is an unsaved local draft (no backend document yet). */\n isLocal: boolean\n isLoading: boolean\n /** See `ArtifactTextStatus`. */\n status: ArtifactTextStatus\n versions: ArtifactVersion[]\n /** \"v2\" for any version (reference) of the document. */\n labelOf: (id: string | null | undefined) => string | null\n /** Props for `<ArtifactEditor>`. */\n view: ArtifactSessionView\n /** Props for `<ArtifactMarkdownEditor>` (`@arbidocs/blocks/artifacts/editor`). */\n editorProps: {\n markdown: string\n onChange: (markdown: string) => void\n onNormalized: (markdown: string) => void\n }\n /** Props for `<ArtifactRedlineView>`. */\n redlineProps: {\n state: ArtifactEditSession['redline']['state']\n onRevert: (regionId: string) => void\n }\n}\n\n/**\n * Edit an artifact through the SDK: load it, edit it, save it as a new version, list its versions\n * and compare any two with track changes. Needs an `<ArbiHost>` with an `arbi` and a selected workspace.\n *\n * ```tsx\n * const session = useArtifactSession({ workspaceId, docId, onSaved: ({ docId }) => docId && open(docId) })\n * <ArtifactEditor {...session.view} onOpenVersion={open}\n * editor={<ArtifactMarkdownEditor {...session.editorProps} />}\n * redline={<ArtifactRedlineView {...session.redlineProps} />} />\n * ```\n */\nexport function useArtifactSession(options: ArtifactSessionOptions): ArtifactSession {\n const {\n workspaceId,\n docId: hostDocId,\n draftContent,\n title,\n configId,\n compareId,\n onCompareChange,\n onSaved,\n ...passthrough\n } = options\n // A saved local draft becomes this real document; another host docId resets it.\n const [saved, setSaved] = useState<{\n forId: string | null | undefined\n id: string\n text: string\n } | null>(null)\n const adopted = saved && saved.forId === hostDocId ? saved : null\n const docId = adopted?.id ?? hostDocId\n const isLocal = !isDocumentId(docId)\n\n // Owned here (not in the edit session) because the compare target's text must be loaded first.\n const [ownCompareId, setOwnCompareId] = useState<string | null>(null)\n const activeCompareId = compareId === undefined ? ownCompareId : compareId\n const changeCompare = useCallback(\n (id: string | null) => {\n setOwnCompareId(id)\n onCompareChange?.(id)\n },\n [onCompareChange]\n )\n\n const { text, isLoading, status, retry } = useArtifactText(docId)\n const versions = useArtifactVersions(workspaceId, isLocal ? null : docId)\n const { text: compareText, isLoading: compareLoading } = useArtifactText(activeCompareId, {\n enabled: !!activeCompareId,\n })\n const saveVersion = useSaveArtifactVersion()\n const current = findVersion(versions, docId)\n\n const save = useCallback(\n async (content: string) => {\n const result = await saveVersion.mutateAsync({\n title: title ?? current?.name ?? 'Untitled',\n content,\n versionOf: isLocal ? null : docId,\n configId,\n })\n const latest = versions.reduce((n, v) => Math.max(n, v.number), 0)\n return { ...result, version: result.version ?? (isLocal ? 1 : latest + 1) }\n },\n [saveVersion, title, current?.name, isLocal, docId, configId, versions]\n )\n\n const handleSaved = useCallback(\n (result: ArtifactSaveResult, content: string) => {\n // A host that keeps passing the local draft's placeholder id: follow every save to the real\n // document it created (the first save creates version 1, later ones chain from it).\n if (!isDocumentId(hostDocId) && result.docId)\n setSaved({ forId: hostDocId, id: result.docId, text: content })\n onSaved?.(result, content)\n },\n [hostDocId, onSaved]\n )\n\n const session = useArtifactEditSession({\n ...passthrough,\n docId,\n // A local draft has only its own text; a just-saved one shows what was saved until the parsed\n // text of the new document arrives.\n baseline: text ?? (isLocal ? draftContent : adopted?.text),\n savedFromId: current?.savedFromId,\n compareId: activeCompareId,\n onCompareChange: changeCompare,\n compareText,\n compareLoading,\n save,\n onSaved: handleSaved,\n })\n\n const { redline } = session\n return useMemo<ArtifactSession>(\n () => ({\n ...session,\n docId,\n isLocal,\n isLoading,\n status,\n versions,\n labelOf: (id) => versionLabel(versions, id),\n view: {\n content: session.baseline,\n isEditing: session.isEditing,\n hasChanges: session.hasChanges,\n isSaving: session.isSaving,\n canCompare: session.canCompare,\n comparing: !!session.compareId,\n compareId: session.compareId,\n showTrackChanges: session.showTrackChanges,\n versions,\n currentVersionId: current?.id ?? docId ?? '',\n onStartEdit: session.startEdit,\n onCancel: session.cancel,\n onSave: () => void session.save(),\n onCompare: () => session.compare(),\n onStopCompare: session.stopCompare,\n onToggleTrackChanges: () => session.setShowTrackChanges(!session.showTrackChanges),\n // A local draft has its text already: it is ready.\n status: isLocal ? 'ready' : status,\n onRetry: retry,\n },\n editorProps: {\n markdown: session.content,\n onChange: session.change,\n onNormalized: session.normalized,\n },\n redlineProps: {\n state: redline.state,\n onRevert: (regionId) => void session.revertRegion(regionId),\n },\n }),\n [session, redline, isLoading, status, retry, isLocal, versions, docId]\n )\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,aAAa,SAAS,gBAAgB;
|
|
4
|
+
"sourcesContent": ["import { registerDocumentTurnFlush } from '../chat/turn-flush'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useMutation, useQueryClient } from '@tanstack/react-query'\nimport { useArbi } from '../useArbi'\nimport { arbiQueryKeys, useDocumentVersionList } from '../hooks'\nimport { refreshDocuments, useDocuments } from '../documents/documents-data'\nimport { useArtifactText, type ArtifactTextStatus } from './useArtifactText'\nimport {\n documentVersions,\n findVersion,\n isDocumentId,\n parseVersionRef,\n toArtifactVersions,\n versionLabel,\n type ArtifactVersion,\n} from './versions'\nimport {\n useArtifactEditSession,\n type ArtifactEditSession,\n type ArtifactEditSessionOptions,\n type ArtifactSaveResult,\n} from './useArtifactEditSession'\n\n/**\n * The versions of an artifact, oldest first. `docId` is a document id or a version reference\n * (`doc-\u2026@v2`). Every listed document carries its versions; a document that is not in the\n * workspace list asks for its version list.\n */\nexport function useArtifactVersions(\n workspaceId: string | undefined,\n docId: string | null | undefined\n): ArtifactVersion[] {\n const plainId = docId ? parseVersionRef(docId).docId : null\n const { data: documents } = useDocuments(workspaceId)\n const listed = useMemo(\n () => (documents ?? []).find((d) => d.external_id === plainId),\n [documents, plainId]\n )\n // Asked only once the list has loaded and the document is not in it.\n const { data: history } = useDocumentVersionList(plainId, {\n enabled: !!plainId && documents !== undefined && !listed,\n })\n return useMemo(\n () => (listed ? documentVersions(listed) : toArtifactVersions(plainId, history)),\n [listed, history, plainId]\n )\n}\n\nexport interface SaveArtifactVersionInput {\n /** File name of the new version (`.md` is added when missing). */\n title: string\n content: string\n /** Save as the next version of this document (it keeps its id); omit for a new document. */\n autosave?: boolean\n versionOf?: string | null\n /** The processing config (auto-title, parsing) to run the upload with. */\n configId?: string | null\n /** `artifact` (default) for AI-style drafts; `source` for a document the user typed. */\n wpType?: 'artifact' | 'source'\n}\n\n/**\n * Save markdown as a NEW immutable version (the next version of `versionOf`'s document, which keeps\n * its id) and refresh the workspace document list, version lists and the document's text.\n */\nexport function useSaveArtifactVersion() {\n const arbi = useArbi()\n const queryClient = useQueryClient()\n return useMutation<ArtifactSaveResult, Error, SaveArtifactVersionInput>({\n mutationFn: async ({ title, content, versionOf, configId, wpType, autosave }) => {\n // The backend classifies uploads by extension: a title without one is silently dropped.\n const fileName = /\\.md$/i.test(title) ? title : `${title}.md`\n const result = await arbi.documents.uploadFile(\n new Blob([content], { type: 'text/markdown' }),\n fileName,\n {\n wpType: wpType ?? 'artifact',\n autosave,\n versionOf: versionOf ? parseVersionRef(versionOf).docId : undefined,\n configExtId: configId ?? undefined,\n }\n )\n const docId = result.doc_ext_ids?.[0]\n // The document's row moves to the new version (its version lists re-read with it).\n if (docId) await refreshDocuments(queryClient, arbi, [docId])\n await queryClient.invalidateQueries({ queryKey: arbiQueryKeys.allDocumentVersions() })\n // The document's current text is the new version's now.\n if (docId)\n await queryClient.invalidateQueries({ queryKey: arbiQueryKeys.parsedDocument(docId) })\n return { docId, version: result.version ?? undefined }\n },\n })\n}\n\nexport interface ArtifactSessionOptions extends Pick<\n ArtifactEditSessionOptions,\n | 'onSaved'\n | 'onSaveError'\n | 'onCompareChange'\n | 'compareId'\n | 'compareOpensEditor'\n | 'redlineEngine'\n | 'onDirtyChange'\n> {\n workspaceId: string | undefined\n /**\n * The open document (its current version), a version reference `doc-\u2026@v{n}`, or the placeholder\n * id of an unsaved local draft (anything that is not a backend document id, i.e. does not start\n * with `doc`).\n */\n docId: string | null | undefined\n /**\n * The text of a local draft (nothing is fetched for it). The FIRST save uploads it as a real\n * document with no parent and the session switches to the new id; later saves chain from it.\n */\n draftContent?: string\n /** Name for saved versions. Defaults to the open version's file name, else \"Untitled\". */\n title?: string\n /** Processing config for saved versions. */\n configId?: string | null\n}\n\n/** Everything `ArtifactEditor` (`@arbidocs/blocks/artifacts`) takes from a session, flat. */\nexport interface ArtifactSessionView {\n content: string\n isEditing: boolean\n hasChanges: boolean\n isSaving: boolean\n canCompare: boolean\n comparing: boolean\n compareId: string | null\n showTrackChanges: boolean\n versions: ArtifactVersion[]\n currentVersionId: string\n onStartEdit: () => void\n onCancel: () => void\n onSave: () => void\n onCompare: () => void\n onStopCompare: () => void\n onToggleTrackChanges: () => void\n /**\n * Whether the text is ready. `ArtifactEditor` renders \"Preparing editor...\" while it is\n * `loading` or `parsing` (a fresh upload is parsed for a few seconds) and an error with Retry.\n */\n status: ArtifactTextStatus\n /** Ask for the text again after an `error`. */\n onRetry: () => void\n}\n\nexport interface ArtifactSession extends ArtifactEditSession {\n /** The open document: the new real id once a local draft has been saved. */\n docId: string | null | undefined\n /** The open version is an unsaved local draft (no backend document yet). */\n isLocal: boolean\n isLoading: boolean\n /** See `ArtifactTextStatus`. */\n status: ArtifactTextStatus\n versions: ArtifactVersion[]\n /** \"v2\" for any version (reference) of the document. */\n labelOf: (id: string | null | undefined) => string | null\n /** Props for `<ArtifactEditor>`. */\n view: ArtifactSessionView\n /** Props for `<ArtifactMarkdownEditor>` (`@arbidocs/blocks/artifacts/editor`). */\n editorProps: {\n markdown: string\n onChange: (markdown: string) => void\n onNormalized: (markdown: string) => void\n }\n /** Props for `<ArtifactRedlineView>`. */\n redlineProps: {\n state: ArtifactEditSession['redline']['state']\n onRevert: (regionId: string) => void\n }\n}\n\n/**\n * Edit an artifact through the SDK: load it, edit it, save it as a new version, list its versions\n * and compare any two with track changes. Needs an `<ArbiHost>` with an `arbi` and a selected workspace.\n *\n * ```tsx\n * const session = useArtifactSession({ workspaceId, docId, onSaved: ({ docId }) => docId && open(docId) })\n * <ArtifactEditor {...session.view} onOpenVersion={open}\n * editor={<ArtifactMarkdownEditor {...session.editorProps} />}\n * redline={<ArtifactRedlineView {...session.redlineProps} />} />\n * ```\n */\nexport function useArtifactSession(options: ArtifactSessionOptions): ArtifactSession {\n const {\n workspaceId,\n docId: hostDocId,\n draftContent,\n title,\n configId,\n compareId,\n onCompareChange,\n onSaved,\n ...passthrough\n } = options\n // A saved local draft becomes this real document; another host docId resets it.\n const [saved, setSaved] = useState<{\n forId: string | null | undefined\n id: string\n text: string\n } | null>(null)\n const adopted = saved && saved.forId === hostDocId ? saved : null\n const docId = adopted?.id ?? hostDocId\n const isLocal = !isDocumentId(docId)\n\n // Owned here (not in the edit session) because the compare target's text must be loaded first.\n const [ownCompareId, setOwnCompareId] = useState<string | null>(null)\n const activeCompareId = compareId === undefined ? ownCompareId : compareId\n const changeCompare = useCallback(\n (id: string | null) => {\n setOwnCompareId(id)\n onCompareChange?.(id)\n },\n [onCompareChange]\n )\n\n const { text, isLoading, status, retry } = useArtifactText(docId)\n const versions = useArtifactVersions(workspaceId, isLocal ? null : docId)\n const { text: compareText, isLoading: compareLoading } = useArtifactText(activeCompareId, {\n enabled: !!activeCompareId,\n })\n const saveVersion = useSaveArtifactVersion()\n const autosaveVersion = saveVersion.mutateAsync\n const current = findVersion(versions, docId)\n\n const save = useCallback(\n async (content: string) => {\n const result = await saveVersion.mutateAsync({\n title: title ?? current?.name ?? 'Untitled',\n content,\n versionOf: isLocal ? null : docId,\n configId,\n })\n const latest = versions.reduce((n, v) => Math.max(n, v.number), 0)\n return { ...result, version: result.version ?? (isLocal ? 1 : latest + 1) }\n },\n [saveVersion, title, current?.name, isLocal, docId, configId, versions]\n )\n\n const handleSaved = useCallback(\n (result: ArtifactSaveResult, content: string) => {\n // A host that keeps passing the local draft's placeholder id: follow every save to the real\n // document it created (the first save creates version 1, later ones chain from it).\n if (!isDocumentId(hostDocId) && result.docId)\n setSaved({ forId: hostDocId, id: result.docId, text: content })\n onSaved?.(result, content)\n },\n [hostDocId, onSaved]\n )\n\n const session = useArtifactEditSession({\n ...passthrough,\n docId,\n // A local draft has only its own text; a just-saved one shows what was saved until the parsed\n // text of the new document arrives.\n baseline: text ?? (isLocal ? draftContent : adopted?.text),\n savedFromId: current?.savedFromId,\n compareId: activeCompareId,\n onCompareChange: changeCompare,\n compareText,\n compareLoading,\n save,\n onSaved: handleSaved,\n })\n\n useEffect(() => {\n if (isLocal || !session.hasChanges || session.isSaving) return\n const timer = setTimeout(() => {\n void autosaveVersion({\n title: title ?? current?.name ?? 'Untitled',\n content: session.content,\n versionOf: docId,\n configId,\n autosave: true,\n }).catch(() => undefined)\n }, 2500)\n return () => clearTimeout(timer)\n }, [\n isLocal,\n session.content,\n session.hasChanges,\n session.isSaving,\n docId,\n title,\n current?.name,\n configId,\n autosaveVersion,\n ])\n\n useEffect(() => {\n if (!docId) return\n return registerDocumentTurnFlush(docId, async () => {\n if (session.hasChanges && !(await session.save()))\n throw new Error('Save the document before sending your message.')\n })\n }, [docId, session])\n\n const { redline } = session\n return useMemo<ArtifactSession>(\n () => ({\n ...session,\n docId,\n isLocal,\n isLoading,\n status,\n versions,\n labelOf: (id) => versionLabel(versions, id),\n view: {\n content: session.baseline,\n isEditing: session.isEditing,\n hasChanges: session.hasChanges || !!current?.autosave,\n isSaving: session.isSaving,\n canCompare: session.canCompare,\n comparing: !!session.compareId,\n compareId: session.compareId,\n showTrackChanges: session.showTrackChanges,\n versions,\n currentVersionId: current?.id ?? docId ?? '',\n onStartEdit: session.startEdit,\n onCancel: session.cancel,\n onSave: () => void session.save(),\n onCompare: () => session.compare(),\n onStopCompare: session.stopCompare,\n onToggleTrackChanges: () => session.setShowTrackChanges(!session.showTrackChanges),\n // A local draft has its text already: it is ready.\n status: isLocal ? 'ready' : status,\n onRetry: retry,\n },\n editorProps: {\n markdown: session.content,\n onChange: session.change,\n onNormalized: session.normalized,\n },\n redlineProps: {\n state: redline.state,\n onRevert: (regionId) => void session.revertRegion(regionId),\n },\n }),\n [\n session,\n redline,\n isLoading,\n status,\n retry,\n isLocal,\n versions,\n docId,\n current?.id,\n current?.autosave,\n ]\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -22,6 +22,7 @@ function toArtifactVersions(docId, entries, currentVersion) {
|
|
|
22
22
|
name: e.file_name ?? "",
|
|
23
23
|
createdAt: e.created_at ?? "",
|
|
24
24
|
number: e.n,
|
|
25
|
+
autosave: e.autosave,
|
|
25
26
|
reason: e.reason ?? "upload",
|
|
26
27
|
isCurrent: current != null ? e.n === current : !!e.is_current,
|
|
27
28
|
savedFromId: savedFromId(docId, e),
|
|
@@ -41,7 +42,7 @@ function findVersion(versions, ref) {
|
|
|
41
42
|
function versionLabel(versions, ref) {
|
|
42
43
|
if (!ref || versions.length <= 1) return null;
|
|
43
44
|
const version = findVersion(versions, ref);
|
|
44
|
-
return version ? `v${version.number}` : null;
|
|
45
|
+
return version ? `v${version.number}${version.autosave ? " \xB7 Autosaved" : ""}` : null;
|
|
45
46
|
}
|
|
46
47
|
function flattenChunks(chunks, options) {
|
|
47
48
|
const text = (chunks ?? []).map((c) => (c.content ?? "").trim()).join("\n\n");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/artifacts/versions.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Versions of a document. A document keeps ONE id for its whole life; its versions live inside it\n * (`current_version` + `versions`, numbered 1..n). A version is named by a version reference\n * `doc-\u2026@v{n}`; a plain document id means its current version. The workspace list carries every\n * document's `versions`, and `GET /v1/document/{id}/versions` (`arbi.documents.versions`) the same\n * list on its own.\n */\n\nimport { repairParsedStructure, repairParsedTitle, unescapeMarkdown } from '../ai/stored-markdown'\nimport { normalizeMarkdown } from './normalize-markdown'\n\n/** One version entry as the API returns it (a subset of `DocVersion`). */\nexport interface DocVersionEntry {\n n: number\n file_name?: string | null\n created_at?: string | null\n reason?: string | null\n /** The version an edit started from, when it was not the previous one. */\n made_from?: number | null\n is_current?: boolean | null\n /** The assistant message whose turn made this version, and its thread (none: a person's). */\n source_message_ext_id?: string | null\n conversation_ext_id?: string | null\n}\n\n/** The fields of a workspace document the version list needs (a subset of the API type). */\nexport interface VersionedDoc {\n external_id?: string | null\n current_version?: number | null\n versions?: readonly DocVersionEntry[] | null\n file_name?: string | null\n created_at?: string | null\n}\n\n/** One row of a version list. Matches `VersionEntry` of `@arbidocs/blocks/artifacts`. */\nexport interface ArtifactVersion {\n /** The version reference `doc-\u2026@v{n}`: what opening or comparing this version names. */\n id: string\n /** The document (the same for every version). */\n docId: string\n /** File name as stored. */\n name: string\n /** ISO timestamp. */\n createdAt: string\n /** 1-based version number within the document. */\n number: number\n /** upload | edit | signed | sync */\n reason: string\n isCurrent: boolean\n /** The version this one was saved from (an older one than the previous version when restored). */\n savedFromId?: string | null\n /** The thread (and its message) whose turn made this version; none for a person's. */\n thread?: { conversationId: string; messageId: string | null } | null\n}\n\nconst VERSION_REF = /^(doc-[A-Za-z0-9]+)@v([1-9][0-9]*)$/\n\n/** `doc-\u2026@v{n}`: a reference to version `n` of a document. */\nexport function versionRef(docId: string, version: number): string {\n return `${docId}@v${version}`\n}\n\n/**\n * Split a reference into the document and the version it names. A plain document id (or anything\n * else) names no version: the document's current one.\n */\nexport function parseVersionRef(ref: string | null | undefined): {\n docId: string\n version: number | null\n} {\n const match = VERSION_REF.exec(ref ?? '')\n if (!match) return { docId: ref ?? '', version: null }\n return { docId: match[1], version: Number(match[2]) }\n}\n\n/** The version a version was saved from: `made_from`, else the one before it. */\nexport function savedFromId(docId: string, entry: DocVersionEntry): string | null {\n if (entry.made_from) return versionRef(docId, entry.made_from)\n return entry.n > 1 ? versionRef(docId, entry.n - 1) : null\n}\n\n/** A document's version entries (oldest first) as plain version rows. */\nexport function toArtifactVersions(\n docId: string | null | undefined,\n entries: readonly DocVersionEntry[] | null | undefined,\n currentVersion?: number | null\n): ArtifactVersion[] {\n if (!docId) return []\n const current = currentVersion ?? entries?.find((e) => e.is_current)?.n ?? null\n return [...(entries ?? [])]\n .sort((a, b) => a.n - b.n)\n .map((e) => ({\n id: versionRef(docId, e.n),\n docId,\n name: e.file_name ?? '',\n createdAt: e.created_at ?? '',\n number: e.n,\n reason: e.reason ?? 'upload',\n isCurrent: current != null ? e.n === current : !!e.is_current,\n savedFromId: savedFromId(docId, e),\n thread: e.conversation_ext_id\n ? { conversationId: e.conversation_ext_id, messageId: e.source_message_ext_id ?? null }\n : null,\n }))\n}\n\n/** The versions of a listed document, oldest first (`[]` when the document is not given). */\nexport function documentVersions(doc: VersionedDoc | null | undefined): ArtifactVersion[] {\n if (!doc?.external_id) return []\n const entries = doc.versions?.length\n ? doc.versions\n : [{ n: doc.current_version ?? 1, file_name: doc.file_name, created_at: doc.created_at }]\n return toArtifactVersions(doc.external_id, entries, doc.current_version ?? null)\n}\n\n/** The row `ref` names: an exact version, or (a plain document id) the current version. */\nexport function findVersion(\n versions: readonly ArtifactVersion[],\n ref: string | null | undefined\n): ArtifactVersion | undefined {\n if (!ref) return undefined\n const { docId, version } = parseVersionRef(ref)\n return version == null\n ? versions.find((v) => v.docId === docId && v.isCurrent)\n : versions.find((v) => v.docId === docId && v.number === version)\n}\n\n/**\n * \"v2\": the version number `ref` names. `null` when there is nothing to tell apart (the document\n * has a single version, or it is unknown), so unversioned documents stay unlabelled.\n */\nexport function versionLabel(\n versions: readonly ArtifactVersion[],\n ref: string | null | undefined\n): string | null {\n if (!ref || versions.length <= 1) return null\n const version = findVersion(versions, ref)\n return version ? `v${version.number}` : null\n}\n\n/**\n * Join parsed chunks into one markdown text (blank line between chunks).\n *\n * `unescape: true` for an artifact stored from markdown: the backend's parse of a `.md` escapes\n * markdown punctuation, which `unescapeMarkdown` undoes (see its rule), and mangles its title\n * (`repairParsedTitle`, `repairParsedStructure`: doubled headings, `- ` stacked on list markers,\n * one-line tables), then normalised. Leave it off for the parse\n * of a PDF or Word file.\n */\nexport function flattenChunks(\n chunks: readonly { content?: string | null }[] | null | undefined,\n options?: { unescape?: boolean }\n): string {\n const text = (chunks ?? []).map((c) => (c.content ?? '').trim()).join('\\n\\n')\n if (!options?.unescape) return text\n // Canonical spelling last, so the text an editor starts from is what a save would persist.\n return normalizeMarkdown(\n repairParsedStructure(repairParsedTitle(unescapeMarkdown(text)))\n ).replace(/\\n$/, '')\n}\n\n/** The backend's pattern for a document id. Anything else (a streamed draft's placeholder) is local. */\nconst DOC_ID = /^doc/\n\n/** Is `id` a real, saved document id? Local drafts have placeholder ids and no backend document. */\nexport function isDocumentId(id: string | null | undefined): id is string {\n return !!id && DOC_ID.test(id)\n}\n"],
|
|
5
|
-
"mappings": "AAQA,SAAS,uBAAuB,mBAAmB,wBAAwB;AAC3E,SAAS,yBAAyB;
|
|
4
|
+
"sourcesContent": ["/**\n * Versions of a document. A document keeps ONE id for its whole life; its versions live inside it\n * (`current_version` + `versions`, numbered 1..n). A version is named by a version reference\n * `doc-\u2026@v{n}`; a plain document id means its current version. The workspace list carries every\n * document's `versions`, and `GET /v1/document/{id}/versions` (`arbi.documents.versions`) the same\n * list on its own.\n */\n\nimport { repairParsedStructure, repairParsedTitle, unescapeMarkdown } from '../ai/stored-markdown'\nimport { normalizeMarkdown } from './normalize-markdown'\n\n/** One version entry as the API returns it (a subset of `DocVersion`). */\nexport interface DocVersionEntry {\n autosave?: boolean\n n: number\n file_name?: string | null\n created_at?: string | null\n reason?: string | null\n /** The version an edit started from, when it was not the previous one. */\n made_from?: number | null\n is_current?: boolean | null\n /** The assistant message whose turn made this version, and its thread (none: a person's). */\n source_message_ext_id?: string | null\n conversation_ext_id?: string | null\n}\n\n/** The fields of a workspace document the version list needs (a subset of the API type). */\nexport interface VersionedDoc {\n external_id?: string | null\n current_version?: number | null\n versions?: readonly DocVersionEntry[] | null\n file_name?: string | null\n created_at?: string | null\n}\n\n/** One row of a version list. Matches `VersionEntry` of `@arbidocs/blocks/artifacts`. */\nexport interface ArtifactVersion {\n /** The version reference `doc-\u2026@v{n}`: what opening or comparing this version names. */\n id: string\n /** The document (the same for every version). */\n docId: string\n /** File name as stored. */\n name: string\n /** ISO timestamp. */\n createdAt: string\n /** 1-based version number within the document. */\n number: number\n /** upload | edit | signed | sync */\n reason: string\n isCurrent: boolean\n /** The version this one was saved from (an older one than the previous version when restored). */\n autosave?: boolean\n savedFromId?: string | null\n /** The thread (and its message) whose turn made this version; none for a person's. */\n thread?: { conversationId: string; messageId: string | null } | null\n}\n\nconst VERSION_REF = /^(doc-[A-Za-z0-9]+)@v([1-9][0-9]*)$/\n\n/** `doc-\u2026@v{n}`: a reference to version `n` of a document. */\nexport function versionRef(docId: string, version: number): string {\n return `${docId}@v${version}`\n}\n\n/**\n * Split a reference into the document and the version it names. A plain document id (or anything\n * else) names no version: the document's current one.\n */\nexport function parseVersionRef(ref: string | null | undefined): {\n docId: string\n version: number | null\n} {\n const match = VERSION_REF.exec(ref ?? '')\n if (!match) return { docId: ref ?? '', version: null }\n return { docId: match[1], version: Number(match[2]) }\n}\n\n/** The version a version was saved from: `made_from`, else the one before it. */\nexport function savedFromId(docId: string, entry: DocVersionEntry): string | null {\n if (entry.made_from) return versionRef(docId, entry.made_from)\n return entry.n > 1 ? versionRef(docId, entry.n - 1) : null\n}\n\n/** A document's version entries (oldest first) as plain version rows. */\nexport function toArtifactVersions(\n docId: string | null | undefined,\n entries: readonly DocVersionEntry[] | null | undefined,\n currentVersion?: number | null\n): ArtifactVersion[] {\n if (!docId) return []\n const current = currentVersion ?? entries?.find((e) => e.is_current)?.n ?? null\n return [...(entries ?? [])]\n .sort((a, b) => a.n - b.n)\n .map((e) => ({\n id: versionRef(docId, e.n),\n docId,\n name: e.file_name ?? '',\n createdAt: e.created_at ?? '',\n number: e.n,\n autosave: e.autosave,\n reason: e.reason ?? 'upload',\n isCurrent: current != null ? e.n === current : !!e.is_current,\n savedFromId: savedFromId(docId, e),\n thread: e.conversation_ext_id\n ? { conversationId: e.conversation_ext_id, messageId: e.source_message_ext_id ?? null }\n : null,\n }))\n}\n\n/** The versions of a listed document, oldest first (`[]` when the document is not given). */\nexport function documentVersions(doc: VersionedDoc | null | undefined): ArtifactVersion[] {\n if (!doc?.external_id) return []\n const entries = doc.versions?.length\n ? doc.versions\n : [{ n: doc.current_version ?? 1, file_name: doc.file_name, created_at: doc.created_at }]\n return toArtifactVersions(doc.external_id, entries, doc.current_version ?? null)\n}\n\n/** The row `ref` names: an exact version, or (a plain document id) the current version. */\nexport function findVersion(\n versions: readonly ArtifactVersion[],\n ref: string | null | undefined\n): ArtifactVersion | undefined {\n if (!ref) return undefined\n const { docId, version } = parseVersionRef(ref)\n return version == null\n ? versions.find((v) => v.docId === docId && v.isCurrent)\n : versions.find((v) => v.docId === docId && v.number === version)\n}\n\n/**\n * \"v2\": the version number `ref` names. `null` when there is nothing to tell apart (the document\n * has a single version, or it is unknown), so unversioned documents stay unlabelled.\n */\nexport function versionLabel(\n versions: readonly ArtifactVersion[],\n ref: string | null | undefined\n): string | null {\n if (!ref || versions.length <= 1) return null\n const version = findVersion(versions, ref)\n return version ? `v${version.number}${version.autosave ? ' \u00B7 Autosaved' : ''}` : null\n}\n\n/**\n * Join parsed chunks into one markdown text (blank line between chunks).\n *\n * `unescape: true` for an artifact stored from markdown: the backend's parse of a `.md` escapes\n * markdown punctuation, which `unescapeMarkdown` undoes (see its rule), and mangles its title\n * (`repairParsedTitle`, `repairParsedStructure`: doubled headings, `- ` stacked on list markers,\n * one-line tables), then normalised. Leave it off for the parse\n * of a PDF or Word file.\n */\nexport function flattenChunks(\n chunks: readonly { content?: string | null }[] | null | undefined,\n options?: { unescape?: boolean }\n): string {\n const text = (chunks ?? []).map((c) => (c.content ?? '').trim()).join('\\n\\n')\n if (!options?.unescape) return text\n // Canonical spelling last, so the text an editor starts from is what a save would persist.\n return normalizeMarkdown(\n repairParsedStructure(repairParsedTitle(unescapeMarkdown(text)))\n ).replace(/\\n$/, '')\n}\n\n/** The backend's pattern for a document id. Anything else (a streamed draft's placeholder) is local. */\nconst DOC_ID = /^doc/\n\n/** Is `id` a real, saved document id? Local drafts have placeholder ids and no backend document. */\nexport function isDocumentId(id: string | null | undefined): id is string {\n return !!id && DOC_ID.test(id)\n}\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,uBAAuB,mBAAmB,wBAAwB;AAC3E,SAAS,yBAAyB;AAgDlC,MAAM,cAAc;AAGb,SAAS,WAAW,OAAe,SAAyB;AACjE,SAAO,GAAG,KAAK,KAAK,OAAO;AAC7B;AAMO,SAAS,gBAAgB,KAG9B;AACA,QAAM,QAAQ,YAAY,KAAK,OAAO,EAAE;AACxC,MAAI,CAAC,MAAO,QAAO,EAAE,OAAO,OAAO,IAAI,SAAS,KAAK;AACrD,SAAO,EAAE,OAAO,MAAM,CAAC,GAAG,SAAS,OAAO,MAAM,CAAC,CAAC,EAAE;AACtD;AAGO,SAAS,YAAY,OAAe,OAAuC;AAChF,MAAI,MAAM,UAAW,QAAO,WAAW,OAAO,MAAM,SAAS;AAC7D,SAAO,MAAM,IAAI,IAAI,WAAW,OAAO,MAAM,IAAI,CAAC,IAAI;AACxD;AAGO,SAAS,mBACd,OACA,SACA,gBACmB;AACnB,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,QAAM,UAAU,kBAAkB,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,KAAK;AAC3E,SAAO,CAAC,GAAI,WAAW,CAAC,CAAE,EACvB,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,EACxB,IAAI,CAAC,OAAO;AAAA,IACX,IAAI,WAAW,OAAO,EAAE,CAAC;AAAA,IACzB;AAAA,IACA,MAAM,EAAE,aAAa;AAAA,IACrB,WAAW,EAAE,cAAc;AAAA,IAC3B,QAAQ,EAAE;AAAA,IACV,UAAU,EAAE;AAAA,IACZ,QAAQ,EAAE,UAAU;AAAA,IACpB,WAAW,WAAW,OAAO,EAAE,MAAM,UAAU,CAAC,CAAC,EAAE;AAAA,IACnD,aAAa,YAAY,OAAO,CAAC;AAAA,IACjC,QAAQ,EAAE,sBACN,EAAE,gBAAgB,EAAE,qBAAqB,WAAW,EAAE,yBAAyB,KAAK,IACpF;AAAA,EACN,EAAE;AACN;AAGO,SAAS,iBAAiB,KAAyD;AACxF,MAAI,CAAC,KAAK,YAAa,QAAO,CAAC;AAC/B,QAAM,UAAU,IAAI,UAAU,SAC1B,IAAI,WACJ,CAAC,EAAE,GAAG,IAAI,mBAAmB,GAAG,WAAW,IAAI,WAAW,YAAY,IAAI,WAAW,CAAC;AAC1F,SAAO,mBAAmB,IAAI,aAAa,SAAS,IAAI,mBAAmB,IAAI;AACjF;AAGO,SAAS,YACd,UACA,KAC6B;AAC7B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,EAAE,OAAO,QAAQ,IAAI,gBAAgB,GAAG;AAC9C,SAAO,WAAW,OACd,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,SAAS,EAAE,SAAS,IACrD,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,SAAS,EAAE,WAAW,OAAO;AACpE;AAMO,SAAS,aACd,UACA,KACe;AACf,MAAI,CAAC,OAAO,SAAS,UAAU,EAAG,QAAO;AACzC,QAAM,UAAU,YAAY,UAAU,GAAG;AACzC,SAAO,UAAU,IAAI,QAAQ,MAAM,GAAG,QAAQ,WAAW,oBAAiB,EAAE,KAAK;AACnF;AAWO,SAAS,cACd,QACA,SACQ;AACR,QAAM,QAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,IAAI,KAAK,CAAC,EAAE,KAAK,MAAM;AAC5E,MAAI,CAAC,SAAS,SAAU,QAAO;AAE/B,SAAO;AAAA,IACL,sBAAsB,kBAAkB,iBAAiB,IAAI,CAAC,CAAC;AAAA,EACjE,EAAE,QAAQ,OAAO,EAAE;AACrB;AAGA,MAAM,SAAS;AAGR,SAAS,aAAa,IAA6C;AACxE,SAAO,CAAC,CAAC,MAAM,OAAO,KAAK,EAAE;AAC/B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('../../chunk-
|
|
3
|
+
var chunkFOBB5U4Y_cjs = require('../../chunk-FOBB5U4Y.cjs');
|
|
4
|
+
require('../../chunk-H3XHBIZ6.cjs');
|
|
5
5
|
var chunkO32T7XRD_cjs = require('../../chunk-O32T7XRD.cjs');
|
|
6
6
|
var chunkIX4HOS5Z_cjs = require('../../chunk-IX4HOS5Z.cjs');
|
|
7
7
|
require('../../chunk-N73IMVFF.cjs');
|
|
8
|
-
require('../../chunk-
|
|
8
|
+
require('../../chunk-KULS4YWP.cjs');
|
|
9
9
|
require('../../chunk-VOFNX4P6.cjs');
|
|
10
10
|
require('../../chunk-EEIYXVM4.cjs');
|
|
11
11
|
require('../../chunk-5NOZDH3F.cjs');
|
|
@@ -123,8 +123,8 @@ function useCallTranscriber({
|
|
|
123
123
|
const arbi = chunkO32T7XRD_cjs.useArbi();
|
|
124
124
|
const queryClient = reactQuery.useQueryClient();
|
|
125
125
|
const profile = arbi.isLoggedIn ? arbi.user.profile() : null;
|
|
126
|
-
const myProfileName = profile &&
|
|
127
|
-
const deploymentAvailable =
|
|
126
|
+
const myProfileName = profile && chunkFOBB5U4Y_cjs.toProfileView(profile).fullName || null;
|
|
127
|
+
const deploymentAvailable = chunkFOBB5U4Y_cjs.useCallTranscriptionAvailable();
|
|
128
128
|
const available = deploymentAvailable && !!callKey;
|
|
129
129
|
const [state, setState] = react.useState(null);
|
|
130
130
|
const [creatorExtId, setCreatorExtId] = react.useState(null);
|
|
@@ -167,7 +167,7 @@ function useCallTranscriber({
|
|
|
167
167
|
};
|
|
168
168
|
}, [available, callExtId]);
|
|
169
169
|
react.useEffect(
|
|
170
|
-
() =>
|
|
170
|
+
() => chunkFOBB5U4Y_cjs.onCallEvent("call.transcription", (message) => {
|
|
171
171
|
if (message.call_ext_id === callExtId) applyState(message);
|
|
172
172
|
}),
|
|
173
173
|
[callExtId, applyState]
|
|
@@ -183,7 +183,7 @@ function useCallTranscriber({
|
|
|
183
183
|
const persist = react.useCallback(
|
|
184
184
|
(segment, speakerNeedsProxy) => {
|
|
185
185
|
if (!callKey || persisted.current.has(segment.segmentId)) return;
|
|
186
|
-
const role =
|
|
186
|
+
const role = chunkFOBB5U4Y_cjs.transcriptPersistRole({
|
|
187
187
|
myIdentity,
|
|
188
188
|
canPost: canPost && (segment.speakerIdentity !== myIdentity || selfPosts.current),
|
|
189
189
|
hasCallKey: true,
|
|
@@ -192,7 +192,7 @@ function useCallTranscriber({
|
|
|
192
192
|
});
|
|
193
193
|
if (!role) return;
|
|
194
194
|
persisted.current.add(segment.segmentId);
|
|
195
|
-
|
|
195
|
+
chunkFOBB5U4Y_cjs.persistTranscriptSegment(arbi, queryClient, callExtId, callKey, segment).catch(
|
|
196
196
|
(err) => {
|
|
197
197
|
persisted.current.delete(segment.segmentId);
|
|
198
198
|
if (role === "own" && err?.status === 403) {
|
|
@@ -212,7 +212,7 @@ function useCallTranscriber({
|
|
|
212
212
|
);
|
|
213
213
|
react.useEffect(() => {
|
|
214
214
|
const onData = (payload, participant, _kind, topic, _encryption, participantIdentity) => {
|
|
215
|
-
if (topic ===
|
|
215
|
+
if (topic === chunkFOBB5U4Y_cjs.CALL_TRANSCRIPTION_TOPIC) {
|
|
216
216
|
if (participant || participantIdentity) return;
|
|
217
217
|
try {
|
|
218
218
|
const next = browser.calls.transcriptionStateFrom(JSON.parse(new TextDecoder().decode(payload)));
|
|
@@ -221,10 +221,10 @@ function useCallTranscriber({
|
|
|
221
221
|
}
|
|
222
222
|
return;
|
|
223
223
|
}
|
|
224
|
-
if (topic !==
|
|
224
|
+
if (topic !== chunkFOBB5U4Y_cjs.CALL_CAPTION_TOPIC || !callKey) return;
|
|
225
225
|
const identity = participant?.identity ?? participantIdentity;
|
|
226
226
|
if (!identity || identity === myIdentity) return;
|
|
227
|
-
void
|
|
227
|
+
void chunkFOBB5U4Y_cjs.decodeCaptionPacket(payload, callKey).then((packet) => {
|
|
228
228
|
if (!packet || packet.identity !== identity) return;
|
|
229
229
|
const name = participant?.name || packet.name || identity;
|
|
230
230
|
const t = Math.min(packet.t, Date.now());
|
|
@@ -264,7 +264,7 @@ function useCallTranscriber({
|
|
|
264
264
|
})) {
|
|
265
265
|
return;
|
|
266
266
|
}
|
|
267
|
-
const segmentId =
|
|
267
|
+
const segmentId = chunkFOBB5U4Y_cjs.newSegmentId();
|
|
268
268
|
const startedAt = new Date(segment.startedAt).toISOString();
|
|
269
269
|
let text;
|
|
270
270
|
try {
|
|
@@ -298,7 +298,7 @@ function useCallTranscriber({
|
|
|
298
298
|
});
|
|
299
299
|
try {
|
|
300
300
|
await localParticipant.publishData(
|
|
301
|
-
await
|
|
301
|
+
await chunkFOBB5U4Y_cjs.encodeCaptionPacket(
|
|
302
302
|
{
|
|
303
303
|
v: 1,
|
|
304
304
|
segment_id: segmentId,
|
|
@@ -310,7 +310,7 @@ function useCallTranscriber({
|
|
|
310
310
|
},
|
|
311
311
|
callKey
|
|
312
312
|
),
|
|
313
|
-
{ reliable: true, topic:
|
|
313
|
+
{ reliable: true, topic: chunkFOBB5U4Y_cjs.CALL_CAPTION_TOPIC }
|
|
314
314
|
);
|
|
315
315
|
} catch (err) {
|
|
316
316
|
console.warn("[calls] could not broadcast a caption", err);
|
|
@@ -425,14 +425,14 @@ function useCallRecorder({
|
|
|
425
425
|
};
|
|
426
426
|
}, [callExtId]);
|
|
427
427
|
react.useEffect(
|
|
428
|
-
() =>
|
|
428
|
+
() => chunkFOBB5U4Y_cjs.onCallEvent("call.recording", (message) => {
|
|
429
429
|
if (message.call_ext_id === callExtId) applyState(message);
|
|
430
430
|
}),
|
|
431
431
|
[callExtId, applyState]
|
|
432
432
|
);
|
|
433
433
|
react.useEffect(() => {
|
|
434
434
|
const onData = (payload, participant, _kind, topic, _encryption, participantIdentity) => {
|
|
435
|
-
if (topic !==
|
|
435
|
+
if (topic !== chunkFOBB5U4Y_cjs.CALL_RECORDING_TOPIC) return;
|
|
436
436
|
if (participant || participantIdentity) return;
|
|
437
437
|
try {
|
|
438
438
|
const next = browser.calls.recordingStateFrom(JSON.parse(new TextDecoder().decode(payload)));
|
|
@@ -453,7 +453,7 @@ function useCallRecorder({
|
|
|
453
453
|
try {
|
|
454
454
|
const row = call ?? await arbi.calls.get(callExtId);
|
|
455
455
|
if (!row.workspace_locked && !row.chat_ext_id && callKey) {
|
|
456
|
-
const ref = await
|
|
456
|
+
const ref = await chunkFOBB5U4Y_cjs.ensureMeetingChat(arbi, queryClient, callExtId, callKey);
|
|
457
457
|
if (ref.kind === "chat") setCall({ ...row, chat_ext_id: ref.chatExtId });
|
|
458
458
|
}
|
|
459
459
|
const answer = await arbi.calls.recording.start(callExtId, mode);
|
|
@@ -555,7 +555,7 @@ function useTrackUploader(recording, callKey, kind, mimeType) {
|
|
|
555
555
|
const queryClient = reactQuery.useQueryClient();
|
|
556
556
|
return react.useMemo(() => {
|
|
557
557
|
if (!shouldCapture || !recordingExtId || !call) return null;
|
|
558
|
-
return
|
|
558
|
+
return chunkFOBB5U4Y_cjs.trackUploaderFor({
|
|
559
559
|
arbi,
|
|
560
560
|
callExtId: call.external_id,
|
|
561
561
|
recordingExtId,
|
|
@@ -563,7 +563,7 @@ function useTrackUploader(recording, callKey, kind, mimeType) {
|
|
|
563
563
|
mimeType,
|
|
564
564
|
recordingStartedAt: startedAt,
|
|
565
565
|
linkVersion,
|
|
566
|
-
conversationKey: () =>
|
|
566
|
+
conversationKey: () => chunkFOBB5U4Y_cjs.conversationKeyForCall(arbi, queryClient, call, { callKey }),
|
|
567
567
|
// The server stopped the recording at the quota (its state event says so too).
|
|
568
568
|
onStopped: () => markStopped()
|
|
569
569
|
});
|
|
@@ -612,10 +612,10 @@ function useInCallChat({
|
|
|
612
612
|
react.useEffect(() => {
|
|
613
613
|
if (!callKey) return;
|
|
614
614
|
const onData = (payload, participant, _kind, topic, _encryption, participantIdentity) => {
|
|
615
|
-
if (topic !==
|
|
615
|
+
if (topic !== chunkFOBB5U4Y_cjs.CALL_CHAT_TOPIC) return;
|
|
616
616
|
const identity = participant?.identity ?? participantIdentity;
|
|
617
617
|
if (!identity || identity === myIdentity) return;
|
|
618
|
-
void
|
|
618
|
+
void chunkFOBB5U4Y_cjs.decodeCallChatPacket(payload, callKey).then((packet) => {
|
|
619
619
|
if (!packet) return;
|
|
620
620
|
const key = `${identity}:${packet.id}`;
|
|
621
621
|
if (seen.current.has(key)) return;
|
|
@@ -645,7 +645,7 @@ function useInCallChat({
|
|
|
645
645
|
if (!open || !canRead || historyRequested.current) return;
|
|
646
646
|
historyRequested.current = true;
|
|
647
647
|
setHistoryLoading(true);
|
|
648
|
-
|
|
648
|
+
chunkFOBB5U4Y_cjs.loadMeetingHistory(arbi, queryClient, callExtId).then((result) => {
|
|
649
649
|
if (!result) return;
|
|
650
650
|
setMeetingChat(result.ref);
|
|
651
651
|
setHistory(result.messages);
|
|
@@ -656,13 +656,13 @@ function useInCallChat({
|
|
|
656
656
|
}, [open, canRead, callExtId]);
|
|
657
657
|
const send = react.useCallback(
|
|
658
658
|
async (raw) => {
|
|
659
|
-
const text = raw.trim().slice(0,
|
|
659
|
+
const text = raw.trim().slice(0, chunkFOBB5U4Y_cjs.CALL_CHAT_MAX_CHARS);
|
|
660
660
|
if (!text || !callKey || readOnly) return;
|
|
661
|
-
const id =
|
|
661
|
+
const id = chunkFOBB5U4Y_cjs.newPacketId();
|
|
662
662
|
const at = Date.now();
|
|
663
663
|
await localParticipant.publishData(
|
|
664
|
-
await
|
|
665
|
-
{ reliable: true, topic:
|
|
664
|
+
await chunkFOBB5U4Y_cjs.encodeCallChatPacket({ v: 1, id, text, at }, callKey),
|
|
665
|
+
{ reliable: true, topic: chunkFOBB5U4Y_cjs.CALL_CHAT_TOPIC }
|
|
666
666
|
);
|
|
667
667
|
setLive((prev) => [
|
|
668
668
|
...prev,
|
|
@@ -681,7 +681,7 @@ function useInCallChat({
|
|
|
681
681
|
(prev) => prev.map((l) => l.id === id && l.senderIdentity === myIdentity ? { ...l, status } : l)
|
|
682
682
|
);
|
|
683
683
|
try {
|
|
684
|
-
setMeetingChat(await
|
|
684
|
+
setMeetingChat(await chunkFOBB5U4Y_cjs.persistMeetingMessage(arbi, queryClient, callExtId, callKey, text));
|
|
685
685
|
mark("saved");
|
|
686
686
|
} catch (err) {
|
|
687
687
|
console.warn("[calls] could not keep the message in the meeting chat", err);
|
|
@@ -693,7 +693,7 @@ function useInCallChat({
|
|
|
693
693
|
const openMeeting = react.useCallback(async () => {
|
|
694
694
|
if (!callKey) return;
|
|
695
695
|
try {
|
|
696
|
-
const ref = meetingChat ?? await
|
|
696
|
+
const ref = meetingChat ?? await chunkFOBB5U4Y_cjs.ensureMeetingChat(arbi, queryClient, callExtId, callKey);
|
|
697
697
|
setMeetingChat(ref);
|
|
698
698
|
openMeetingChat?.(ref);
|
|
699
699
|
} catch (err) {
|
|
@@ -701,7 +701,7 @@ function useInCallChat({
|
|
|
701
701
|
}
|
|
702
702
|
}, [callExtId, callKey, meetingChat]);
|
|
703
703
|
const lines = react.useMemo(
|
|
704
|
-
() =>
|
|
704
|
+
() => chunkFOBB5U4Y_cjs.mergeChatLines(history, live, {
|
|
705
705
|
myIdentity,
|
|
706
706
|
nameOf: (identity) => identity === myIdentity ? localParticipant.name || "You" : room.remoteParticipants.get(identity)?.name || null
|
|
707
707
|
}),
|
|
@@ -760,7 +760,7 @@ function useWebinar({
|
|
|
760
760
|
const n = ++questionsRead.current;
|
|
761
761
|
try {
|
|
762
762
|
const rows = await webinarApi().questions.list(callExtId, linkVersion);
|
|
763
|
-
const opened = await Promise.all(rows.map((q) =>
|
|
763
|
+
const opened = await Promise.all(rows.map((q) => chunkFOBB5U4Y_cjs.openQuestion(q, callKey)));
|
|
764
764
|
if (n === questionsRead.current) setQuestions(opened);
|
|
765
765
|
} catch (err) {
|
|
766
766
|
console.warn("[calls] could not load the questions", err);
|
|
@@ -772,7 +772,7 @@ function useWebinar({
|
|
|
772
772
|
}, [refresh, refreshQuestions]);
|
|
773
773
|
react.useEffect(() => {
|
|
774
774
|
if (!enabled) return;
|
|
775
|
-
return
|
|
775
|
+
return chunkFOBB5U4Y_cjs.onCallEvent("call.webinar", (m) => {
|
|
776
776
|
if (m.call_ext_id !== callExtId) return;
|
|
777
777
|
setState((s) => s ? { ...s, live: m.live, chat: m.chat } : s);
|
|
778
778
|
});
|
|
@@ -781,8 +781,8 @@ function useWebinar({
|
|
|
781
781
|
react.useEffect(() => {
|
|
782
782
|
if (!enabled) return;
|
|
783
783
|
const onData = (payload, participant, _kind, topic, _encryption, participantIdentity) => {
|
|
784
|
-
if (topic !==
|
|
785
|
-
const packet =
|
|
784
|
+
if (topic !== chunkFOBB5U4Y_cjs.CALL_WEBINAR_TOPIC) return;
|
|
785
|
+
const packet = chunkFOBB5U4Y_cjs.parseWebinarPacket(payload, !!(participant || participantIdentity));
|
|
786
786
|
if (!packet) return;
|
|
787
787
|
switch (packet.type) {
|
|
788
788
|
case "state":
|
|
@@ -803,7 +803,7 @@ function useWebinar({
|
|
|
803
803
|
return;
|
|
804
804
|
case "demoted":
|
|
805
805
|
setStageCheck(false);
|
|
806
|
-
|
|
806
|
+
chunkFOBB5U4Y_cjs.markRejoining(callExtId);
|
|
807
807
|
if (fallback.current) clearTimeout(fallback.current);
|
|
808
808
|
fallback.current = setTimeout(() => void room.disconnect(), DEMOTE_REJOIN_FALLBACK_MS);
|
|
809
809
|
return;
|
|
@@ -830,12 +830,12 @@ function useWebinar({
|
|
|
830
830
|
[callExtId, linkVersion]
|
|
831
831
|
);
|
|
832
832
|
const stepDown = react.useCallback(async () => {
|
|
833
|
-
|
|
833
|
+
chunkFOBB5U4Y_cjs.markRejoining(callExtId);
|
|
834
834
|
setStageCheck(false);
|
|
835
835
|
try {
|
|
836
836
|
await webinarApi().demote(callExtId, myIdentity, linkVersion);
|
|
837
837
|
} catch (err) {
|
|
838
|
-
|
|
838
|
+
chunkFOBB5U4Y_cjs.markRejoining(callExtId, false);
|
|
839
839
|
failed("leave the stage")(err);
|
|
840
840
|
}
|
|
841
841
|
}, [callExtId, myIdentity, linkVersion]);
|
|
@@ -895,7 +895,7 @@ function useWebinar({
|
|
|
895
895
|
if (!callKey) throw new Error("This call has no key.");
|
|
896
896
|
await webinarApi().questions.ask(
|
|
897
897
|
callExtId,
|
|
898
|
-
await
|
|
898
|
+
await chunkFOBB5U4Y_cjs.sealQuestionText(text, callKey),
|
|
899
899
|
linkVersion
|
|
900
900
|
);
|
|
901
901
|
}, "ask the question"),
|
|
@@ -909,7 +909,7 @@ function useWebinar({
|
|
|
909
909
|
await webinarApi().questions.update(
|
|
910
910
|
callExtId,
|
|
911
911
|
id,
|
|
912
|
-
{ answer: await
|
|
912
|
+
{ answer: await chunkFOBB5U4Y_cjs.sealQuestionText(text, callKey) },
|
|
913
913
|
linkVersion
|
|
914
914
|
);
|
|
915
915
|
}, "answer the question"),
|
|
@@ -939,15 +939,15 @@ function useWebinar({
|
|
|
939
939
|
function useHostMuteNotice(callExtId) {
|
|
940
940
|
const room = componentsReact.useRoomContext();
|
|
941
941
|
react.useEffect(() => {
|
|
942
|
-
const notifyMuted =
|
|
942
|
+
const notifyMuted = chunkFOBB5U4Y_cjs.createMutedNotifier((text) => chunkIX4HOS5Z_cjs.notify.info(text));
|
|
943
943
|
const onData = (payload, participant, _kind, topic, _encryption, participantIdentity) => {
|
|
944
|
-
if (topic !==
|
|
944
|
+
if (topic !== chunkFOBB5U4Y_cjs.CALL_MODERATION_TOPIC) return;
|
|
945
945
|
if (participant || participantIdentity) return;
|
|
946
|
-
const notice =
|
|
946
|
+
const notice = chunkFOBB5U4Y_cjs.mutedNoticeFromPacket(payload);
|
|
947
947
|
if (notice) notifyMuted(notice);
|
|
948
948
|
};
|
|
949
949
|
room.on(livekitClient.RoomEvent.DataReceived, onData);
|
|
950
|
-
const offWs =
|
|
950
|
+
const offWs = chunkFOBB5U4Y_cjs.onCallEvent("call.muted", (message) => {
|
|
951
951
|
if (message.callExtId === callExtId) notifyMuted(message);
|
|
952
952
|
});
|
|
953
953
|
return () => {
|
package/dist/chat/turn-flush.js
CHANGED
|
@@ -5,9 +5,9 @@ function registerDocumentTurnFlush(documentId, flush) {
|
|
|
5
5
|
if (flushes.get(documentId) === flush) flushes.delete(documentId);
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
|
-
function flushDocumentForTurn(
|
|
9
|
-
const
|
|
10
|
-
return
|
|
8
|
+
function flushDocumentForTurn(_documentId) {
|
|
9
|
+
const pending = [...flushes.values()];
|
|
10
|
+
return pending.length ? Promise.all(pending.map((flush) => flush())).then(() => void 0) : null;
|
|
11
11
|
}
|
|
12
12
|
export {
|
|
13
13
|
flushDocumentForTurn,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/chat/turn-flush.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/** Mounted editors save their local changes before any chat turn reads the workspace. */\n\ntype Flush = () => Promise<void>\n\nconst flushes = new Map<string, Flush>()\n\n/** While mounted, `flush` saves `documentId`'s unsaved changes; returns the unregister. */\nexport function registerDocumentTurnFlush(documentId: string, flush: Flush): () => void {\n flushes.set(documentId, flush)\n return () => {\n if (flushes.get(documentId) === flush) flushes.delete(documentId)\n }\n}\n\n/** Save every mounted editor before a turn reads workspace documents. */\nexport function flushDocumentForTurn(_documentId: string | null | undefined): Promise<void> | null {\n const pending = [...flushes.values()]\n return pending.length ? Promise.all(pending.map((flush) => flush())).then(() => undefined) : null\n}\n"],
|
|
5
|
+
"mappings": "AAIA,MAAM,UAAU,oBAAI,IAAmB;AAGhC,SAAS,0BAA0B,YAAoB,OAA0B;AACtF,UAAQ,IAAI,YAAY,KAAK;AAC7B,SAAO,MAAM;AACX,QAAI,QAAQ,IAAI,UAAU,MAAM,MAAO,SAAQ,OAAO,UAAU;AAAA,EAClE;AACF;AAGO,SAAS,qBAAqB,aAA8D;AACjG,QAAM,UAAU,CAAC,GAAG,QAAQ,OAAO,CAAC;AACpC,SAAO,QAAQ,SAAS,QAAQ,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,CAAC,CAAC,EAAE,KAAK,MAAM,MAAS,IAAI;AAC/F;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|