@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.
Files changed (157) hide show
  1. package/README.md +3 -48
  2. package/dist/artifacts-document.js +2 -3
  3. package/dist/artifacts-presentation.js +2 -3
  4. package/dist/artifacts-spreadsheet.js +2 -2
  5. package/dist/artifacts.d.ts +0 -1
  6. package/dist/artifacts.js +1402 -505
  7. package/dist/artifacts.js.map +1 -1
  8. package/dist/{browser-viewer-R6KK5VKR.js → browser-viewer-2E6R2Y4W.js} +4 -4
  9. package/dist/{chunk-WUISZ3WU.js → chunk-3E2AITTZ.js} +28 -32
  10. package/dist/{chunk-WUISZ3WU.js.map → chunk-3E2AITTZ.js.map} +1 -1
  11. package/dist/{chunk-PWUILT2U.js → chunk-5AZSK6MV.js} +2 -2
  12. package/dist/{chunk-Y2PRMYLC.js → chunk-DUEBTL3N.js} +3 -4
  13. package/dist/{chunk-Y2PRMYLC.js.map → chunk-DUEBTL3N.js.map} +1 -1
  14. package/dist/{chunk-7UAMYXJY.js → chunk-KNDIXZN6.js} +18 -25
  15. package/dist/{chunk-7UAMYXJY.js.map → chunk-KNDIXZN6.js.map} +1 -1
  16. package/dist/{chunk-P4G6K2E2.js → chunk-L4OSXQ6H.js} +884 -1119
  17. package/dist/chunk-L4OSXQ6H.js.map +1 -0
  18. package/dist/{chunk-LLIH7Q4B.js → chunk-S5JQ4R22.js} +19 -47
  19. package/dist/chunk-S5JQ4R22.js.map +1 -0
  20. package/dist/{chunk-A4UJB2MM.js → chunk-SEEVCT36.js} +2 -2
  21. package/dist/{chunk-UXJCQ5GH.js → chunk-UDD6DLLT.js} +1 -6
  22. package/dist/chunk-UDD6DLLT.js.map +1 -0
  23. package/dist/chunk-UGMKPUEM.js +299 -0
  24. package/dist/chunk-UGMKPUEM.js.map +1 -0
  25. package/dist/{chunk-ZCZHKJKZ.js → chunk-USXVQACU.js} +3 -3
  26. package/dist/{chunk-VABBF52G.js → chunk-V2ABIVYJ.js} +6 -70
  27. package/dist/chunk-V2ABIVYJ.js.map +1 -0
  28. package/dist/{chunk-TVO6GXR6.js → chunk-X4HIN6TV.js} +162 -71
  29. package/dist/chunk-X4HIN6TV.js.map +1 -0
  30. package/dist/{chunk-IIDEIBMC.js → chunk-XEJEOAOV.js} +38 -49
  31. package/dist/chunk-XEJEOAOV.js.map +1 -0
  32. package/dist/{chunk-ZKLCPBWN.js → chunk-Y3QTZN2F.js} +5 -5
  33. package/dist/{chunk-73BUNGLF.js → chunk-YZ3YYMOM.js} +1 -1
  34. package/dist/components/artifacts/editable-artifact-ui.d.ts +1 -2
  35. package/dist/components/artifacts/index.d.ts +0 -7
  36. package/dist/components/browser-select-control.d.ts +1 -3
  37. package/dist/components/message-timeline.d.ts +1 -18
  38. package/dist/components/open-geni-links.d.ts +0 -16
  39. package/dist/components/session-conversation.d.ts +1 -20
  40. package/dist/composer.js +3 -3
  41. package/dist/{computer-viewer-DJHPSP2J.js → computer-viewer-WMJE6OA3.js} +3 -3
  42. package/dist/connect.js +6 -7
  43. package/dist/connect.js.map +1 -1
  44. package/dist/hooks/latest-question.d.ts +1 -1
  45. package/dist/index.d.ts +2 -5
  46. package/dist/index.js +28 -39
  47. package/dist/index.js.map +1 -1
  48. package/dist/interaction.js +4 -4
  49. package/dist/{latest-question-KGCDK3XX.js → latest-question-QB4EUCW5.js} +3 -3
  50. package/dist/lib/format.d.ts +0 -3
  51. package/dist/machines.js +2 -2
  52. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-PFUEZ2U4.js} +5 -37
  53. package/dist/markdown-table-layout-PFUEZ2U4.js.map +1 -0
  54. package/dist/{sandbox-files-4PMXDSDV.js → sandbox-files-PXPIZAFG.js} +2 -2
  55. package/dist/session-ui.d.ts +2 -5
  56. package/dist/session-ui.js +13 -23
  57. package/dist/session-ui.js.map +1 -1
  58. package/dist/session.js +3 -3
  59. package/dist/sites.js +289 -4
  60. package/dist/sites.js.map +1 -1
  61. package/dist/timeline/types.d.ts +1 -7
  62. package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-ODLVF4OI.js} +1 -6
  63. package/dist/timeline-annotation-selection-ODLVF4OI.js.map +1 -0
  64. package/package.json +3 -7
  65. package/src/artifacts.ts +0 -37
  66. package/src/components/artifacts/editable-artifact-ui.tsx +1 -3
  67. package/src/components/artifacts/editable-artifact-workbench.tsx +2 -5
  68. package/src/components/artifacts/editable-document.tsx +36 -34
  69. package/src/components/artifacts/editable-presentation.tsx +16 -21
  70. package/src/components/artifacts/index.ts +0 -41
  71. package/src/components/browser-select-control.tsx +1 -4
  72. package/src/components/browser-viewer.tsx +22 -81
  73. package/src/components/computer-viewer.tsx +23 -51
  74. package/src/components/markdown-table-layout.ts +5 -49
  75. package/src/components/markdown.tsx +2 -5
  76. package/src/components/message-timeline.tsx +434 -515
  77. package/src/components/open-geni-links.tsx +0 -24
  78. package/src/components/session-conversation.tsx +20 -131
  79. package/src/components/timeline-annotation-selection.tsx +0 -7
  80. package/src/hooks/use-available-models.ts +1 -1
  81. package/src/index.ts +0 -9
  82. package/src/lib/format.ts +0 -13
  83. package/src/session-ui.ts +0 -9
  84. package/src/timeline/projection.ts +4 -78
  85. package/src/timeline/types.ts +0 -7
  86. package/styles/compiled.css +0 -325
  87. package/dist/chat-interactive-block-VAGABN7J.js +0 -11
  88. package/dist/chunk-5CVXZA3Y.js +0 -292
  89. package/dist/chunk-5CVXZA3Y.js.map +0 -1
  90. package/dist/chunk-B3V6TG77.js +0 -41
  91. package/dist/chunk-B3V6TG77.js.map +0 -1
  92. package/dist/chunk-IIDEIBMC.js.map +0 -1
  93. package/dist/chunk-LLIH7Q4B.js.map +0 -1
  94. package/dist/chunk-P4G6K2E2.js.map +0 -1
  95. package/dist/chunk-RAIQYITC.js +0 -1772
  96. package/dist/chunk-RAIQYITC.js.map +0 -1
  97. package/dist/chunk-SCOTPE2I.js +0 -589
  98. package/dist/chunk-SCOTPE2I.js.map +0 -1
  99. package/dist/chunk-TVO6GXR6.js.map +0 -1
  100. package/dist/chunk-UXJCQ5GH.js.map +0 -1
  101. package/dist/chunk-VABBF52G.js.map +0 -1
  102. package/dist/chunk-WEHMPOXK.js +0 -39
  103. package/dist/chunk-WEHMPOXK.js.map +0 -1
  104. package/dist/chunk-XCHWDJ22.js +0 -107
  105. package/dist/chunk-XCHWDJ22.js.map +0 -1
  106. package/dist/components/artifacts/artifact-chrome.d.ts +0 -120
  107. package/dist/components/artifacts/artifact-sandbox.d.ts +0 -27
  108. package/dist/components/artifacts/chat-interactive-block.d.ts +0 -29
  109. package/dist/components/artifacts/deferred-chat-media.d.ts +0 -15
  110. package/dist/components/artifacts/editable-artifact-view.d.ts +0 -46
  111. package/dist/components/artifacts/latest-task-runner.d.ts +0 -15
  112. package/dist/components/artifacts/session-artifact-viewer.d.ts +0 -39
  113. package/dist/components/artifacts/site-view.d.ts +0 -23
  114. package/dist/components/timeline-pill-geometry.d.ts +0 -6
  115. package/dist/components/timeline-pill-placement.d.ts +0 -18
  116. package/dist/components/usage-limit-notice.d.ts +0 -49
  117. package/dist/components/usage-member-list.d.ts +0 -91
  118. package/dist/components/usage-meter.d.ts +0 -73
  119. package/dist/hooks/use-usage.d.ts +0 -40
  120. package/dist/markdown-table-layout-QXSLK4BY.js.map +0 -1
  121. package/dist/sandbox-files-4PMXDSDV.js.map +0 -1
  122. package/dist/timeline/allowance-exhausted-row.d.ts +0 -24
  123. package/dist/timeline-annotation-selection-DMLMM2TX.js.map +0 -1
  124. package/dist/timeline-pill-geometry-2G6EK47V.js +0 -73
  125. package/dist/timeline-pill-geometry-2G6EK47V.js.map +0 -1
  126. package/dist/usage/allowance-copy.d.ts +0 -25
  127. package/dist/usage/summary.d.ts +0 -35
  128. package/dist/usage.d.ts +0 -13
  129. package/dist/usage.js +0 -1073
  130. package/dist/usage.js.map +0 -1
  131. package/src/components/artifacts/artifact-chrome.tsx +0 -413
  132. package/src/components/artifacts/artifact-sandbox.tsx +0 -185
  133. package/src/components/artifacts/chat-interactive-block.tsx +0 -260
  134. package/src/components/artifacts/deferred-chat-media.tsx +0 -67
  135. package/src/components/artifacts/editable-artifact-view.tsx +0 -187
  136. package/src/components/artifacts/latest-task-runner.ts +0 -49
  137. package/src/components/artifacts/session-artifact-viewer.tsx +0 -376
  138. package/src/components/artifacts/site-view.tsx +0 -116
  139. package/src/components/timeline-pill-geometry.ts +0 -112
  140. package/src/components/timeline-pill-placement.ts +0 -81
  141. package/src/components/usage-limit-notice.tsx +0 -179
  142. package/src/components/usage-member-list.tsx +0 -792
  143. package/src/components/usage-meter.tsx +0 -382
  144. package/src/hooks/use-usage.ts +0 -100
  145. package/src/timeline/allowance-exhausted-row.tsx +0 -86
  146. package/src/usage/allowance-copy.ts +0 -64
  147. package/src/usage/summary.ts +0 -93
  148. package/src/usage.ts +0 -50
  149. /package/dist/{browser-viewer-R6KK5VKR.js.map → browser-viewer-2E6R2Y4W.js.map} +0 -0
  150. /package/dist/{chunk-PWUILT2U.js.map → chunk-5AZSK6MV.js.map} +0 -0
  151. /package/dist/{chunk-A4UJB2MM.js.map → chunk-SEEVCT36.js.map} +0 -0
  152. /package/dist/{chunk-ZCZHKJKZ.js.map → chunk-USXVQACU.js.map} +0 -0
  153. /package/dist/{chunk-ZKLCPBWN.js.map → chunk-Y3QTZN2F.js.map} +0 -0
  154. /package/dist/{chunk-73BUNGLF.js.map → chunk-YZ3YYMOM.js.map} +0 -0
  155. /package/dist/{chat-interactive-block-VAGABN7J.js.map → computer-viewer-WMJE6OA3.js.map} +0 -0
  156. /package/dist/{latest-question-KGCDK3XX.js.map → latest-question-QB4EUCW5.js.map} +0 -0
  157. /package/dist/{computer-viewer-DJHPSP2J.js.map → sandbox-files-PXPIZAFG.js.map} +0 -0
