@opengeni/react 7.4.0-canary.36855269964001 → 7.4.0
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/README.md +3 -48
- package/dist/artifacts-document.js +2 -3
- package/dist/artifacts-presentation.js +2 -3
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.d.ts +0 -1
- package/dist/artifacts.js +1402 -505
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-R6KK5VKR.js → browser-viewer-2E6R2Y4W.js} +4 -4
- package/dist/{chunk-WUISZ3WU.js → chunk-3E2AITTZ.js} +28 -32
- package/dist/{chunk-WUISZ3WU.js.map → chunk-3E2AITTZ.js.map} +1 -1
- package/dist/{chunk-PWUILT2U.js → chunk-5AZSK6MV.js} +2 -2
- package/dist/{chunk-Y2PRMYLC.js → chunk-DUEBTL3N.js} +3 -4
- package/dist/{chunk-Y2PRMYLC.js.map → chunk-DUEBTL3N.js.map} +1 -1
- package/dist/{chunk-7UAMYXJY.js → chunk-KNDIXZN6.js} +18 -25
- package/dist/{chunk-7UAMYXJY.js.map → chunk-KNDIXZN6.js.map} +1 -1
- package/dist/{chunk-P4G6K2E2.js → chunk-L4OSXQ6H.js} +884 -1119
- package/dist/chunk-L4OSXQ6H.js.map +1 -0
- package/dist/{chunk-LLIH7Q4B.js → chunk-S5JQ4R22.js} +19 -47
- package/dist/chunk-S5JQ4R22.js.map +1 -0
- package/dist/{chunk-A4UJB2MM.js → chunk-SEEVCT36.js} +2 -2
- package/dist/{chunk-UXJCQ5GH.js → chunk-UDD6DLLT.js} +1 -6
- package/dist/chunk-UDD6DLLT.js.map +1 -0
- package/dist/chunk-UGMKPUEM.js +299 -0
- package/dist/chunk-UGMKPUEM.js.map +1 -0
- package/dist/{chunk-ZCZHKJKZ.js → chunk-USXVQACU.js} +3 -3
- package/dist/{chunk-VABBF52G.js → chunk-V2ABIVYJ.js} +6 -70
- package/dist/chunk-V2ABIVYJ.js.map +1 -0
- package/dist/{chunk-TVO6GXR6.js → chunk-X4HIN6TV.js} +162 -71
- package/dist/chunk-X4HIN6TV.js.map +1 -0
- package/dist/{chunk-IIDEIBMC.js → chunk-XEJEOAOV.js} +38 -49
- package/dist/chunk-XEJEOAOV.js.map +1 -0
- package/dist/{chunk-ZKLCPBWN.js → chunk-Y3QTZN2F.js} +5 -5
- package/dist/{chunk-73BUNGLF.js → chunk-YZ3YYMOM.js} +1 -1
- package/dist/components/artifacts/editable-artifact-ui.d.ts +1 -2
- package/dist/components/artifacts/index.d.ts +0 -7
- package/dist/components/browser-select-control.d.ts +1 -3
- package/dist/components/message-timeline.d.ts +1 -18
- package/dist/components/open-geni-links.d.ts +0 -16
- package/dist/components/session-conversation.d.ts +1 -20
- package/dist/composer.js +3 -3
- package/dist/{computer-viewer-DJHPSP2J.js → computer-viewer-WMJE6OA3.js} +3 -3
- package/dist/connect.js +6 -7
- package/dist/connect.js.map +1 -1
- package/dist/hooks/latest-question.d.ts +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.js +28 -39
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +4 -4
- package/dist/{latest-question-KGCDK3XX.js → latest-question-QB4EUCW5.js} +3 -3
- package/dist/lib/format.d.ts +0 -3
- package/dist/machines.js +2 -2
- package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-PFUEZ2U4.js} +5 -37
- package/dist/markdown-table-layout-PFUEZ2U4.js.map +1 -0
- package/dist/{sandbox-files-4PMXDSDV.js → sandbox-files-PXPIZAFG.js} +2 -2
- package/dist/session-ui.d.ts +2 -5
- package/dist/session-ui.js +13 -23
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +3 -3
- package/dist/sites.js +289 -4
- package/dist/sites.js.map +1 -1
- package/dist/timeline/types.d.ts +1 -7
- package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-ODLVF4OI.js} +1 -6
- package/dist/timeline-annotation-selection-ODLVF4OI.js.map +1 -0
- package/package.json +3 -7
- package/src/artifacts.ts +0 -37
- package/src/components/artifacts/editable-artifact-ui.tsx +1 -3
- package/src/components/artifacts/editable-artifact-workbench.tsx +2 -5
- package/src/components/artifacts/editable-document.tsx +36 -34
- package/src/components/artifacts/editable-presentation.tsx +16 -21
- package/src/components/artifacts/index.ts +0 -41
- package/src/components/browser-select-control.tsx +1 -4
- package/src/components/browser-viewer.tsx +22 -81
- package/src/components/computer-viewer.tsx +23 -51
- package/src/components/markdown-table-layout.ts +5 -49
- package/src/components/markdown.tsx +2 -5
- package/src/components/message-timeline.tsx +434 -515
- package/src/components/open-geni-links.tsx +0 -24
- package/src/components/session-conversation.tsx +20 -131
- package/src/components/timeline-annotation-selection.tsx +0 -7
- package/src/hooks/use-available-models.ts +1 -1
- package/src/index.ts +0 -9
- package/src/lib/format.ts +0 -13
- package/src/session-ui.ts +0 -9
- package/src/timeline/projection.ts +4 -78
- package/src/timeline/types.ts +0 -7
- package/styles/compiled.css +0 -325
- package/dist/chat-interactive-block-VAGABN7J.js +0 -11
- package/dist/chunk-5CVXZA3Y.js +0 -292
- package/dist/chunk-5CVXZA3Y.js.map +0 -1
- package/dist/chunk-B3V6TG77.js +0 -41
- package/dist/chunk-B3V6TG77.js.map +0 -1
- package/dist/chunk-IIDEIBMC.js.map +0 -1
- package/dist/chunk-LLIH7Q4B.js.map +0 -1
- package/dist/chunk-P4G6K2E2.js.map +0 -1
- package/dist/chunk-RAIQYITC.js +0 -1772
- package/dist/chunk-RAIQYITC.js.map +0 -1
- package/dist/chunk-SCOTPE2I.js +0 -589
- package/dist/chunk-SCOTPE2I.js.map +0 -1
- package/dist/chunk-TVO6GXR6.js.map +0 -1
- package/dist/chunk-UXJCQ5GH.js.map +0 -1
- package/dist/chunk-VABBF52G.js.map +0 -1
- package/dist/chunk-WEHMPOXK.js +0 -39
- package/dist/chunk-WEHMPOXK.js.map +0 -1
- package/dist/chunk-XCHWDJ22.js +0 -107
- package/dist/chunk-XCHWDJ22.js.map +0 -1
- package/dist/components/artifacts/artifact-chrome.d.ts +0 -120
- package/dist/components/artifacts/artifact-sandbox.d.ts +0 -27
- package/dist/components/artifacts/chat-interactive-block.d.ts +0 -29
- package/dist/components/artifacts/deferred-chat-media.d.ts +0 -15
- package/dist/components/artifacts/editable-artifact-view.d.ts +0 -46
- package/dist/components/artifacts/latest-task-runner.d.ts +0 -15
- package/dist/components/artifacts/session-artifact-viewer.d.ts +0 -39
- package/dist/components/artifacts/site-view.d.ts +0 -23
- package/dist/components/timeline-pill-geometry.d.ts +0 -6
- package/dist/components/timeline-pill-placement.d.ts +0 -18
- package/dist/components/usage-limit-notice.d.ts +0 -49
- package/dist/components/usage-member-list.d.ts +0 -91
- package/dist/components/usage-meter.d.ts +0 -73
- package/dist/hooks/use-usage.d.ts +0 -40
- package/dist/markdown-table-layout-QXSLK4BY.js.map +0 -1
- package/dist/sandbox-files-4PMXDSDV.js.map +0 -1
- package/dist/timeline/allowance-exhausted-row.d.ts +0 -24
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +0 -1
- package/dist/timeline-pill-geometry-2G6EK47V.js +0 -73
- package/dist/timeline-pill-geometry-2G6EK47V.js.map +0 -1
- package/dist/usage/allowance-copy.d.ts +0 -25
- package/dist/usage/summary.d.ts +0 -35
- package/dist/usage.d.ts +0 -13
- package/dist/usage.js +0 -1073
- package/dist/usage.js.map +0 -1
- package/src/components/artifacts/artifact-chrome.tsx +0 -413
- package/src/components/artifacts/artifact-sandbox.tsx +0 -185
- package/src/components/artifacts/chat-interactive-block.tsx +0 -260
- package/src/components/artifacts/deferred-chat-media.tsx +0 -67
- package/src/components/artifacts/editable-artifact-view.tsx +0 -187
- package/src/components/artifacts/latest-task-runner.ts +0 -49
- package/src/components/artifacts/session-artifact-viewer.tsx +0 -376
- package/src/components/artifacts/site-view.tsx +0 -116
- package/src/components/timeline-pill-geometry.ts +0 -112
- package/src/components/timeline-pill-placement.ts +0 -81
- package/src/components/usage-limit-notice.tsx +0 -179
- package/src/components/usage-member-list.tsx +0 -792
- package/src/components/usage-meter.tsx +0 -382
- package/src/hooks/use-usage.ts +0 -100
- package/src/timeline/allowance-exhausted-row.tsx +0 -86
- package/src/usage/allowance-copy.ts +0 -64
- package/src/usage/summary.ts +0 -93
- package/src/usage.ts +0 -50
- /package/dist/{browser-viewer-R6KK5VKR.js.map → browser-viewer-2E6R2Y4W.js.map} +0 -0
- /package/dist/{chunk-PWUILT2U.js.map → chunk-5AZSK6MV.js.map} +0 -0
- /package/dist/{chunk-A4UJB2MM.js.map → chunk-SEEVCT36.js.map} +0 -0
- /package/dist/{chunk-ZCZHKJKZ.js.map → chunk-USXVQACU.js.map} +0 -0
- /package/dist/{chunk-ZKLCPBWN.js.map → chunk-Y3QTZN2F.js.map} +0 -0
- /package/dist/{chunk-73BUNGLF.js.map → chunk-YZ3YYMOM.js.map} +0 -0
- /package/dist/{chat-interactive-block-VAGABN7J.js.map → computer-viewer-WMJE6OA3.js.map} +0 -0
- /package/dist/{latest-question-KGCDK3XX.js.map → latest-question-QB4EUCW5.js.map} +0 -0
- /package/dist/{computer-viewer-DJHPSP2J.js.map → sandbox-files-PXPIZAFG.js.map} +0 -0
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useRef } from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Runs one task at a time; a task scheduled while another runs replaces any
|
|
5
|
-
* task still waiting, so only the newest request starts next. Editors use it
|
|
6
|
-
* for projection loads: replaying a long history emits one revision per
|
|
7
|
-
* committed transaction, and composing a projection for each of them at once
|
|
8
|
-
* would overrun the artifact Worker's bounded request queue.
|
|
9
|
-
*/
|
|
10
|
-
export function createLatestTaskRunner(): (task: () => Promise<void>) => void {
|
|
11
|
-
let running = false;
|
|
12
|
-
let waiting: (() => Promise<void>) | null = null;
|
|
13
|
-
const drain = async () => {
|
|
14
|
-
running = true;
|
|
15
|
-
try {
|
|
16
|
-
while (waiting) {
|
|
17
|
-
const task = waiting;
|
|
18
|
-
waiting = null;
|
|
19
|
-
try {
|
|
20
|
-
await task();
|
|
21
|
-
} catch {
|
|
22
|
-
// Tasks settle their own errors; one failure must not stall the next.
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
} finally {
|
|
26
|
-
running = false;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
return (task) => {
|
|
30
|
-
waiting = task;
|
|
31
|
-
if (!running) void drain();
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* One runner per artifact session. A composition still pending on a replaced
|
|
37
|
-
* session (for example one whose Worker stopped answering) must never hold up
|
|
38
|
-
* the next session's first load, so the runner is scoped to the session object
|
|
39
|
-
* rather than to the component.
|
|
40
|
-
*/
|
|
41
|
-
export function useLatestTaskRunner(session: object): (task: () => Promise<void>) => void {
|
|
42
|
-
const runner = useRef<{ session: object; schedule: (task: () => Promise<void>) => void } | null>(
|
|
43
|
-
null,
|
|
44
|
-
);
|
|
45
|
-
if (runner.current?.session !== session) {
|
|
46
|
-
runner.current = { session, schedule: createLatestTaskRunner() };
|
|
47
|
-
}
|
|
48
|
-
return runner.current.schedule;
|
|
49
|
-
}
|
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
import { SESSION_SCOPE_HEADER, type ClientConfig, type OpenGeniClient } from "@opengeni/sdk";
|
|
2
|
-
import { createEditableArtifactReplicaId } from "@opengeni/sdk/editable-artifacts";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/cn";
|
|
6
|
-
import { useOpenGeni, type ClientOverride } from "../../session-context";
|
|
7
|
-
import {
|
|
8
|
-
ArtifactLabelsProvider,
|
|
9
|
-
ArtifactLoading,
|
|
10
|
-
ArtifactProblem,
|
|
11
|
-
ArtifactViewerHeader,
|
|
12
|
-
artifactLoadErrorView,
|
|
13
|
-
useArtifactLabels,
|
|
14
|
-
type ArtifactKind,
|
|
15
|
-
type ArtifactLabels,
|
|
16
|
-
} from "./artifact-chrome";
|
|
17
|
-
import { EditableArtifactView, type EditableArtifactRuntimes } from "./editable-artifact-view";
|
|
18
|
-
import type { SiteToolBridgeFactory } from "./chat-interactive-block";
|
|
19
|
-
import { SiteView } from "./site-view";
|
|
20
|
-
|
|
21
|
-
/** An artifact opened from a session: the targets agent links and Site previews name. */
|
|
22
|
-
export type SessionArtifactTarget = Readonly<
|
|
23
|
-
| { kind: "editable-artifact"; artifactId: string; title?: string | undefined }
|
|
24
|
-
| { kind: "site"; artifactId: string; title?: string | undefined }
|
|
25
|
-
>;
|
|
26
|
-
|
|
27
|
-
/** Every string the viewer shows, including its Site frame and states. */
|
|
28
|
-
export type SessionArtifactViewerLabels = Partial<ArtifactLabels>;
|
|
29
|
-
|
|
30
|
-
export type SessionArtifactViewerProps = ClientOverride &
|
|
31
|
-
Readonly<{
|
|
32
|
-
/** The conversation the artifact belongs to; the proxy only serves its artifacts. */
|
|
33
|
-
sessionId: string;
|
|
34
|
-
target: SessionArtifactTarget;
|
|
35
|
-
onClose: () => void;
|
|
36
|
-
/** Show a Back control, for example to return to the conversation on phones. */
|
|
37
|
-
onBack?: (() => void) | undefined;
|
|
38
|
-
/** Browser kernels and Worker for documents, spreadsheets, and presentations. */
|
|
39
|
-
editableRuntimes?: EditableArtifactRuntimes | undefined;
|
|
40
|
-
theme?: "light" | "dark" | undefined;
|
|
41
|
-
/** Host-owned Site API access; omit it and Sites render without workspace tools. */
|
|
42
|
-
siteToolBridge?: SiteToolBridgeFactory | undefined;
|
|
43
|
-
labels?: SessionArtifactViewerLabels | undefined;
|
|
44
|
-
className?: string | undefined;
|
|
45
|
-
}>;
|
|
46
|
-
|
|
47
|
-
type ViewerClient = Pick<
|
|
48
|
-
OpenGeniClient,
|
|
49
|
-
| "withHeaders"
|
|
50
|
-
| "apiUrl"
|
|
51
|
-
| "fetchApi"
|
|
52
|
-
| "getClientConfig"
|
|
53
|
-
| "getEditableArtifact"
|
|
54
|
-
| "getWorkspaceArtifact"
|
|
55
|
-
| "getWorkspaceArtifactHtml"
|
|
56
|
-
>;
|
|
57
|
-
|
|
58
|
-
const viewerClientKeys = new WeakMap<ViewerClient, string>();
|
|
59
|
-
|
|
60
|
-
function viewerClientKey(client: ViewerClient): string {
|
|
61
|
-
let key = viewerClientKeys.get(client);
|
|
62
|
-
if (!key) {
|
|
63
|
-
key = crypto.randomUUID();
|
|
64
|
-
viewerClientKeys.set(client, key);
|
|
65
|
-
}
|
|
66
|
-
return key;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A host-mountable viewer for one editable artifact or Site from a session,
|
|
71
|
-
* with its own header (title, Back, Close). Mount it in any sized container
|
|
72
|
-
* (a side panel, a sheet); it fills it. Data flows through the provider's
|
|
73
|
-
* client and `createSessionProxyHandler({ artifacts: true })`, scoped to
|
|
74
|
-
* `sessionId`.
|
|
75
|
-
*/
|
|
76
|
-
export function SessionArtifactViewer({ labels, ...props }: SessionArtifactViewerProps) {
|
|
77
|
-
return (
|
|
78
|
-
<ArtifactLabelsProvider labels={labels}>
|
|
79
|
-
<Viewer {...props} />
|
|
80
|
-
</ArtifactLabelsProvider>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function Viewer({
|
|
85
|
-
client: clientOverride,
|
|
86
|
-
workspaceId: workspaceOverride,
|
|
87
|
-
sessionId,
|
|
88
|
-
target,
|
|
89
|
-
onClose,
|
|
90
|
-
onBack,
|
|
91
|
-
editableRuntimes,
|
|
92
|
-
theme,
|
|
93
|
-
siteToolBridge,
|
|
94
|
-
className,
|
|
95
|
-
}: Omit<SessionArtifactViewerProps, "labels">) {
|
|
96
|
-
const labels = useArtifactLabels();
|
|
97
|
-
const context = useOpenGeni({ client: clientOverride, workspaceId: workspaceOverride });
|
|
98
|
-
const base = context.client as unknown as ViewerClient;
|
|
99
|
-
const workspaceId = context.workspaceId;
|
|
100
|
-
const client = useMemo(
|
|
101
|
-
() => base.withHeaders({ [SESSION_SCOPE_HEADER]: sessionId }),
|
|
102
|
-
[base, sessionId],
|
|
103
|
-
);
|
|
104
|
-
const [loaded, setLoaded] = useState<{
|
|
105
|
-
key: string;
|
|
106
|
-
client: ViewerClient;
|
|
107
|
-
workspaceId: string;
|
|
108
|
-
title: string;
|
|
109
|
-
kind: ArtifactKind | null;
|
|
110
|
-
} | null>(null);
|
|
111
|
-
const targetKey = `${target.kind}:${target.artifactId}`;
|
|
112
|
-
const current =
|
|
113
|
-
loaded?.key === targetKey && loaded.client === client && loaded.workspaceId === workspaceId
|
|
114
|
-
? loaded
|
|
115
|
-
: null;
|
|
116
|
-
const kind: ArtifactKind | null = target.kind === "site" ? "site" : (current?.kind ?? null);
|
|
117
|
-
const [unavailable, setUnavailable] = useState<{
|
|
118
|
-
key: string;
|
|
119
|
-
client: ViewerClient;
|
|
120
|
-
workspaceId: string;
|
|
121
|
-
} | null>(null);
|
|
122
|
-
const markUnavailable = useCallback(
|
|
123
|
-
() => setUnavailable({ key: targetKey, client, workspaceId }),
|
|
124
|
-
[client, targetKey, workspaceId],
|
|
125
|
-
);
|
|
126
|
-
const title =
|
|
127
|
-
current?.title ??
|
|
128
|
-
target.title ??
|
|
129
|
-
(unavailable?.key === targetKey &&
|
|
130
|
-
unavailable.client === client &&
|
|
131
|
-
unavailable.workspaceId === workspaceId
|
|
132
|
-
? labels.artifact
|
|
133
|
-
: labels.opening);
|
|
134
|
-
return (
|
|
135
|
-
<section
|
|
136
|
-
aria-label={title}
|
|
137
|
-
data-og-artifact-viewer={target.kind}
|
|
138
|
-
className={cn("og-root flex h-full min-h-0 w-full min-w-0 flex-col bg-bg text-fg", className)}
|
|
139
|
-
>
|
|
140
|
-
<ArtifactViewerHeader
|
|
141
|
-
kind={kind}
|
|
142
|
-
title={title}
|
|
143
|
-
onBack={onBack}
|
|
144
|
-
backLabel={labels.back}
|
|
145
|
-
onClose={onClose}
|
|
146
|
-
closeLabel={labels.close}
|
|
147
|
-
/>
|
|
148
|
-
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
|
149
|
-
{target.kind === "site" ? (
|
|
150
|
-
<SiteView
|
|
151
|
-
key={targetKey}
|
|
152
|
-
client={client}
|
|
153
|
-
workspaceId={workspaceId}
|
|
154
|
-
siteId={target.artifactId}
|
|
155
|
-
theme={theme}
|
|
156
|
-
toolBridge={siteToolBridge}
|
|
157
|
-
showTitle={false}
|
|
158
|
-
onTitle={(next) =>
|
|
159
|
-
setLoaded({ key: targetKey, client, workspaceId, title: next, kind: "site" })
|
|
160
|
-
}
|
|
161
|
-
/>
|
|
162
|
-
) : (
|
|
163
|
-
<EditableBody
|
|
164
|
-
key={targetKey}
|
|
165
|
-
client={client}
|
|
166
|
-
workspaceId={workspaceId}
|
|
167
|
-
sessionId={sessionId}
|
|
168
|
-
artifactId={target.artifactId}
|
|
169
|
-
runtimes={editableRuntimes}
|
|
170
|
-
onOpened={(next, modality) =>
|
|
171
|
-
setLoaded({ key: targetKey, client, workspaceId, title: next, kind: modality })
|
|
172
|
-
}
|
|
173
|
-
onUnavailable={markUnavailable}
|
|
174
|
-
/>
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
</section>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function EditableBody({
|
|
182
|
-
client,
|
|
183
|
-
workspaceId,
|
|
184
|
-
sessionId,
|
|
185
|
-
artifactId,
|
|
186
|
-
runtimes,
|
|
187
|
-
onOpened,
|
|
188
|
-
onUnavailable,
|
|
189
|
-
}: {
|
|
190
|
-
client: ViewerClient;
|
|
191
|
-
workspaceId: string;
|
|
192
|
-
sessionId: string;
|
|
193
|
-
artifactId: string;
|
|
194
|
-
runtimes: EditableArtifactRuntimes | undefined;
|
|
195
|
-
onOpened: (title: string, modality: ArtifactKind) => void;
|
|
196
|
-
onUnavailable: () => void;
|
|
197
|
-
}) {
|
|
198
|
-
const labels = useArtifactLabels();
|
|
199
|
-
const clientKey = viewerClientKey(client);
|
|
200
|
-
const [retryEpoch, setRetryEpoch] = useState(0);
|
|
201
|
-
const [configState, setConfigState] = useState<{
|
|
202
|
-
client: ViewerClient;
|
|
203
|
-
workspaceId: string;
|
|
204
|
-
sessionId: string;
|
|
205
|
-
result:
|
|
206
|
-
| { kind: "ready"; config: NonNullable<ClientConfig["artifacts"]> }
|
|
207
|
-
| { kind: "unavailable" }
|
|
208
|
-
| { kind: "error"; error: unknown };
|
|
209
|
-
} | null>(null);
|
|
210
|
-
const current =
|
|
211
|
-
configState?.client === client &&
|
|
212
|
-
configState.workspaceId === workspaceId &&
|
|
213
|
-
configState.sessionId === sessionId
|
|
214
|
-
? configState.result
|
|
215
|
-
: null;
|
|
216
|
-
const config = current?.kind === "ready" ? current.config : null;
|
|
217
|
-
const scopeRef = useRef({ client, workspaceId, sessionId });
|
|
218
|
-
scopeRef.current = { client, workspaceId, sessionId };
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
let live = true;
|
|
221
|
-
setConfigState(null);
|
|
222
|
-
client.getClientConfig().then(
|
|
223
|
-
(value) =>
|
|
224
|
-
live &&
|
|
225
|
-
setConfigState({
|
|
226
|
-
client,
|
|
227
|
-
workspaceId,
|
|
228
|
-
sessionId,
|
|
229
|
-
result: value.artifacts
|
|
230
|
-
? { kind: "ready", config: value.artifacts }
|
|
231
|
-
: { kind: "unavailable" },
|
|
232
|
-
}),
|
|
233
|
-
(error: unknown) =>
|
|
234
|
-
live &&
|
|
235
|
-
setConfigState({ client, workspaceId, sessionId, result: { kind: "error", error } }),
|
|
236
|
-
);
|
|
237
|
-
return () => {
|
|
238
|
-
live = false;
|
|
239
|
-
};
|
|
240
|
-
}, [client, retryEpoch, sessionId, workspaceId]);
|
|
241
|
-
const baseUrl = useMemo(
|
|
242
|
-
() => new URL(client.apiUrl("/"), globalThis.location?.href ?? "http://localhost/"),
|
|
243
|
-
[client],
|
|
244
|
-
);
|
|
245
|
-
const transport = useMemo(() => {
|
|
246
|
-
if (!config) return undefined;
|
|
247
|
-
const socket = new URL(config.editableLiveUrl);
|
|
248
|
-
let scope = new AbortController();
|
|
249
|
-
const checkCurrent = (signal: AbortSignal) => {
|
|
250
|
-
signal.throwIfAborted();
|
|
251
|
-
if (
|
|
252
|
-
scopeRef.current.client !== client ||
|
|
253
|
-
scopeRef.current.workspaceId !== workspaceId ||
|
|
254
|
-
scopeRef.current.sessionId !== sessionId
|
|
255
|
-
) {
|
|
256
|
-
throw new DOMException("Artifact viewer scope changed", "AbortError");
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
return {
|
|
260
|
-
activate: () => {
|
|
261
|
-
if (scope.signal.aborted) scope = new AbortController();
|
|
262
|
-
},
|
|
263
|
-
abort: () => scope.abort(),
|
|
264
|
-
fetch: (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
265
|
-
const signal = AbortSignal.any([scope.signal, ...(init?.signal ? [init.signal] : [])]);
|
|
266
|
-
checkCurrent(signal);
|
|
267
|
-
const url = new URL(input instanceof Request ? input.url : input, baseUrl);
|
|
268
|
-
if (
|
|
269
|
-
init?.method === "POST" &&
|
|
270
|
-
url.pathname ===
|
|
271
|
-
new URL(
|
|
272
|
-
`v1/workspaces/${encodeURIComponent(workspaceId)}/editable-artifacts/${encodeURIComponent(artifactId)}/live-ticket`,
|
|
273
|
-
baseUrl,
|
|
274
|
-
).pathname
|
|
275
|
-
) {
|
|
276
|
-
// A reconnect must revalidate the current host principal before
|
|
277
|
-
// minting a ticket for the editor's retained cache partition.
|
|
278
|
-
const next = (await client.getClientConfig({ signal })).artifacts;
|
|
279
|
-
checkCurrent(signal);
|
|
280
|
-
if (!next || artifactCapabilityKey(next) !== artifactCapabilityKey(config)) {
|
|
281
|
-
setConfigState({
|
|
282
|
-
client,
|
|
283
|
-
workspaceId,
|
|
284
|
-
sessionId,
|
|
285
|
-
result: next ? { kind: "ready", config: next } : { kind: "unavailable" },
|
|
286
|
-
});
|
|
287
|
-
scope.abort();
|
|
288
|
-
signal.throwIfAborted();
|
|
289
|
-
}
|
|
290
|
-
const { replicaId } = (await new Request(input, init).json()) as {
|
|
291
|
-
replicaId?: unknown;
|
|
292
|
-
};
|
|
293
|
-
if (typeof replicaId !== "string") throw new TypeError("Artifact replica is required");
|
|
294
|
-
await client.getEditableArtifact(workspaceId, artifactId, { replicaId, signal });
|
|
295
|
-
checkCurrent(signal);
|
|
296
|
-
}
|
|
297
|
-
return client.fetchApi(url, { ...init, signal });
|
|
298
|
-
}) as typeof fetch,
|
|
299
|
-
webSocketUrl: socket,
|
|
300
|
-
...(isLoopback(baseUrl) && isLoopback(socket)
|
|
301
|
-
? { allowInsecureDevelopmentTransport: true }
|
|
302
|
-
: {}),
|
|
303
|
-
};
|
|
304
|
-
}, [artifactId, baseUrl, client, config, sessionId, workspaceId]);
|
|
305
|
-
useEffect(() => {
|
|
306
|
-
transport?.activate();
|
|
307
|
-
return () => transport?.abort();
|
|
308
|
-
}, [transport]);
|
|
309
|
-
const blocked = current !== null && (current.kind !== "ready" || !runtimes);
|
|
310
|
-
useEffect(() => {
|
|
311
|
-
if (blocked) onUnavailable();
|
|
312
|
-
}, [blocked, onUnavailable]);
|
|
313
|
-
if (!current) return <ArtifactLoading label={labels.opening} />;
|
|
314
|
-
if (current.kind === "error") {
|
|
315
|
-
return (
|
|
316
|
-
<ArtifactProblem
|
|
317
|
-
view={artifactLoadErrorView(current.error, "editable", labels)}
|
|
318
|
-
onRetry={() => setRetryEpoch((value) => value + 1)}
|
|
319
|
-
/>
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
if (!config || !transport) {
|
|
323
|
-
return <ArtifactProblem view={{ ...labels.viewingDisabled, retryable: false }} />;
|
|
324
|
-
}
|
|
325
|
-
if (!runtimes) {
|
|
326
|
-
return <ArtifactProblem view={{ ...labels.editorsMissing, retryable: false }} />;
|
|
327
|
-
}
|
|
328
|
-
return (
|
|
329
|
-
<EditableArtifactView
|
|
330
|
-
baseUrl={baseUrl}
|
|
331
|
-
workspaceId={workspaceId}
|
|
332
|
-
artifactId={artifactId}
|
|
333
|
-
authorityKey={JSON.stringify([clientKey, sessionId, artifactCapabilityKey(config)])}
|
|
334
|
-
runtimes={{
|
|
335
|
-
...runtimes,
|
|
336
|
-
...(isLoopback(baseUrl) ? { allowInsecureDevelopmentAssets: true } : {}),
|
|
337
|
-
}}
|
|
338
|
-
transport={transport}
|
|
339
|
-
showHeader={false}
|
|
340
|
-
open={async (signal) => {
|
|
341
|
-
const replicaId = createEditableArtifactReplicaId();
|
|
342
|
-
const artifact = await client.getEditableArtifact(workspaceId, artifactId, {
|
|
343
|
-
replicaId,
|
|
344
|
-
signal,
|
|
345
|
-
});
|
|
346
|
-
signal.throwIfAborted();
|
|
347
|
-
onOpened(artifact.title, artifact.modality);
|
|
348
|
-
return {
|
|
349
|
-
artifact,
|
|
350
|
-
replicaId,
|
|
351
|
-
authority: {
|
|
352
|
-
deploymentOrigin: baseUrl.origin,
|
|
353
|
-
workspaceId,
|
|
354
|
-
...config.cachePartition,
|
|
355
|
-
},
|
|
356
|
-
};
|
|
357
|
-
}}
|
|
358
|
-
/>
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function artifactCapabilityKey(config: NonNullable<ClientConfig["artifacts"]>): string {
|
|
363
|
-
return JSON.stringify([
|
|
364
|
-
config.editableLiveUrl,
|
|
365
|
-
config.cachePartition.accountId,
|
|
366
|
-
config.cachePartition.principalId,
|
|
367
|
-
config.cachePartition.authorizationEpoch,
|
|
368
|
-
]);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function isLoopback(url: URL): boolean {
|
|
372
|
-
return (
|
|
373
|
-
(url.protocol === "http:" || url.protocol === "ws:") &&
|
|
374
|
-
(url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]")
|
|
375
|
-
);
|
|
376
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
import { loadSiteSnapshot } from "../../sites-ui";
|
|
4
|
-
import {
|
|
5
|
-
ArtifactLoading,
|
|
6
|
-
ArtifactProblem,
|
|
7
|
-
artifactLoadErrorView,
|
|
8
|
-
useArtifactLabels,
|
|
9
|
-
} from "./artifact-chrome";
|
|
10
|
-
import { ArtifactSandbox } from "./artifact-sandbox";
|
|
11
|
-
import type { SiteSnapshotClient, SiteToolBridgeFactory } from "./chat-interactive-block";
|
|
12
|
-
|
|
13
|
-
type Snapshot = Awaited<ReturnType<typeof loadSiteSnapshot>>;
|
|
14
|
-
|
|
15
|
-
export type SiteViewProps = {
|
|
16
|
-
client: SiteSnapshotClient;
|
|
17
|
-
workspaceId: string;
|
|
18
|
-
siteId: string;
|
|
19
|
-
/** Host-owned Site API access. Omit it and the Site renders without workspace tools. */
|
|
20
|
-
toolBridge?: SiteToolBridgeFactory | undefined;
|
|
21
|
-
theme?: "light" | "dark" | undefined;
|
|
22
|
-
/** Shown instead of the archived notice; the console links to its full page. */
|
|
23
|
-
archivedMessage?: string | undefined;
|
|
24
|
-
/** Show the Site title in the frame bar; hide it under a host header that already does. */
|
|
25
|
-
showTitle?: boolean | undefined;
|
|
26
|
-
/** Loading copy; defaults to "Loading Site…". */
|
|
27
|
-
loadingLabel?: string | undefined;
|
|
28
|
-
/** Called with the loaded title, for a host header. */
|
|
29
|
-
onTitle?: ((title: string) => void) | undefined;
|
|
30
|
-
className?: string | undefined;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A published Site filling its container: the console's session dock and an
|
|
35
|
-
* embedding host's artifact viewer render the same view.
|
|
36
|
-
*/
|
|
37
|
-
export function SiteView({
|
|
38
|
-
client,
|
|
39
|
-
workspaceId,
|
|
40
|
-
siteId,
|
|
41
|
-
toolBridge,
|
|
42
|
-
theme,
|
|
43
|
-
archivedMessage,
|
|
44
|
-
showTitle = true,
|
|
45
|
-
loadingLabel,
|
|
46
|
-
onTitle,
|
|
47
|
-
className,
|
|
48
|
-
}: SiteViewProps) {
|
|
49
|
-
const labels = useArtifactLabels();
|
|
50
|
-
const [snapshot, setSnapshot] = useState<Snapshot | null>(null);
|
|
51
|
-
const [error, setError] = useState<unknown>(null);
|
|
52
|
-
const readAbort = useRef<AbortController | null>(null);
|
|
53
|
-
const onTitleRef = useRef(onTitle);
|
|
54
|
-
onTitleRef.current = onTitle;
|
|
55
|
-
const load = useCallback(async () => {
|
|
56
|
-
readAbort.current?.abort();
|
|
57
|
-
const abort = new AbortController();
|
|
58
|
-
readAbort.current = abort;
|
|
59
|
-
try {
|
|
60
|
-
setError(null);
|
|
61
|
-
const next = await loadSiteSnapshot(client, workspaceId, siteId, {
|
|
62
|
-
signal: abort.signal,
|
|
63
|
-
includeArchivedContent: true,
|
|
64
|
-
});
|
|
65
|
-
if (abort.signal.aborted) return;
|
|
66
|
-
setSnapshot(next);
|
|
67
|
-
onTitleRef.current?.(next.detail.artifact.title);
|
|
68
|
-
} catch (nextError) {
|
|
69
|
-
if (abort.signal.aborted) return;
|
|
70
|
-
setSnapshot(null);
|
|
71
|
-
setError(nextError);
|
|
72
|
-
}
|
|
73
|
-
}, [client, siteId, workspaceId]);
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
setSnapshot(null);
|
|
76
|
-
void load();
|
|
77
|
-
return () => readAbort.current?.abort();
|
|
78
|
-
}, [load]);
|
|
79
|
-
const content = snapshot?.content;
|
|
80
|
-
const bridge = useMemo(
|
|
81
|
-
() =>
|
|
82
|
-
content
|
|
83
|
-
? toolBridge?.({
|
|
84
|
-
artifactId: siteId,
|
|
85
|
-
siteVersionId: content.versionId,
|
|
86
|
-
requestedTools: content.requestedTools,
|
|
87
|
-
})
|
|
88
|
-
: undefined,
|
|
89
|
-
[content, siteId, toolBridge],
|
|
90
|
-
);
|
|
91
|
-
if (error)
|
|
92
|
-
return (
|
|
93
|
-
<ArtifactProblem
|
|
94
|
-
view={artifactLoadErrorView(error, "site", labels)}
|
|
95
|
-
onRetry={() => void load()}
|
|
96
|
-
/>
|
|
97
|
-
);
|
|
98
|
-
if (!snapshot || !content) return <ArtifactLoading label={loadingLabel ?? labels.loadingSite} />;
|
|
99
|
-
if (snapshot.detail.artifact.status === "archived")
|
|
100
|
-
return (
|
|
101
|
-
<div className="p-4 text-sm text-fg-muted">{archivedMessage ?? labels.siteArchived}</div>
|
|
102
|
-
);
|
|
103
|
-
return (
|
|
104
|
-
<ArtifactSandbox
|
|
105
|
-
html={content.html}
|
|
106
|
-
title={snapshot.detail.artifact.title}
|
|
107
|
-
showTitle={showTitle}
|
|
108
|
-
versionLabel={`v${snapshot.detail.artifact.currentVersion?.revision}`}
|
|
109
|
-
toolBridge={bridge}
|
|
110
|
-
connectedToolCount={content.requestedTools.length}
|
|
111
|
-
theme={theme}
|
|
112
|
-
fill
|
|
113
|
-
className={className ?? "h-full rounded-none border-0"}
|
|
114
|
-
/>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import type { TimelinePillPlacement } from "./timeline-pill-placement";
|
|
2
|
-
|
|
3
|
-
const INTERACTIVE =
|
|
4
|
-
'a[href], button, input, select, textarea, summary, [role="button"], [role="link"], [role="menuitem"], [role="tab"], [role="switch"], [role="checkbox"], [contenteditable="true"]';
|
|
5
|
-
|
|
6
|
-
/** A control this wide is a row (a disclosure header); only what it draws counts. */
|
|
7
|
-
const ROW_WIDTH_RATIO = 0.8;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Hit-testing for useTimelinePillPlacement, loaded only once a pill shows so it
|
|
11
|
-
* stays out of a session's first load.
|
|
12
|
-
*/
|
|
13
|
-
export function choosePlacement(
|
|
14
|
-
pill: HTMLElement,
|
|
15
|
-
frame: HTMLElement,
|
|
16
|
-
scroller: HTMLElement,
|
|
17
|
-
preference: readonly TimelinePillPlacement[],
|
|
18
|
-
current: TimelinePillPlacement,
|
|
19
|
-
): TimelinePillPlacement {
|
|
20
|
-
const pillRect = pill.getBoundingClientRect();
|
|
21
|
-
const frameRect = frame.getBoundingClientRect();
|
|
22
|
-
const view = scroller.getBoundingClientRect();
|
|
23
|
-
if (pillRect.width === 0 || view.width === 0) return current;
|
|
24
|
-
const inset = Math.max(0, pillRect.left - frameRect.left, frameRect.right - pillRect.right);
|
|
25
|
-
const gutter = Math.min(inset, parseFloat(getComputedStyle(scroller).paddingLeft) || 16);
|
|
26
|
-
const left = (placement: TimelinePillPlacement) =>
|
|
27
|
-
placement === "start"
|
|
28
|
-
? frameRect.left + gutter
|
|
29
|
-
: placement === "end"
|
|
30
|
-
? frameRect.right - gutter - pillRect.width
|
|
31
|
-
: frameRect.left + (frameRect.width - pillRect.width) / 2;
|
|
32
|
-
const collisions = (placement: TimelinePillPlacement) =>
|
|
33
|
-
countCollisions(
|
|
34
|
-
pill,
|
|
35
|
-
scroller,
|
|
36
|
-
view,
|
|
37
|
-
left(placement),
|
|
38
|
-
pillRect.top,
|
|
39
|
-
pillRect.width,
|
|
40
|
-
pillRect.height,
|
|
41
|
-
);
|
|
42
|
-
// Stay put while the current spot is clear, so the pill does not wander.
|
|
43
|
-
if (collisions(current) === 0) return current;
|
|
44
|
-
let best = current;
|
|
45
|
-
let fewest = Number.POSITIVE_INFINITY;
|
|
46
|
-
for (const candidate of preference) {
|
|
47
|
-
const count = candidate === current ? collisions(current) : collisions(candidate);
|
|
48
|
-
if (count === 0) return candidate;
|
|
49
|
-
if (count < fewest) {
|
|
50
|
-
fewest = count;
|
|
51
|
-
best = candidate;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return best;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function countCollisions(
|
|
58
|
-
pill: HTMLElement,
|
|
59
|
-
scroller: HTMLElement,
|
|
60
|
-
view: DOMRect,
|
|
61
|
-
left: number,
|
|
62
|
-
top: number,
|
|
63
|
-
width: number,
|
|
64
|
-
height: number,
|
|
65
|
-
): number {
|
|
66
|
-
const document = scroller.ownerDocument;
|
|
67
|
-
let count = 0;
|
|
68
|
-
for (const fx of [0.06, 0.3, 0.5, 0.7, 0.94]) {
|
|
69
|
-
for (const fy of [0.2, 0.5, 0.8]) {
|
|
70
|
-
const x = left + width * fx;
|
|
71
|
-
const y = top + height * fy;
|
|
72
|
-
if (x < view.left || x > view.right || y < view.top || y > view.bottom) continue;
|
|
73
|
-
if (coversControl(document.elementsFromPoint(x, y), pill, scroller, view, x, y)) count += 1;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return count;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function coversControl(
|
|
80
|
-
stack: Element[],
|
|
81
|
-
pill: HTMLElement,
|
|
82
|
-
scroller: HTMLElement,
|
|
83
|
-
view: DOMRect,
|
|
84
|
-
x: number,
|
|
85
|
-
y: number,
|
|
86
|
-
): boolean {
|
|
87
|
-
for (const element of stack) {
|
|
88
|
-
if (pill.contains(element)) continue;
|
|
89
|
-
if (!scroller.contains(element) || element === scroller) return false;
|
|
90
|
-
const control = element.closest(INTERACTIVE);
|
|
91
|
-
if (!control || !scroller.contains(control)) return false;
|
|
92
|
-
// A compact control (a link, an icon button) is covered anywhere on its box.
|
|
93
|
-
if (control.getBoundingClientRect().width < view.width * ROW_WIDTH_RATIO) return true;
|
|
94
|
-
// A full-width row (a disclosure header) only where it draws something: an
|
|
95
|
-
// icon or the glyphs of its label, never its empty background.
|
|
96
|
-
if (element.closest("svg, img, canvas, video")) return true;
|
|
97
|
-
return textUnderPoint(element, x, y);
|
|
98
|
-
}
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function textUnderPoint(element: Element, x: number, y: number): boolean {
|
|
103
|
-
const range = element.ownerDocument.createRange();
|
|
104
|
-
for (const node of element.childNodes) {
|
|
105
|
-
if (node.nodeType !== Node.TEXT_NODE || !node.textContent?.trim()) continue;
|
|
106
|
-
range.selectNodeContents(node);
|
|
107
|
-
for (const rect of range.getClientRects()) {
|
|
108
|
-
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) return true;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
}
|