@opengeni/react 7.4.0 → 7.5.0-canary.36927100590001
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 +124 -27
- package/dist/artifacts-document.js +3 -2
- package/dist/artifacts-presentation.js +3 -2
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +516 -1413
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-2E6R2Y4W.js → browser-viewer-Z7S7QEJL.js} +6 -5
- package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
- package/dist/{chunk-Y3QTZN2F.js → chunk-2VFNCX4E.js} +6 -6
- package/dist/{chunk-UDD6DLLT.js → chunk-3IJJEYW4.js} +15 -3
- package/dist/chunk-3IJJEYW4.js.map +1 -0
- package/dist/chunk-4RVYOY4O.js +379 -0
- package/dist/chunk-4RVYOY4O.js.map +1 -0
- package/dist/chunk-5CVXZA3Y.js +292 -0
- package/dist/chunk-5CVXZA3Y.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-5J4FOW6Q.js} +2 -10
- package/dist/chunk-5J4FOW6Q.js.map +1 -0
- package/dist/{chunk-KVERXCFY.js → chunk-5KYZIQFY.js} +40 -3
- package/dist/chunk-5KYZIQFY.js.map +1 -0
- package/dist/{chunk-KNDIXZN6.js → chunk-7UAMYXJY.js} +25 -18
- package/dist/{chunk-KNDIXZN6.js.map → chunk-7UAMYXJY.js.map} +1 -1
- package/dist/{chunk-SEEVCT36.js → chunk-A4UJB2MM.js} +2 -2
- package/dist/{chunk-X4HIN6TV.js → chunk-AFLPWUQ7.js} +71 -162
- package/dist/chunk-AFLPWUQ7.js.map +1 -0
- package/dist/chunk-B3V6TG77.js +41 -0
- package/dist/chunk-B3V6TG77.js.map +1 -0
- package/dist/{chunk-XEJEOAOV.js → chunk-B7WMS3CS.js} +51 -40
- package/dist/chunk-B7WMS3CS.js.map +1 -0
- package/dist/{chunk-S5JQ4R22.js → chunk-BDKR6BAR.js} +54 -24
- package/dist/chunk-BDKR6BAR.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-DYWBUX5Q.js} +24 -12
- package/dist/chunk-DYWBUX5Q.js.map +1 -0
- package/dist/{chunk-YZ3YYMOM.js → chunk-E3G7SAYZ.js} +252 -624
- package/dist/chunk-E3G7SAYZ.js.map +1 -0
- package/dist/chunk-JT4JW7BC.js +54 -0
- package/dist/chunk-JT4JW7BC.js.map +1 -0
- package/dist/{chunk-5AZSK6MV.js → chunk-LWNLETQT.js} +5 -5
- package/dist/{chunk-USXVQACU.js → chunk-M7LBYP63.js} +5 -4
- package/dist/{chunk-USXVQACU.js.map → chunk-M7LBYP63.js.map} +1 -1
- package/dist/{chunk-L4OSXQ6H.js → chunk-NBOHNRLJ.js} +1289 -1060
- package/dist/chunk-NBOHNRLJ.js.map +1 -0
- package/dist/chunk-RHREZ7BO.js +11 -0
- package/dist/chunk-RHREZ7BO.js.map +1 -0
- package/dist/chunk-SCOTPE2I.js +589 -0
- package/dist/chunk-SCOTPE2I.js.map +1 -0
- package/dist/chunk-WEHMPOXK.js +39 -0
- package/dist/chunk-WEHMPOXK.js.map +1 -0
- package/dist/chunk-WRHIZYZF.js +988 -0
- package/dist/chunk-WRHIZYZF.js.map +1 -0
- package/dist/{chunk-3E2AITTZ.js → chunk-WUISZ3WU.js} +32 -28
- package/dist/{chunk-3E2AITTZ.js.map → chunk-WUISZ3WU.js.map} +1 -1
- package/dist/chunk-XCHWDJ22.js +107 -0
- package/dist/chunk-XCHWDJ22.js.map +1 -0
- package/dist/{chunk-DUEBTL3N.js → chunk-Y2PRMYLC.js} +4 -3
- package/dist/{chunk-DUEBTL3N.js.map → chunk-Y2PRMYLC.js.map} +1 -1
- package/dist/chunk-YBXQDQFX.js +1772 -0
- package/dist/chunk-YBXQDQFX.js.map +1 -0
- package/dist/chunk-YV4L3ILF.js +9 -0
- package/dist/chunk-YV4L3ILF.js.map +1 -0
- package/dist/{chunk-V2ABIVYJ.js → chunk-ZXSEDZP7.js} +70 -6
- package/dist/chunk-ZXSEDZP7.js.map +1 -0
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
- package/dist/components/artifacts/index.d.ts +7 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +23 -0
- package/dist/components/browser-select-control.d.ts +3 -1
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +20 -3
- package/dist/components/open-geni-links.d.ts +16 -0
- package/dist/components/session-conversation.d.ts +20 -1
- package/dist/components/timeline-pill-geometry.d.ts +8 -0
- package/dist/components/timeline-pill-placement.d.ts +24 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/composer.js +5 -5
- package/dist/{computer-viewer-WMJE6OA3.js → computer-viewer-KJWLH3GI.js} +7 -5
- package/dist/connect.js +7 -6
- package/dist/connect.js.map +1 -1
- package/dist/conversation-timeline.d.ts +1 -1
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +26 -0
- package/dist/desktop.js.map +1 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +21 -0
- package/dist/editor.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +1 -1
- package/dist/hooks/use-composer.d.ts +4 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +460 -1386
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +8 -6
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-QB4EUCW5.js → latest-question-IS445HOW.js} +3 -3
- package/dist/lib/format.d.ts +6 -1
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/machines.js +2 -2
- package/dist/{markdown-table-layout-PFUEZ2U4.js → markdown-table-layout-QXSLK4BY.js} +37 -5
- package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
- package/dist/{queue-surface-implementation-IM2HD3SK.js → queue-surface-implementation-2CAQAIHM.js} +2 -2
- package/dist/realtime.js +3 -3
- package/dist/{sandbox-files-PXPIZAFG.js → sandbox-files-AEJTWGT4.js} +8 -5
- package/dist/sandbox-files-AEJTWGT4.js.map +1 -0
- package/dist/session-ui.d.ts +9 -2
- package/dist/session-ui.js +39 -20
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +4 -4
- package/dist/sites.js +4 -289
- package/dist/sites.js.map +1 -1
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +31 -0
- package/dist/terminal.js.map +1 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/genie-loading.d.ts +3 -1
- package/dist/timeline/types.d.ts +8 -1
- package/dist/{timeline-annotation-selection-ODLVF4OI.js → timeline-annotation-selection-DMLMM2TX.js} +6 -1
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
- package/dist/timeline-pill-geometry-Z23V243M.js +110 -0
- package/dist/timeline-pill-geometry-Z23V243M.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +1073 -0
- package/dist/usage.js.map +1 -0
- package/package.json +19 -3
- package/src/artifacts.ts +37 -0
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +185 -0
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +3 -1
- package/src/components/artifacts/editable-artifact-view.tsx +187 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +34 -36
- package/src/components/artifacts/editable-presentation.tsx +21 -16
- package/src/components/artifacts/index.ts +41 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
- package/src/components/artifacts/site-view.tsx +116 -0
- package/src/components/browser-select-control.tsx +4 -1
- package/src/components/browser-viewer.tsx +81 -22
- package/src/components/code-editor.tsx +13 -16
- package/src/components/computer-viewer.tsx +51 -23
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/markdown-table-layout.ts +49 -5
- package/src/components/markdown.tsx +5 -2
- package/src/components/message-timeline.tsx +557 -516
- package/src/components/open-geni-links.tsx +24 -0
- package/src/components/sandbox-terminal.tsx +48 -11
- package/src/components/session-chrome.tsx +22 -3
- package/src/components/session-conversation.tsx +195 -102
- package/src/components/timeline-annotation-selection.tsx +7 -0
- package/src/components/timeline-pill-geometry.ts +169 -0
- package/src/components/timeline-pill-placement.ts +99 -0
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/conversation-timeline.ts +10 -2
- package/src/desktop.ts +24 -0
- package/src/editor.ts +13 -0
- package/src/hooks/use-available-models.ts +1 -1
- package/src/hooks/use-composer.ts +57 -2
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-usage.ts +100 -0
- package/src/index.ts +22 -0
- package/src/lib/format.ts +32 -6
- package/src/lib/workbench-peers.ts +62 -0
- package/src/session-ui.ts +22 -0
- package/src/terminal.ts +26 -0
- package/src/timeline/activity-rail.tsx +5 -3
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/genie-loading.tsx +16 -6
- package/src/timeline/projection.ts +78 -4
- package/src/timeline/types.ts +8 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +50 -0
- package/styles/compiled.css +325 -0
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-KVERXCFY.js.map +0 -1
- package/dist/chunk-L4OSXQ6H.js.map +0 -1
- package/dist/chunk-S5JQ4R22.js.map +0 -1
- package/dist/chunk-UDD6DLLT.js.map +0 -1
- package/dist/chunk-UGMKPUEM.js +0 -299
- package/dist/chunk-UGMKPUEM.js.map +0 -1
- package/dist/chunk-V2ABIVYJ.js.map +0 -1
- package/dist/chunk-X4HIN6TV.js.map +0 -1
- package/dist/chunk-XEJEOAOV.js.map +0 -1
- package/dist/chunk-YZ3YYMOM.js.map +0 -1
- package/dist/markdown-table-layout-PFUEZ2U4.js.map +0 -1
- package/dist/timeline-annotation-selection-ODLVF4OI.js.map +0 -1
- /package/dist/{browser-viewer-2E6R2Y4W.js.map → browser-viewer-Z7S7QEJL.js.map} +0 -0
- /package/dist/{computer-viewer-WMJE6OA3.js.map → chat-interactive-block-UGBTNB3L.js.map} +0 -0
- /package/dist/{chunk-Y3QTZN2F.js.map → chunk-2VFNCX4E.js.map} +0 -0
- /package/dist/{chunk-SEEVCT36.js.map → chunk-A4UJB2MM.js.map} +0 -0
- /package/dist/{chunk-5AZSK6MV.js.map → chunk-LWNLETQT.js.map} +0 -0
- /package/dist/{sandbox-files-PXPIZAFG.js.map → computer-viewer-KJWLH3GI.js.map} +0 -0
- /package/dist/{latest-question-QB4EUCW5.js.map → latest-question-IS445HOW.js.map} +0 -0
- /package/dist/{queue-surface-implementation-IM2HD3SK.js.map → queue-surface-implementation-2CAQAIHM.js.map} +0 -0
|
@@ -153,3 +153,27 @@ export function sessionLinkResolver(input: {
|
|
|
153
153
|
return null;
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
+
|
|
157
|
+
/** An editable artifact or Site to open in a host-mounted viewer. */
|
|
158
|
+
export type OpenGeniViewerTarget = Readonly<{
|
|
159
|
+
kind: "editable-artifact" | "site";
|
|
160
|
+
artifactId: string;
|
|
161
|
+
/** The link text the agent wrote, until the viewer loads the real title. */
|
|
162
|
+
title?: string | undefined;
|
|
163
|
+
}>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* "Open in host viewer": resolve agent links to editable artifacts and Sites
|
|
167
|
+
* in this workspace to `open(target)`, for example to mount
|
|
168
|
+
* `SessionArtifactViewer` beside the conversation. Other targets fall through.
|
|
169
|
+
*/
|
|
170
|
+
export function viewerLinkResolver(input: {
|
|
171
|
+
workspaceId: string;
|
|
172
|
+
open: (target: OpenGeniViewerTarget) => void;
|
|
173
|
+
}): OpenGeniLinkResolver {
|
|
174
|
+
return (target) => {
|
|
175
|
+
if (target.kind !== "editable-artifact" && target.kind !== "site") return null;
|
|
176
|
+
if (target.workspaceId !== input.workspaceId) return null;
|
|
177
|
+
return { open: () => input.open({ kind: target.kind, artifactId: target.artifactId }) };
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { TerminalCapability } from "@opengeni/sdk";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useEffect,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
useSyncExternalStore,
|
|
9
|
+
} from "react";
|
|
3
10
|
import { cn } from "../lib/cn";
|
|
4
11
|
import { type TerminalStreamStatus, useTerminalStream } from "../hooks/use-terminal-stream";
|
|
5
12
|
import type { UseSandboxTerminalResult } from "../hooks/use-sandbox-terminal";
|
|
@@ -7,6 +14,7 @@ import { resolveTerminalFont, xtermThemeFromTokens } from "../lib/xterm-theme";
|
|
|
7
14
|
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
8
15
|
import { terminalCanAcquirePty } from "../lib/terminal-capability";
|
|
9
16
|
import { attachRenderer, type RendererLoaders, type RendererTier } from "../lib/xterm-renderer";
|
|
17
|
+
import { sandboxTerminalPeers } from "../lib/workbench-peers";
|
|
10
18
|
|
|
11
19
|
/**
|
|
12
20
|
* A COMPLETE xterm `ITheme` (subset by intent, but every field xterm colors a
|
|
@@ -114,6 +122,19 @@ type XtermLike = {
|
|
|
114
122
|
};
|
|
115
123
|
};
|
|
116
124
|
type FitAddonLike = { fit: () => void };
|
|
125
|
+
type TerminalPeerBundle = {
|
|
126
|
+
Terminal: new (options: Record<string, unknown>) => XtermLike;
|
|
127
|
+
FitAddon: new () => FitAddonLike;
|
|
128
|
+
WebLinksAddon: new (handler: (event: MouseEvent, uri: string) => void) => unknown;
|
|
129
|
+
loadWebgl?:
|
|
130
|
+
| (() => Promise<{
|
|
131
|
+
WebglAddon: new () => {
|
|
132
|
+
dispose: () => void;
|
|
133
|
+
onContextLoss?: (callback: () => void) => void;
|
|
134
|
+
};
|
|
135
|
+
}>)
|
|
136
|
+
| undefined;
|
|
137
|
+
};
|
|
117
138
|
|
|
118
139
|
export type TerminalSurfaceState =
|
|
119
140
|
| "loading"
|
|
@@ -237,6 +258,12 @@ export function SandboxTerminal({
|
|
|
237
258
|
const wroteFirehoseRef = useRef(false);
|
|
238
259
|
const bootActiveRef = useRef(false);
|
|
239
260
|
const [ready, setReady] = useState(false);
|
|
261
|
+
const [loadError, setLoadError] = useState<Error | null>(null);
|
|
262
|
+
const peerRevision = useSyncExternalStore(
|
|
263
|
+
sandboxTerminalPeers.subscribe,
|
|
264
|
+
sandboxTerminalPeers.revision,
|
|
265
|
+
sandboxTerminalPeers.revision,
|
|
266
|
+
);
|
|
240
267
|
const [inputReady, setInputReady] = useState(false);
|
|
241
268
|
const [activated, setActivated] = useState(false);
|
|
242
269
|
|
|
@@ -353,12 +380,10 @@ export function SandboxTerminal({
|
|
|
353
380
|
|
|
354
381
|
ensureXtermBaseCss();
|
|
355
382
|
|
|
383
|
+
setLoadError(null);
|
|
356
384
|
void (async () => {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
import("@xterm/addon-fit"),
|
|
360
|
-
import("@xterm/addon-web-links"),
|
|
361
|
-
]);
|
|
385
|
+
const { Terminal, FitAddon, WebLinksAddon, loadWebgl } =
|
|
386
|
+
(await sandboxTerminalPeers.load()) as TerminalPeerBundle;
|
|
362
387
|
if (disposed) return;
|
|
363
388
|
await waitForSize(el);
|
|
364
389
|
if (disposed) return;
|
|
@@ -397,13 +422,17 @@ export function SandboxTerminal({
|
|
|
397
422
|
const failSet = forcedRendererFailures();
|
|
398
423
|
const loaders: RendererLoaders = {
|
|
399
424
|
webgl: async (onLoss) => {
|
|
425
|
+
if (disposed) throw new Error("Terminal was disposed");
|
|
400
426
|
if (failSet.has("webgl")) throw new Error("forced webgl failure");
|
|
401
|
-
|
|
427
|
+
if (!loadWebgl) throw new Error("WebGL terminal renderer is not enabled");
|
|
428
|
+
const { WebglAddon } = await loadWebgl();
|
|
429
|
+
if (disposed) throw new Error("Terminal was disposed");
|
|
402
430
|
const addon = new WebglAddon() as unknown as {
|
|
403
431
|
dispose: () => void;
|
|
404
432
|
onContextLoss?: (cb: () => void) => void;
|
|
405
433
|
};
|
|
406
434
|
addon.onContextLoss?.(() => {
|
|
435
|
+
if (disposed) return;
|
|
407
436
|
try {
|
|
408
437
|
addon.dispose();
|
|
409
438
|
} catch {
|
|
@@ -416,6 +445,7 @@ export function SandboxTerminal({
|
|
|
416
445
|
},
|
|
417
446
|
};
|
|
418
447
|
await attachRenderer("webgl", loaders, (tier) => {
|
|
448
|
+
if (disposed) return;
|
|
419
449
|
rendererRef.current = tier;
|
|
420
450
|
el.setAttribute("data-og-term-renderer", tier);
|
|
421
451
|
});
|
|
@@ -448,7 +478,9 @@ export function SandboxTerminal({
|
|
|
448
478
|
}
|
|
449
479
|
setReady(true);
|
|
450
480
|
});
|
|
451
|
-
})()
|
|
481
|
+
})().catch((cause: unknown) => {
|
|
482
|
+
if (!disposed) setLoadError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
483
|
+
});
|
|
452
484
|
|
|
453
485
|
return () => {
|
|
454
486
|
disposed = true;
|
|
@@ -461,9 +493,9 @@ export function SandboxTerminal({
|
|
|
461
493
|
bootActiveRef.current = false;
|
|
462
494
|
setReady(false);
|
|
463
495
|
};
|
|
464
|
-
// Mount
|
|
496
|
+
// Mount once per peer registration; runtime options sync without a remount.
|
|
465
497
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
466
|
-
}, []);
|
|
498
|
+
}, [peerRevision]);
|
|
467
499
|
|
|
468
500
|
// Sync stdin/cursor at runtime (NOT a remount) when interactivity flips, so the
|
|
469
501
|
// projection→PTY handoff keeps the single Terminal instance + its scrollback.
|
|
@@ -621,7 +653,12 @@ export function SandboxTerminal({
|
|
|
621
653
|
onPointerDownCapture={handleActivate}
|
|
622
654
|
onFocusCapture={handleActivate}
|
|
623
655
|
>
|
|
624
|
-
{!ready &&
|
|
656
|
+
{!ready &&
|
|
657
|
+
(loadError ? (
|
|
658
|
+
<p role="alert">{loadError.message}</p>
|
|
659
|
+
) : (
|
|
660
|
+
(placeholder ?? <TerminalPlaceholder />)
|
|
661
|
+
))}
|
|
625
662
|
{/* Kept `visibility:hidden` until the first successful fit so the first
|
|
626
663
|
VISIBLE frame is already correctly sized (no 80×24 flash — E4). */}
|
|
627
664
|
<div
|
|
@@ -826,6 +826,7 @@ export function SessionChrome({
|
|
|
826
826
|
onRefresh={queue.refresh}
|
|
827
827
|
onClearMutationError={queue.clearMutationError}
|
|
828
828
|
onRetryOptimistic={composer?.retryOptimisticMessage}
|
|
829
|
+
onRestoreOptimistic={composer?.restoreOptimisticMessage}
|
|
829
830
|
onRemoveOptimistic={composer?.removeOptimisticMessage}
|
|
830
831
|
readOnly={!canMutateQueue}
|
|
831
832
|
mutationFor={queue.mutationFor}
|
|
@@ -1333,6 +1334,7 @@ function QueuePanel({
|
|
|
1333
1334
|
onRefresh,
|
|
1334
1335
|
onClearMutationError,
|
|
1335
1336
|
onRetryOptimistic,
|
|
1337
|
+
onRestoreOptimistic,
|
|
1336
1338
|
onRemoveOptimistic,
|
|
1337
1339
|
readOnly,
|
|
1338
1340
|
mutationFor,
|
|
@@ -1351,6 +1353,7 @@ function QueuePanel({
|
|
|
1351
1353
|
onRefresh: () => Promise<void>;
|
|
1352
1354
|
onClearMutationError: () => void;
|
|
1353
1355
|
onRetryOptimistic?: ((clientEventId: string) => void) | undefined;
|
|
1356
|
+
onRestoreOptimistic?: ((clientEventId: string) => void) | undefined;
|
|
1354
1357
|
onRemoveOptimistic?: ((clientEventId: string) => void) | undefined;
|
|
1355
1358
|
readOnly: boolean;
|
|
1356
1359
|
mutationFor: UseTurnQueueResult["mutationFor"];
|
|
@@ -1536,17 +1539,24 @@ function QueuePanel({
|
|
|
1536
1539
|
<span className="shrink-0 font-og-mono text-[10px] leading-4 text-og-fg-subtle">
|
|
1537
1540
|
{turns.length + index + 1}
|
|
1538
1541
|
</span>
|
|
1539
|
-
<
|
|
1542
|
+
<div className="min-w-0 flex-1">
|
|
1543
|
+
<CompactQueueItemContent text={message.text} annotations={message.annotations} />
|
|
1544
|
+
{message.state === "failed" && message.error ? (
|
|
1545
|
+
<p className="break-words text-og-xs">{message.error}</p>
|
|
1546
|
+
) : null}
|
|
1547
|
+
</div>
|
|
1540
1548
|
<span className="sr-only">
|
|
1541
1549
|
{message.state === "failed"
|
|
1542
|
-
?
|
|
1550
|
+
? message.outcomeUnknown
|
|
1551
|
+
? "Not confirmed"
|
|
1552
|
+
: "Message not sent"
|
|
1543
1553
|
: message.state === "sending"
|
|
1544
1554
|
? "Placing in queue"
|
|
1545
1555
|
: "Queued"}
|
|
1546
1556
|
</span>
|
|
1547
1557
|
{message.state === "failed" ? (
|
|
1548
1558
|
<div className="flex shrink-0 items-center gap-1 text-[10px]">
|
|
1549
|
-
{onRetryOptimistic ? (
|
|
1559
|
+
{message.retryable !== false && onRetryOptimistic ? (
|
|
1550
1560
|
<button
|
|
1551
1561
|
type="button"
|
|
1552
1562
|
className="rounded-og-sm px-1.5 py-1 font-medium hover:bg-og-surface-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11"
|
|
@@ -1555,6 +1565,15 @@ function QueuePanel({
|
|
|
1555
1565
|
Retry
|
|
1556
1566
|
</button>
|
|
1557
1567
|
) : null}
|
|
1568
|
+
{message.retryable === false && onRestoreOptimistic ? (
|
|
1569
|
+
<button
|
|
1570
|
+
type="button"
|
|
1571
|
+
className="rounded-og-sm px-1.5 py-1 font-medium hover:bg-og-surface-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11"
|
|
1572
|
+
onClick={() => onRestoreOptimistic(message.clientEventId)}
|
|
1573
|
+
>
|
|
1574
|
+
Edit message
|
|
1575
|
+
</button>
|
|
1576
|
+
) : null}
|
|
1558
1577
|
{onRemoveOptimistic ? (
|
|
1559
1578
|
<button
|
|
1560
1579
|
type="button"
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
1
|
+
import { SESSION_SCOPE_HEADER, type SendMessageInput } from "@opengeni/sdk";
|
|
2
|
+
import {
|
|
3
|
+
lazy,
|
|
4
|
+
Suspense,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type CSSProperties,
|
|
11
|
+
} from "react";
|
|
12
|
+
import type { SiteSnapshotClient } from "./artifacts/chat-interactive-block";
|
|
3
13
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
4
14
|
import { useWorkspaceModelCatalog } from "../hooks/use-available-models";
|
|
5
15
|
import { ModelPolicyPicker } from "./model-policy-picker";
|
|
@@ -20,7 +30,9 @@ import {
|
|
|
20
30
|
chainLinkResolvers,
|
|
21
31
|
sessionLinkResolver,
|
|
22
32
|
useOpenGeniLinkResolver,
|
|
33
|
+
viewerLinkResolver,
|
|
23
34
|
type OpenGeniLinkResolver,
|
|
35
|
+
type OpenGeniViewerTarget,
|
|
24
36
|
} from "./open-geni-links";
|
|
25
37
|
import type { UserMessageDisclosureLabels } from "./user-message-body";
|
|
26
38
|
import { conversationTimeline } from "../conversation-timeline";
|
|
@@ -37,8 +49,27 @@ export type SessionConversationProps = ClientOverride & {
|
|
|
37
49
|
* editable artifacts and Sites stay unavailable until the host resolves them.
|
|
38
50
|
*/
|
|
39
51
|
resolveLink?: OpenGeniLinkResolver | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Open agent links to editable artifacts and Sites in a host viewer, for
|
|
54
|
+
* example `SessionArtifactViewer` mounted beside the conversation. Asked
|
|
55
|
+
* after `resolveLink`.
|
|
56
|
+
*/
|
|
57
|
+
onOpenArtifact?: ((target: OpenGeniViewerTarget) => void) | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Inline previews for assistant `opengeni-site` / `opengeni-html` fences.
|
|
60
|
+
* Defaults to the OpenGeni preview (Site reads need the proxy's
|
|
61
|
+
* `artifacts` option); `false` shows the fence as code.
|
|
62
|
+
*/
|
|
63
|
+
renderInteractiveBlock?: MessageTimelineProps["renderInteractiveBlock"] | false;
|
|
40
64
|
/** Product-specific tool-call renderers; defaults to the built-in registry. */
|
|
41
65
|
toolRegistry?: MessageTimelineProps["toolRegistry"];
|
|
66
|
+
/**
|
|
67
|
+
* Replace the "usage limit reached" row for an `allowance_exhausted`
|
|
68
|
+
* refusal, for example to link your own plan or admin page.
|
|
69
|
+
*/
|
|
70
|
+
renderAllowanceExhausted?: MessageTimelineProps["renderAllowanceExhausted"];
|
|
71
|
+
/** Replace the words of the default "usage limit reached" row. */
|
|
72
|
+
allowanceExhaustedLabels?: MessageTimelineProps["allowanceExhaustedLabels"];
|
|
42
73
|
/**
|
|
43
74
|
* File attachments in the composer. Defaults to true; the attach control
|
|
44
75
|
* appears only when the deployment's client config enables file uploads.
|
|
@@ -72,7 +103,11 @@ function Conversation({
|
|
|
72
103
|
sessionId,
|
|
73
104
|
renderMessageText,
|
|
74
105
|
resolveLink,
|
|
106
|
+
onOpenArtifact,
|
|
107
|
+
renderInteractiveBlock,
|
|
75
108
|
toolRegistry,
|
|
109
|
+
renderAllowanceExhausted,
|
|
110
|
+
allowanceExhaustedLabels,
|
|
76
111
|
attachments: attachmentsRequested = true,
|
|
77
112
|
modelPicker,
|
|
78
113
|
userMessageDisclosureLabels,
|
|
@@ -85,8 +120,6 @@ function Conversation({
|
|
|
85
120
|
}: SessionConversationProps) {
|
|
86
121
|
const scope = { client, workspaceId };
|
|
87
122
|
const context = useOpenGeni(scope);
|
|
88
|
-
const scopeRef = useRef(context);
|
|
89
|
-
scopeRef.current = context;
|
|
90
123
|
const config = useClientConfigFlags(context.client);
|
|
91
124
|
const showModelPicker = modelPicker ?? config.modelSelection;
|
|
92
125
|
const catalog = useWorkspaceModelCatalog({
|
|
@@ -98,10 +131,6 @@ function Conversation({
|
|
|
98
131
|
const options = { ...scope, events: feed.events };
|
|
99
132
|
const detail = useSession(sessionId, options);
|
|
100
133
|
const queue = useTurnQueue(sessionId, options);
|
|
101
|
-
const [queueFocusTarget, setQueueFocusTarget] = useState<{
|
|
102
|
-
turnId: string;
|
|
103
|
-
requestId: number;
|
|
104
|
-
}>();
|
|
105
134
|
const human = useHumanInputRequests(sessionId, options);
|
|
106
135
|
const control = useSessionControl(sessionId, scope);
|
|
107
136
|
const approvals = useMemo(() => projectPendingApprovals(feed.events), [feed.events]);
|
|
@@ -109,6 +138,7 @@ function Conversation({
|
|
|
109
138
|
const uploadsEnabled = attachmentsRequested && config.uploads;
|
|
110
139
|
const status = feed.sessionStatus ?? detail.session?.status;
|
|
111
140
|
const terminal = status === "cancelled";
|
|
141
|
+
const importedArchive = detail.session?.importedArchive?.readOnly === true;
|
|
112
142
|
const releaseSentFiles = (input: SendMessageInput) =>
|
|
113
143
|
files.removeReadyFiles(
|
|
114
144
|
(input.resources ?? []).flatMap((resource) =>
|
|
@@ -129,6 +159,11 @@ function Conversation({
|
|
|
129
159
|
: {}),
|
|
130
160
|
});
|
|
131
161
|
const region = useRef<HTMLDivElement>(null);
|
|
162
|
+
const defaultInteractiveBlock = useDefaultInteractiveBlock(
|
|
163
|
+
context.client,
|
|
164
|
+
context.workspaceId,
|
|
165
|
+
sessionId,
|
|
166
|
+
);
|
|
132
167
|
const inheritedLinks = useOpenGeniLinkResolver();
|
|
133
168
|
const defaultLinks = useMemo(
|
|
134
169
|
() =>
|
|
@@ -140,9 +175,22 @@ function Conversation({
|
|
|
140
175
|
}),
|
|
141
176
|
[context.client, context.workspaceId, sessionId, config.sandboxFiles],
|
|
142
177
|
);
|
|
178
|
+
const onOpenArtifactRef = useRef(onOpenArtifact);
|
|
179
|
+
onOpenArtifactRef.current = onOpenArtifact;
|
|
180
|
+
const opensArtifacts = onOpenArtifact !== undefined;
|
|
181
|
+
const viewerLinks = useMemo(
|
|
182
|
+
() =>
|
|
183
|
+
opensArtifacts
|
|
184
|
+
? viewerLinkResolver({
|
|
185
|
+
workspaceId: context.workspaceId,
|
|
186
|
+
open: (target) => onOpenArtifactRef.current?.(target),
|
|
187
|
+
})
|
|
188
|
+
: null,
|
|
189
|
+
[context.workspaceId, opensArtifacts],
|
|
190
|
+
);
|
|
143
191
|
const links = useMemo(
|
|
144
|
-
() => chainLinkResolvers(resolveLink, inheritedLinks, defaultLinks) ?? undefined,
|
|
145
|
-
[resolveLink, inheritedLinks, defaultLinks],
|
|
192
|
+
() => chainLinkResolvers(resolveLink, viewerLinks, inheritedLinks, defaultLinks) ?? undefined,
|
|
193
|
+
[resolveLink, viewerLinks, inheritedLinks, defaultLinks],
|
|
146
194
|
);
|
|
147
195
|
const error = detail.error ?? feed.error ?? human.error;
|
|
148
196
|
return (
|
|
@@ -159,7 +207,14 @@ function Conversation({
|
|
|
159
207
|
<MessageTimeline
|
|
160
208
|
renderMessageText={renderMessageText}
|
|
161
209
|
resolveLink={links}
|
|
210
|
+
renderInteractiveBlock={
|
|
211
|
+
renderInteractiveBlock === false
|
|
212
|
+
? undefined
|
|
213
|
+
: (renderInteractiveBlock ?? defaultInteractiveBlock)
|
|
214
|
+
}
|
|
162
215
|
userMessageDisclosureLabels={userMessageDisclosureLabels}
|
|
216
|
+
renderAllowanceExhausted={renderAllowanceExhausted}
|
|
217
|
+
allowanceExhaustedLabels={allowanceExhaustedLabels}
|
|
163
218
|
className="min-h-0 flex-1"
|
|
164
219
|
{...(toolRegistry ? { toolRegistry } : {})}
|
|
165
220
|
events={feed.events}
|
|
@@ -177,104 +232,142 @@ function Conversation({
|
|
|
177
232
|
}}
|
|
178
233
|
loadingOldest={feed.loadingOldest}
|
|
179
234
|
onJumpToLatest={feed.jumpToLatest}
|
|
180
|
-
|
|
181
|
-
feed.jumpToLatestQuestion({
|
|
182
|
-
onQueuedQuestion: async (turn, navigation) => {
|
|
183
|
-
await queue.refresh();
|
|
184
|
-
if (
|
|
185
|
-
!navigation.isCurrent() ||
|
|
186
|
-
scopeRef.current.client !== context.client ||
|
|
187
|
-
scopeRef.current.workspaceId !== context.workspaceId
|
|
188
|
-
)
|
|
189
|
-
return;
|
|
190
|
-
setQueueFocusTarget((previous) => ({
|
|
191
|
-
turnId: turn.id,
|
|
192
|
-
requestId: (previous?.requestId ?? 0) + 1,
|
|
193
|
-
}));
|
|
194
|
-
},
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
onAnnotate={composer.addAnnotation}
|
|
235
|
+
onAnnotate={importedArchive ? undefined : composer.addAnnotation}
|
|
198
236
|
/>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<div className="shrink-0" data-og-conversation-composer="">
|
|
244
|
-
<ChatComposer
|
|
245
|
-
{...composerProps}
|
|
246
|
-
composer={composer}
|
|
247
|
-
attachments={uploadsEnabled ? files : undefined}
|
|
248
|
-
disabled={terminal || composerProps?.disabled}
|
|
249
|
-
controlsStart={
|
|
250
|
-
composerProps?.controlsStart ??
|
|
251
|
-
(showModelPicker && composer.policy && (
|
|
252
|
-
<ModelPolicyPicker
|
|
253
|
-
rows={catalog.rows}
|
|
254
|
-
model={composer.policy.model}
|
|
255
|
-
effort={composer.policy.reasoningEffort}
|
|
256
|
-
latencyMode={composer.policy.latencyMode}
|
|
257
|
-
loading={catalog.loading}
|
|
258
|
-
error={catalog.error?.message}
|
|
259
|
-
disabled={terminal}
|
|
260
|
-
sessionKey={sessionId}
|
|
261
|
-
onModelChange={(model) => composer.setModel?.(model)}
|
|
262
|
-
onEffortChange={(effort) => composer.setReasoningEffort?.(effort)}
|
|
263
|
-
onLatencyModeChange={(mode) => composer.setLatencyMode?.(mode)}
|
|
237
|
+
{importedArchive ? (
|
|
238
|
+
<p className="shrink-0 px-4 py-2 text-center text-sm text-og-muted" role="status">
|
|
239
|
+
Archived conversation · Read only
|
|
240
|
+
</p>
|
|
241
|
+
) : (
|
|
242
|
+
<>
|
|
243
|
+
<div
|
|
244
|
+
className="min-h-0 max-h-[40%] shrink-0 overflow-y-auto"
|
|
245
|
+
data-og-conversation-inputs=""
|
|
246
|
+
>
|
|
247
|
+
{approvals.length > 0 && !terminal ? (
|
|
248
|
+
<ApprovalSurface
|
|
249
|
+
className="mx-auto max-w-3xl"
|
|
250
|
+
approvals={approvals}
|
|
251
|
+
onApprove={async (approval) => {
|
|
252
|
+
await control.approve(approval.id);
|
|
253
|
+
}}
|
|
254
|
+
onReject={async (approval) => {
|
|
255
|
+
await control.reject(approval.id);
|
|
256
|
+
}}
|
|
257
|
+
responding={control.responding}
|
|
258
|
+
error={control.error}
|
|
259
|
+
/>
|
|
260
|
+
) : null}
|
|
261
|
+
<HumanInputSurface
|
|
262
|
+
loadSkillReview={loadSkillReview}
|
|
263
|
+
requests={human.requests}
|
|
264
|
+
onSubmit={async (id, response) => {
|
|
265
|
+
await human.respond(id, response);
|
|
266
|
+
}}
|
|
267
|
+
respondingRequestId={human.respondingRequestId}
|
|
268
|
+
error={human.mutationError?.message}
|
|
269
|
+
autoFocus={false}
|
|
270
|
+
/>
|
|
271
|
+
{terminal ? (
|
|
272
|
+
<SessionChrome queue={queue} sessionStatus={status} readOnly />
|
|
273
|
+
) : (
|
|
274
|
+
<SessionChrome
|
|
275
|
+
queue={queue}
|
|
276
|
+
composer={composer}
|
|
277
|
+
sessionStatus={status}
|
|
278
|
+
onComposerFocus={() =>
|
|
279
|
+
region.current?.querySelector<HTMLTextAreaElement>("textarea")?.focus()
|
|
280
|
+
}
|
|
264
281
|
/>
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
<div className="shrink-0" data-og-conversation-composer="">
|
|
285
|
+
<ChatComposer
|
|
286
|
+
{...composerProps}
|
|
287
|
+
composer={composer}
|
|
288
|
+
attachments={uploadsEnabled ? files : undefined}
|
|
289
|
+
disabled={terminal || composerProps?.disabled}
|
|
290
|
+
controlsStart={
|
|
291
|
+
composerProps?.controlsStart ??
|
|
292
|
+
(showModelPicker && composer.policy && (
|
|
293
|
+
<ModelPolicyPicker
|
|
294
|
+
rows={catalog.rows}
|
|
295
|
+
model={composer.policy.model}
|
|
296
|
+
effort={composer.policy.reasoningEffort}
|
|
297
|
+
latencyMode={composer.policy.latencyMode}
|
|
298
|
+
loading={catalog.loading}
|
|
299
|
+
error={catalog.error?.message}
|
|
300
|
+
disabled={terminal}
|
|
301
|
+
sessionKey={sessionId}
|
|
302
|
+
onModelChange={(model) => composer.setModel?.(model)}
|
|
303
|
+
onEffortChange={(effort) => composer.setReasoningEffort?.(effort)}
|
|
304
|
+
onLatencyModeChange={(mode) => composer.setLatencyMode?.(mode)}
|
|
305
|
+
/>
|
|
306
|
+
))
|
|
307
|
+
}
|
|
308
|
+
responsiveBasis={composerProps?.responsiveBasis ?? "container"}
|
|
309
|
+
effectiveControl={
|
|
310
|
+
composer.effectiveControl ??
|
|
311
|
+
queue.effectiveControl ??
|
|
312
|
+
detail.session?.effectiveControl
|
|
313
|
+
}
|
|
314
|
+
queuedAheadCount={queue.queue.length}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
</>
|
|
318
|
+
)}
|
|
274
319
|
</div>
|
|
275
320
|
);
|
|
276
321
|
}
|
|
277
322
|
|
|
323
|
+
const LazyChatInteractiveBlock = lazy(() =>
|
|
324
|
+
import("./artifacts/chat-interactive-block").then((module) => ({
|
|
325
|
+
default: module.ChatInteractiveBlock,
|
|
326
|
+
})),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
type ScopableClient = SiteSnapshotClient & {
|
|
330
|
+
withHeaders?: (headers: Readonly<Record<string, string>>) => SiteSnapshotClient;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/** Inline Site/HTML preview reading through this conversation's session scope. */
|
|
334
|
+
function useDefaultInteractiveBlock(
|
|
335
|
+
client: unknown,
|
|
336
|
+
workspaceId: string,
|
|
337
|
+
sessionId: string,
|
|
338
|
+
): NonNullable<MessageTimelineProps["renderInteractiveBlock"]> {
|
|
339
|
+
// Scope lazily: only a rendered preview reads, and some clients (a Site's
|
|
340
|
+
// own client) cannot add headers.
|
|
341
|
+
const scoped = useMemo((): SiteSnapshotClient => {
|
|
342
|
+
let resolved: SiteSnapshotClient | null = null;
|
|
343
|
+
const get = () => {
|
|
344
|
+
const candidate = client as ScopableClient;
|
|
345
|
+
resolved ??=
|
|
346
|
+
typeof candidate.withHeaders === "function"
|
|
347
|
+
? candidate.withHeaders({ [SESSION_SCOPE_HEADER]: sessionId })
|
|
348
|
+
: candidate;
|
|
349
|
+
return resolved;
|
|
350
|
+
};
|
|
351
|
+
return {
|
|
352
|
+
getWorkspaceArtifact: (...args) => get().getWorkspaceArtifact(...args),
|
|
353
|
+
getWorkspaceArtifactHtml: (...args) => get().getWorkspaceArtifactHtml(...args),
|
|
354
|
+
getWorkspaceArtifactContent: (...args) => {
|
|
355
|
+
const target = get();
|
|
356
|
+
if (!target.getWorkspaceArtifactContent) throw new Error("Site version unavailable");
|
|
357
|
+
return target.getWorkspaceArtifactContent(...args);
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}, [client, sessionId]);
|
|
361
|
+
return useCallback(
|
|
362
|
+
(block) => (
|
|
363
|
+
<Suspense fallback={<span role="status">Loading preview…</span>}>
|
|
364
|
+
<LazyChatInteractiveBlock workspaceId={workspaceId} client={scoped} {...block} />
|
|
365
|
+
</Suspense>
|
|
366
|
+
),
|
|
367
|
+
[scoped, workspaceId],
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
278
371
|
/** Deployment/proxy flags from the client config: uploads, and whether model choice is open. */
|
|
279
372
|
function useClientConfigFlags(client: {
|
|
280
373
|
getClientConfig: () => Promise<{
|
|
@@ -163,6 +163,7 @@ export function TimelineAnnotationSelection({
|
|
|
163
163
|
}) {
|
|
164
164
|
const [candidate, setCandidate] = useState<SelectionCandidate | null>(null);
|
|
165
165
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
166
|
+
const initialSelectionObservedRef = useRef(false);
|
|
166
167
|
const pointerOriginRef = useRef<"unset" | "inside" | "outside">("unset");
|
|
167
168
|
const portalStyle = usePortalTokenStyle(rootRef.current);
|
|
168
169
|
|
|
@@ -175,6 +176,12 @@ export function TimelineAnnotationSelection({
|
|
|
175
176
|
const update = (keyboard: boolean) => {
|
|
176
177
|
setCandidate(buildCandidate(root, sources, keyboard));
|
|
177
178
|
};
|
|
179
|
+
// A user may finish selecting text before this lazy component arrives.
|
|
180
|
+
// Reconcile once; later prop changes must not revive dismissed selections.
|
|
181
|
+
if (!initialSelectionObservedRef.current) {
|
|
182
|
+
initialSelectionObservedRef.current = true;
|
|
183
|
+
update(false);
|
|
184
|
+
}
|
|
178
185
|
const onRootPointerUp = (event: PointerEvent) => {
|
|
179
186
|
if (event.target instanceof Node && buttonRef.current?.contains(event.target)) return;
|
|
180
187
|
// Tests may fire only pointerup on the source. A real click that started
|