@@ -1,185 +0,0 @@
1
- import {
2
- ArrowLeftIcon,
3
- Globe2Icon,
4
- Maximize2Icon,
5
- PlugZapIcon,
6
- RefreshCwIcon,
7
- SparklesIcon,
8
- } from "lucide-react";
9
- import { useLayoutEffect, useRef, useState, type ReactNode } from "react";
10
-
11
- import { cn } from "../../lib/cn";
12
- import { ArtifactBadge, ArtifactButton, useArtifactLabels } from "./artifact-chrome";
13
- import {
14
- PublishedHtmlArtifactFrame,
15
- type PublishedHtmlArtifactToolBridge,
16
- } from "./published-html-artifact-frame";
17
-
18
- export type ArtifactSandboxProps = {
19
- html: string;
20
- height?: number | undefined;
21
- autoHeight?: boolean | undefined;
22
- theme?: "light" | "dark" | undefined;
23
- headerControls?: ReactNode | undefined;
24
- title: string;
25
- /** Hide the visible title/icon while retaining the accessible iframe title. */
26
- showTitle?: boolean | undefined;
27
- showLiveStatus?: boolean | undefined;
28
- versionLabel?: string | undefined;
29
- className?: string | undefined;
30
- editDisabled?: boolean | undefined;
31
- onEdit?: (() => void) | undefined;
32
- toolBridge?: PublishedHtmlArtifactToolBridge | undefined;
33
- connectedToolCount?: number | undefined;
34
- sourceFileCount?: number | undefined;
35
- fill?: boolean | undefined;
36
- };
37
-
38
- /**
39
- * The Site/preview frame chrome shared by the console and embedding hosts:
40
- * title, version and tool badges, live status, reload, and a full-screen mode
41
- * that escapes the host's scroll containers through the modal top layer.
42
- */
43
- export function ArtifactSandbox(props: ArtifactSandboxProps) {
44
- const labels = useArtifactLabels();
45
- const [reloadKey, setReloadKey] = useState(0);
46
- const [focused, setFocused] = useState(false);
47
- const frameRef = useRef<HTMLDialogElement>(null);
48
- const modalRef = useRef(false);
49
- useLayoutEffect(() => {
50
- const frame = frameRef.current;
51
- if (!frame) return;
52
- if (focused) {
53
- // A fixed child of the chat timeline is still clipped and layered by its
54
- // scroll pane and the sibling workspace dock. The modal top layer escapes
55
- // both while keeping the live Site iframe mounted in the same DOM node.
56
- frame.close();
57
- frame.showModal();
58
- modalRef.current = true;
59
- } else if (modalRef.current) {
60
- frame.close();
61
- frame.show();
62
- modalRef.current = false;
63
- }
64
- }, [focused]);
65
- const reload = () => {
66
- setReloadKey((value) => value + 1);
67
- };
68
- return (
69
- <dialog
70
- ref={frameRef}
71
- open
72
- aria-label={props.title}
73
- onCancel={(event) => {
74
- event.preventDefault();
75
- setFocused(false);
76
- }}
77
- className={cn(
78
- "og-root static m-0 w-full max-h-none max-w-none overflow-hidden rounded-2xl border border-border/80 bg-white p-0 text-left shadow-sm",
79
- props.className,
80
- props.fill && "flex min-h-0 flex-col",
81
- focused &&
82
- "fixed inset-0 z-50 flex h-dvh w-dvw flex-col rounded-none border-0 bg-surface shadow-none",
83
- )}
84
- >
85
- <div className="flex min-h-12 shrink-0 items-center justify-between gap-3 border-b border-border/80 bg-surface/95 px-3 sm:px-4">
86
- <div className="flex min-w-0 items-center gap-2.5">
87
- {focused ? (
88
- <ArtifactButton
89
- variant="ghost"
90
- size="sm"
91
- className="h-8 shrink-0 px-2"
92
- onClick={() => setFocused(false)}
93
- >
94
- <ArrowLeftIcon className="mr-2 size-3.5" />
95
- {labels.back}
96
- </ArtifactButton>
97
- ) : null}
98
- {!focused && props.showTitle !== false ? (
99
- <span className="grid size-6 shrink-0 place-items-center rounded-md bg-surface-2 text-fg-muted">
100
- <Globe2Icon className="size-3.5" />
101
- </span>
102
- ) : null}
103
- {props.showTitle !== false && (
104
- <span className="truncate text-xs font-semibold text-fg">{props.title}</span>
105
- )}
106
- {props.headerControls}
107
- {props.versionLabel ? (
108
- <ArtifactBadge className="hidden h-5 rounded-md border-border/80 px-1.5 text-2xs font-normal text-fg-muted sm:inline-flex">
109
- {props.versionLabel}
110
- </ArtifactBadge>
111
- ) : null}
112
- {props.connectedToolCount ? (
113
- <ArtifactBadge
114
- className="hidden h-5 max-w-40 gap-1 rounded-md border-border/80 px-1.5 text-2xs font-normal text-fg-muted sm:inline-flex"
115
- title={labels.toolsAvailable(props.connectedToolCount)}
116
- >
117
- <PlugZapIcon className="size-3" />
118
- {labels.toolCount(props.connectedToolCount)}
119
- </ArtifactBadge>
120
- ) : null}
121
- {props.sourceFileCount ? (
122
- <span className="hidden text-2xs text-fg-subtle xl:inline">
123
- {labels.sourceFileCount(props.sourceFileCount)}
124
- </span>
125
- ) : null}
126
- </div>
127
- <div className="flex shrink-0 items-center gap-1">
128
- {focused && props.onEdit ? (
129
- <ArtifactButton
130
- variant="ghost"
131
- size="sm"
132
- className="h-8 px-2"
133
- disabled={props.editDisabled}
134
- onClick={props.onEdit}
135
- >
136
- <SparklesIcon className="mr-2 size-3.5" />
137
- <span className="hidden sm:inline">{labels.editWithAgent}</span>
138
- <span className="sm:hidden">{labels.editShort}</span>
139
- </ArtifactButton>
140
- ) : null}
141
- {props.showLiveStatus !== false && (
142
- <span className="mr-1 hidden items-center gap-1.5 text-2xs font-medium text-fg-muted sm:inline-flex">
143
- <span className="size-1.5 rounded-full bg-status-success ring-4 ring-status-success/10" />
144
- {labels.live}
145
- </span>
146
- )}
147
- <ArtifactButton
148
- variant="ghost"
149
- size="icon"
150
- className="size-8 rounded-md text-fg-muted hover:text-fg"
151
- aria-label={labels.reloadSite}
152
- onClick={reload}
153
- >
154
- <RefreshCwIcon className="size-3.5" />
155
- </ArtifactButton>
156
- {!focused ? (
157
- <ArtifactButton
158
- variant="ghost"
159
- size="icon"
160
- className="size-8 rounded-md text-fg-muted hover:text-fg"
161
- aria-label={labels.openFullScreen}
162
- onClick={() => setFocused(true)}
163
- >
164
- <Maximize2Icon className="size-3.5" />
165
- </ArtifactButton>
166
- ) : null}
167
- </div>
168
- </div>
169
- <PublishedHtmlArtifactFrame
170
- key={reloadKey}
171
- title={props.title}
172
- html={props.html}
173
- autoHeight={Boolean(props.autoHeight && !focused)}
174
- {...(props.theme ? { theme: props.theme } : {})}
175
- {...(!focused && props.height ? { style: { height: props.height } } : {})}
176
- {...(props.toolBridge ? { toolBridge: props.toolBridge } : {})}
177
- className={cn(
178
- "block h-[clamp(30rem,62vh,48rem)] w-full border-0 bg-white",
179
- props.fill && "h-0 min-h-0 flex-1",
180
- focused && "min-h-0 flex-1",
181
- )}
182
- />
183
- </dialog>
184
- );
185
- }
@@ -1,260 +0,0 @@
1
- import type { OpenGeniClient, ToolGatewayIdentity } from "@opengeni/sdk";
2
- import { useEffect, useMemo, useState } from "react";
3
-
4
- import { loadSiteSnapshot } from "../../sites-ui";
5
- import { useOpenGeniLinkResolver } from "../open-geni-links";
6
- import {
7
- ArtifactButton,
8
- ArtifactLabelsProvider,
9
- ArtifactSelect,
10
- useArtifactLabels,
11
- type ArtifactLabels,
12
- } from "./artifact-chrome";
13
- import { ArtifactSandbox } from "./artifact-sandbox";
14
- import { DeferredChatMedia } from "./deferred-chat-media";
15
- import { inlineHtmlDocument } from "./inline-html-document";
16
- import type { PublishedHtmlArtifactToolBridge } from "./published-html-artifact-frame";
17
-
18
- // Fixed chat viewports keep async Site loads and fragment resize messages from
19
- // moving the conversation. Full-screen remains available for larger content.
20
- const INLINE_PREVIEW_HEIGHT = 360;
21
- const SITE_PREVIEW_HEIGHT = 400;
22
- const PREVIEW_CHROME_HEIGHT = 50;
23
-
24
- /** Reads a Site needs to render; the console client, the SDK client, or a proxy. */
25
- export type SiteSnapshotClient = Pick<
26
- OpenGeniClient,
27
- "getWorkspaceArtifact" | "getWorkspaceArtifactHtml"
28
- > &
29
- Partial<Pick<OpenGeniClient, "getWorkspaceArtifactContent">>;
30
-
31
- /** The exact saved Site a tool bridge is pinned to (absent for inline HTML). */
32
- export type SiteToolScope = {
33
- artifactId: string;
34
- siteVersionId: string;
35
- requestedTools: readonly ToolGatewayIdentity[];
36
- };
37
-
38
- /** Host-owned Site API access. Omit it and Sites render without workspace tools. */
39
- export type SiteToolBridgeFactory = (
40
- site?: SiteToolScope,
41
- ) => PublishedHtmlArtifactToolBridge | undefined;
42
-
43
- export type ChatInteractiveBlockProps = {
44
- workspaceId: string;
45
- kind: "html" | "site";
46
- content: string;
47
- client: SiteSnapshotClient;
48
- toolBridge?: SiteToolBridgeFactory | undefined;
49
- theme?: "light" | "dark" | undefined;
50
- /** Translations for the preview's copy; defaults to English. */
51
- labels?: Partial<ArtifactLabels> | undefined;
52
- };
53
-
54
- /**
55
- * Inline preview for an assistant `opengeni-html` or `opengeni-site` fence, as
56
- * the OpenGeni console renders it. "Open Site" goes through the nearest
57
- * `resolveLink`, so a host decides where a Site opens.
58
- */
59
- export function ChatInteractiveBlock({ labels, ...props }: ChatInteractiveBlockProps) {
60
- return (
61
- <ArtifactLabelsProvider labels={labels}>
62
- <InteractiveBlock {...props} />
63
- </ArtifactLabelsProvider>
64
- );
65
- }
66
-
67
- function InteractiveBlock(props: ChatInteractiveBlockProps) {
68
- const labels = useArtifactLabels();
69
- const height =
70
- (props.kind === "html" ? INLINE_PREVIEW_HEIGHT : SITE_PREVIEW_HEIGHT) + PREVIEW_CHROME_HEIGHT;
71
- return (
72
- <DeferredChatMedia
73
- key={`${props.workspaceId}:${props.kind}:${props.kind === "site" ? props.content : "inline"}`}
74
- height={height}
75
- actionLabel={props.kind === "html" ? labels.loadPreview : labels.loadSitePreview}
76
- >
77
- <div style={{ height }}>
78
- <LoadedChatInteractiveBlock {...props} />
79
- </div>
80
- </DeferredChatMedia>
81
- );
82
- }
83
-
84
- function LoadedChatInteractiveBlock(props: ChatInteractiveBlockProps) {
85
- const labels = useArtifactLabels();
86
- if (props.kind === "html") return <InlineHtml {...props} />;
87
- try {
88
- const value = JSON.parse(props.content);
89
- if (
90
- !value ||
91
- typeof value.siteId !== "string" ||
92
- !/^[0-9a-f-]{36}$/i.test(value.siteId) ||
93
- (value.versionId !== undefined &&
94
- (typeof value.versionId !== "string" || !/^[0-9a-f-]{36}$/i.test(value.versionId)))
95
- )
96
- throw new Error("Invalid Site reference");
97
- return (
98
- <SiteEmbed
99
- key={`${props.workspaceId}:${value.siteId}:${value.versionId ?? "current"}`}
100
- {...props}
101
- siteId={value.siteId}
102
- initialVersionId={value.versionId}
103
- />
104
- );
105
- } catch {
106
- return <p role="alert">{labels.siteReferenceInvalid}</p>;
107
- }
108
- }
109
-
110
- function InlineHtml({ content, toolBridge, theme }: ChatInteractiveBlockProps) {
111
- const labels = useArtifactLabels();
112
- const html = useMemo(() => inlineHtmlDocument(content), [content]);
113
- const bridge = useMemo(() => toolBridge?.(), [toolBridge]);
114
- return (
115
- <ArtifactSandbox
116
- title={labels.preview}
117
- showTitle={false}
118
- showLiveStatus={false}
119
- html={html}
120
- toolBridge={bridge}
121
- height={INLINE_PREVIEW_HEIGHT}
122
- className="h-full"
123
- fill
124
- theme={theme}
125
- />
126
- );
127
- }
128
-
129
- function SiteEmbed(
130
- props: ChatInteractiveBlockProps & { siteId: string; initialVersionId?: string },
131
- ) {
132
- const [versionId, setVersionId] = useState(props.initialVersionId);
133
- return (
134
- <SiteEmbedContent
135
- key={versionId ?? "current"}
136
- {...props}
137
- versionId={versionId}
138
- onVersionChange={setVersionId}
139
- />
140
- );
141
- }
142
-
143
- function SiteEmbedContent({
144
- workspaceId,
145
- client,
146
- toolBridge,
147
- theme,
148
- siteId,
149
- versionId,
150
- onVersionChange,
151
- }: ChatInteractiveBlockProps & {
152
- siteId: string;
153
- versionId?: string | undefined;
154
- onVersionChange: (id: string) => void;
155
- }) {
156
- const [loaded, setLoaded] = useState<{
157
- client: SiteSnapshotClient;
158
- snapshot: Awaited<ReturnType<typeof loadSiteSnapshot>>;
159
- } | null>(null);
160
- const snapshot = loaded?.client === client ? loaded.snapshot : null;
161
- const [error, setError] = useState(false);
162
- const [retry, setRetry] = useState(0);
163
- useEffect(() => {
164
- const abort = new AbortController();
165
- setError(false);
166
- setLoaded(null);
167
- void loadSiteSnapshot(client, workspaceId, siteId, {
168
- signal: abort.signal,
169
- ...(versionId ? { versionId } : {}),
170
- })
171
- .then((value) => {
172
- if (!abort.signal.aborted) setLoaded({ client, snapshot: value });
173
- })
174
- .catch(() => {
175
- if (!abort.signal.aborted) setError(true);
176
- });
177
- return () => abort.abort();
178
- }, [client, workspaceId, siteId, versionId, retry]);
179
- const content = snapshot?.content;
180
- const bridge = useMemo(
181
- () =>
182
- content
183
- ? toolBridge?.({
184
- artifactId: siteId,
185
- siteVersionId: content.versionId,
186
- requestedTools: content.requestedTools,
187
- })
188
- : undefined,
189
- [toolBridge, siteId, content],
190
- );
191
- const open = useOpenGeniLinkResolver()?.({ kind: "site", artifactId: siteId, workspaceId });
192
- const labels = useArtifactLabels();
193
- if (error)
194
- return (
195
- <p role="alert">
196
- {labels.siteLoadFailed}{" "}
197
- <ArtifactButton size="sm" onClick={() => setRetry((v) => v + 1)}>
198
- {labels.retry}
199
- </ArtifactButton>
200
- </p>
201
- );
202
- if (!snapshot) return <p role="status">{labels.loadingSite}</p>;
203
- if (!content) return <p>{labels.siteUnpublished}</p>;
204
- return (
205
- <ArtifactSandbox
206
- title={snapshot.detail.artifact.title}
207
- html={content.html}
208
- toolBridge={bridge}
209
- height={SITE_PREVIEW_HEIGHT}
210
- className="h-full"
211
- fill
212
- theme={theme}
213
- headerControls={
214
- <>
215
- {/* One published version has nothing to choose; keep the bar for the title. */}
216
- {snapshot.detail.versions.length > 1 ||
217
- !snapshot.detail.versions.some((v) => v.id === content.versionId) ? (
218
- <ArtifactSelect
219
- aria-label={labels.siteVersion}
220
- className="bg-transparent text-xs"
221
- value={content.versionId}
222
- onChange={(e) => onVersionChange(e.target.value)}
223
- >
224
- {!snapshot.detail.versions.some((v) => v.id === content.versionId) && (
225
- <option value={content.versionId}>{labels.savedVersion}</option>
226
- )}
227
- {snapshot.detail.versions.map((v) => (
228
- <option key={v.id} value={v.id}>
229
- {labels.version(v.revision)}
230
- </option>
231
- ))}
232
- </ArtifactSelect>
233
- ) : null}
234
- {open?.href ? (
235
- <a
236
- className="shrink-0 whitespace-nowrap text-xs underline"
237
- href={open.href}
238
- data-og-open-site=""
239
- >
240
- {labels.openSite}
241
- </a>
242
- ) : open?.open ? (
243
- <button
244
- type="button"
245
- className="shrink-0 cursor-pointer whitespace-nowrap text-xs underline"
246
- data-og-open-site=""
247
- onClick={() =>
248
- void Promise.resolve()
249
- .then(open.open)
250
- .catch(() => undefined)
251
- }
252
- >
253
- {labels.openSite}
254
- </button>
255
- ) : null}
256
- </>
257
- }
258
- />
259
- );
260
- }
@@ -1,67 +0,0 @@
1
- import { useEffect, useRef, useState, type ReactNode } from "react";
2
-
3
- /**
4
- * Reserve the chat slot before starting expensive media work. Activation is
5
- * one-way: scrolling away must not discard an interactive preview's state.
6
- * The timeline parks at its initial tip in layout effects before this observer
7
- * is attached, so historical previews above the entry viewport stay dormant.
8
- */
9
- export function DeferredChatMedia({
10
- children,
11
- height,
12
- label,
13
- actionLabel,
14
- }: {
15
- children: ReactNode;
16
- height: number;
17
- /** What is loading, as in "Load {label}". */
18
- label?: string | undefined;
19
- /** The complete (translated) activation label; overrides `label`. */
20
- actionLabel?: string | undefined;
21
- }) {
22
- const host = useRef<HTMLDivElement>(null);
23
- const [active, setActive] = useState(false);
24
- useEffect(() => {
25
- const node = host.current;
26
- if (active || !node) return;
27
- if (typeof IntersectionObserver === "undefined") {
28
- setActive(true);
29
- return;
30
- }
31
- let disposed = false;
32
- const observer = new IntersectionObserver(
33
- (entries) => {
34
- if (disposed || !entries.some((entry) => entry.isIntersecting)) return;
35
- setActive(true);
36
- observer.disconnect();
37
- },
38
- {
39
- root: node.closest("[data-og-timeline-scroller]"),
40
- rootMargin: "200px 0px",
41
- },
42
- );
43
- observer.observe(node);
44
- return () => {
45
- disposed = true;
46
- observer.disconnect();
47
- };
48
- }, [active]);
49
-
50
- return (
51
- <div ref={host} data-chat-media={active ? "active" : "deferred"}>
52
- {active ? (
53
- children
54
- ) : (
55
- <div className="flex w-full items-center justify-center" style={{ height }}>
56
- <button
57
- type="button"
58
- className="rounded-md px-3 py-2 text-sm text-fg-muted hover:text-fg focus-visible:outline-2 focus-visible:outline-ring"
59
- onClick={() => setActive(true)}
60
- >
61
- {actionLabel ?? `Load ${label ?? "media"}`}
62
- </button>
63
- </div>
64
- )}
65
- </div>
66
- );
67
- }
@@ -1,187 +0,0 @@
1
- import type { EditableArtifactResource } from "@opengeni/sdk/artifacts";
2
- import {
3
- createBrowserEditableArtifactSession,
4
- type CreateBrowserEditableArtifactSessionOptions,
5
- type EditableArtifactBrowserRuntime,
6
- type EditableArtifactCacheAuthority,
7
- type EditableArtifactModality,
8
- } from "@opengeni/sdk/editable-artifacts";
9
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
-
11
- import {
12
- ArtifactLoading,
13
- ArtifactProblem,
14
- artifactLoadErrorView,
15
- useArtifactLabels,
16
- type ArtifactLoadErrorView,
17
- } from "./artifact-chrome";
18
- import { EditableArtifactWorkbenchHost } from "./editable-artifact-workbench";
19
-
20
- /**
21
- * The browser kernels an editor needs, one per modality, plus the host-built
22
- * Worker URL (for Vite: `@opengeni/sdk/editable-artifacts/worker?worker&url`).
23
- * Kernels come from `@opengeni/artifact-kernel-wasm-{document,spreadsheet,presentation}`.
24
- */
25
- export type EditableArtifactRuntimes = Readonly<{
26
- document: Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory">;
27
- spreadsheet: Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory">;
28
- presentation: Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory">;
29
- workerUrl: string;
30
- applicationOrigin?: string | undefined;
31
- /** Loopback development only. */
32
- allowInsecureDevelopmentAssets?: boolean | undefined;
33
- }>;
34
-
35
- /** Everything needed to open one artifact for the current principal. */
36
- export type OpenedEditableArtifact = Readonly<{
37
- artifact: EditableArtifactResource;
38
- authority: EditableArtifactCacheAuthority;
39
- replicaId: string;
40
- }>;
41
-
42
- export type EditableArtifactViewProps = Readonly<{
43
- /** Absolute API (or session proxy) base the live transport talks to. */
44
- baseUrl: URL;
45
- workspaceId: string;
46
- artifactId: string;
47
- /** Resolve metadata, cache authority, and replica; called again on retry. */
48
- open: (signal: AbortSignal) => Promise<OpenedEditableArtifact>;
49
- runtimes: EditableArtifactRuntimes;
50
- transport?: CreateBrowserEditableArtifactSessionOptions["transport"];
51
- /** Show the editor's own title header (hosts with their own header hide it). */
52
- showHeader?: boolean | undefined;
53
- /** Re-open when this changes (for example the host's credential version). */
54
- authorityKey?: string | undefined;
55
- describeError?: ((error: unknown) => ArtifactLoadErrorView) | undefined;
56
- onTitle?: ((title: string) => void) | undefined;
57
- /** Loading copy; defaults to the `opening` label. */
58
- loadingLabel?: string | undefined;
59
- }>;
60
-
61
- type LoadState =
62
- | Readonly<{ key: string | null; kind: "loading" }>
63
- | Readonly<{ key: string; kind: "ready"; value: OpenedEditableArtifact }>
64
- | Readonly<{ key: string; kind: "error"; error: unknown }>;
65
-
66
- /**
67
- * One editable document, spreadsheet, or presentation with its first-party
68
- * editor. The console dock/page and embedding hosts supply only data access.
69
- */
70
- export function EditableArtifactView({
71
- baseUrl,
72
- workspaceId,
73
- artifactId,
74
- open,
75
- runtimes,
76
- transport,
77
- showHeader = true,
78
- authorityKey,
79
- describeError,
80
- onTitle,
81
- loadingLabel,
82
- }: EditableArtifactViewProps) {
83
- const [loadEpoch, setLoadEpoch] = useState(0);
84
- const labels = useArtifactLabels();
85
- const loadKey = JSON.stringify([artifactId, authorityKey, baseUrl.href, workspaceId, loadEpoch]);
86
- const [state, setState] = useState<LoadState>({ key: null, kind: "loading" });
87
- const current = state.key === loadKey ? state : null;
88
- const openRef = useRef(open);
89
- openRef.current = open;
90
- const onTitleRef = useRef(onTitle);
91
- onTitleRef.current = onTitle;
92
- useEffect(() => {
93
- const abort = new AbortController();
94
- setState({ key: loadKey, kind: "loading" });
95
- void openRef
96
- .current(abort.signal)
97
- .then((value) => {
98
- if (abort.signal.aborted) return;
99
- setState({ key: loadKey, kind: "ready", value });
100
- onTitleRef.current?.(value.artifact.title);
101
- })
102
- .catch((error: unknown) => {
103
- if (!abort.signal.aborted) setState({ key: loadKey, kind: "error", error });
104
- });
105
- return () => abort.abort();
106
- }, [loadKey]);
107
-
108
- const ready = current?.kind === "ready" ? current.value : null;
109
- const transportRef = useRef(transport);
110
- transportRef.current = transport;
111
- const sessionKey = useMemo(
112
- () =>
113
- ready
114
- ? JSON.stringify({
115
- baseUrl: baseUrl.href,
116
- authorityKey,
117
- workspaceId,
118
- artifact: { id: ready.artifact.id, modality: ready.artifact.modality },
119
- authority: ready.authority,
120
- replicaId: ready.replicaId,
121
- workerUrl: runtimes.workerUrl,
122
- })
123
- : "",
124
- [authorityKey, baseUrl.href, ready, runtimes.workerUrl, workspaceId],
125
- );
126
- const createSession = useCallback(() => {
127
- if (!ready) throw new Error("Artifact is not open");
128
- return createBrowserEditableArtifactSession({
129
- baseUrl,
130
- workspaceId,
131
- artifact: ready.artifact,
132
- storageAuthority: ready.authority,
133
- replicaId: ready.replicaId,
134
- runtime: {
135
- ...runtimeFor(runtimes, ready.artifact.modality),
136
- workerUrl: runtimes.workerUrl,
137
- ...(runtimes.applicationOrigin ? { applicationOrigin: runtimes.applicationOrigin } : {}),
138
- ...(runtimes.allowInsecureDevelopmentAssets
139
- ? { allowInsecureDevelopmentAssets: true }
140
- : {}),
141
- },
142
- ...(transportRef.current ? { transport: transportRef.current } : {}),
143
- });
144
- }, [baseUrl, ready, runtimes, workspaceId]);
145
-
146
- if (!current || current.kind === "loading")
147
- return <ArtifactLoading label={loadingLabel ?? labels.opening} />;
148
- if (current.kind === "error") {
149
- return (
150
- <ArtifactProblem
151
- view={
152
- describeError
153
- ? describeError(current.error)
154
- : artifactLoadErrorView(current.error, "editable", labels)
155
- }
156
- onRetry={() => setLoadEpoch((value) => value + 1)}
157
- />
158
- );
159
- }
160
- const { artifact } = current.value;
161
- const surface = { title: artifact.title, showHeader };
162
- return (
163
- <div className="h-full min-h-0 bg-og-bg text-og-fg">
164
- <EditableArtifactWorkbenchHost
165
- sessionKey={sessionKey}
166
- createSession={createSession}
167
- document={surface}
168
- spreadsheet={surface}
169
- presentation={surface}
170
- />
171
- </div>
172
- );
173
- }
174
-
175
- function runtimeFor(
176
- runtimes: EditableArtifactRuntimes,
177
- modality: EditableArtifactModality,
178
- ): Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory"> {
179
- switch (modality) {
180
- case "document":
181
- return runtimes.document;
182
- case "spreadsheet":
183
- return runtimes.spreadsheet;
184
- case "presentation":
185
- return runtimes.presentation;
186
- }
187
- }