@opengeni/react 6.1.0 → 7.0.0-canary.35585322809001
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 +83 -2
- package/dist/artifacts-document.js +1 -1
- package/dist/artifacts-presentation.js +1 -1
- package/dist/artifacts-spreadsheet.js +1 -1
- package/dist/artifacts.js +3 -3
- package/dist/{browser-viewer-NTD6UIPR.js → browser-viewer-LGH4IP47.js} +3 -3
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/{chunk-NOSYBS2D.js → chunk-2VHARTBL.js} +2 -1
- package/dist/{chunk-VRBL3F5E.js → chunk-5GAL23FF.js} +10 -2
- package/dist/{chunk-VRBL3F5E.js.map → chunk-5GAL23FF.js.map} +1 -1
- package/dist/chunk-5UONOQ3Y.js +635 -0
- package/dist/chunk-5UONOQ3Y.js.map +1 -0
- package/dist/{chunk-NYESIADM.js → chunk-AKOMC3VO.js} +3 -3
- package/dist/chunk-AKOMC3VO.js.map +1 -0
- package/dist/chunk-BRM6FVZA.js +302 -0
- package/dist/chunk-BRM6FVZA.js.map +1 -0
- package/dist/{chunk-TDSEILUH.js → chunk-D5NOQPLR.js} +1289 -1530
- package/dist/chunk-D5NOQPLR.js.map +1 -0
- package/dist/chunk-D7GAPJ3V.js +1558 -0
- package/dist/chunk-D7GAPJ3V.js.map +1 -0
- package/dist/{chunk-5M6VTFJ5.js → chunk-GBPCZH3Y.js} +2 -2
- package/dist/{chunk-YBM6CQRU.js → chunk-GEN54DU5.js} +4 -4
- package/dist/{chunk-UMECRJRV.js → chunk-HJ5VJ4JB.js} +21 -12
- package/dist/chunk-HJ5VJ4JB.js.map +1 -0
- package/dist/{chunk-R4KRSFGA.js → chunk-IP4BUBKH.js} +3 -3
- package/dist/{chunk-KANFU34B.js → chunk-IYUPN32E.js} +6 -2
- package/dist/chunk-IYUPN32E.js.map +1 -0
- package/dist/{chunk-L2NBTKRX.js → chunk-JKLGJQO5.js} +193 -12
- package/dist/chunk-JKLGJQO5.js.map +1 -0
- package/dist/{chunk-NLTS7JHI.js → chunk-JPHDWKZE.js} +5 -5
- package/dist/{chunk-ICA6R6TW.js → chunk-OWHPKAOV.js} +28 -20
- package/dist/chunk-OWHPKAOV.js.map +1 -0
- package/dist/{chunk-24M4YBCL.js → chunk-PNAVEK2U.js} +4 -4
- package/dist/{chunk-45XEUVTK.js → chunk-UXE5SSNN.js} +293 -463
- package/dist/chunk-UXE5SSNN.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-VR4Z73EU.js} +203 -9
- package/dist/chunk-VR4Z73EU.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-XMXRI5JK.js} +65 -18
- package/dist/chunk-XMXRI5JK.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/chat-composer.d.ts +13 -3
- package/dist/components/markdown.d.ts +4 -3
- package/dist/components/message-timeline.d.ts +7 -2
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/session-capability-frame.d.ts +24 -0
- package/dist/components/session-chrome.d.ts +1 -2
- package/dist/components/session-commands.d.ts +6 -0
- package/dist/components/session-connection-request.d.ts +8 -0
- package/dist/components/session-mcp-capability-card.d.ts +22 -0
- package/dist/components/timeline-search.d.ts +34 -0
- package/dist/composer.js +7 -7
- package/dist/connect-account-identity.d.ts +8 -0
- package/dist/connect-accounts.d.ts +5 -0
- package/dist/connect-chooser.d.ts +6 -0
- package/dist/connect-panel.d.ts +9 -2
- package/dist/connect-setup.d.ts +5 -1
- package/dist/connect.d.ts +5 -21
- package/dist/connect.js +1268 -584
- package/dist/connect.js.map +1 -1
- package/dist/connection-catalog.d.ts +6 -1
- package/dist/connection-discovery.d.ts +12 -0
- package/dist/connection-installed.d.ts +1 -0
- package/dist/connection-logo.d.ts +5 -2
- package/dist/connection-service-logo.d.ts +8 -0
- package/dist/connection-service-presentation.d.ts +13 -0
- package/dist/connection-skeleton.d.ts +5 -0
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/hooks/use-codex-accounts.d.ts +11 -5
- package/dist/hooks/use-connect.d.ts +22 -0
- package/dist/hooks/use-session-events.d.ts +5 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-workspace-sessions.d.ts +2 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +771 -793
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/machines.js +2 -2
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/{model-policy-picker-menu-3R336DP2.js → model-policy-picker-menu-RVOQ4ZZG.js} +4 -4
- package/dist/model-policy.d.ts +6 -2
- package/dist/model-policy.js +1 -1
- package/dist/{platform-activity-row-DUE2W74J.js → platform-activity-row-HLOH7FW4.js} +9 -4
- package/dist/platform-activity-row-HLOH7FW4.js.map +1 -0
- package/dist/plugin-discovery.d.ts +6 -0
- package/dist/{queue-surface-implementation-LK3S4BIW.js → queue-surface-implementation-3RIFK2BS.js} +4 -4
- package/dist/realtime.js +22 -22
- package/dist/realtime.js.map +1 -1
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +12 -0
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +14 -12
- package/dist/session.d.ts +1 -0
- package/dist/session.js +6 -4
- package/dist/skill-discovery.d.ts +11 -0
- package/dist/timeline/compaction-copy.d.ts +10 -0
- package/dist/timeline/projection.d.ts +3 -1
- package/dist/timeline/turn-summary.d.ts +2 -2
- package/dist/timeline/types.d.ts +22 -1
- package/dist/{timeline-annotation-markers-DRSTLEM6.js → timeline-annotation-markers-OY6OWR3V.js} +2 -2
- package/dist/{timeline-annotation-selection-IHLUGVND.js → timeline-annotation-selection-BOXA2TIN.js} +2 -2
- package/dist/{timeline-annotations-dialog-6Q53N5FG.js → timeline-annotations-dialog-RGY3MHL3.js} +3 -3
- package/package.json +3 -3
- package/src/assets/README.md +8 -0
- package/src/assets/gmail.ico +0 -0
- package/src/assets/outlook.ico +0 -0
- package/src/capability-catalog-row.tsx +94 -0
- package/src/capability-logo-fallback.ts +22 -0
- package/src/client.ts +0 -9
- package/src/components/artifacts/document-editor.tsx +63 -6
- package/src/components/artifacts/presentation-editor.tsx +214 -9
- package/src/components/artifacts/spreadsheet-canvas.ts +3 -8
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +10 -11
- package/src/components/chat-composer.tsx +26 -3
- package/src/components/composer.tsx +10 -4
- package/src/components/human-input-form.tsx +12 -2
- package/src/components/markdown.tsx +86 -4
- package/src/components/message-timeline.tsx +104 -36
- package/src/components/model-picker.tsx +11 -3
- package/src/components/model-policy-picker-menu.tsx +10 -3
- package/src/components/preview-loading.tsx +154 -0
- package/src/components/sandbox-workspace.tsx +20 -36
- package/src/components/session-capability-frame.tsx +163 -0
- package/src/components/session-chrome.tsx +46 -77
- package/src/components/session-commands.tsx +18 -0
- package/src/components/session-connection-request.tsx +97 -0
- package/src/components/session-conversation.tsx +5 -4
- package/src/components/session-mcp-capability-card.tsx +532 -0
- package/src/components/session-status.tsx +2 -2
- package/src/components/timeline-anchor.tsx +9 -8
- package/src/components/timeline-search.tsx +168 -0
- package/src/components/user-message-body.tsx +7 -0
- package/src/connect-account-identity.tsx +34 -0
- package/src/connect-accounts.tsx +114 -52
- package/src/connect-chooser.tsx +171 -39
- package/src/connect-panel.tsx +73 -9
- package/src/connect-setup.tsx +72 -25
- package/src/connect.ts +13 -22
- package/src/connection-assets.d.ts +4 -0
- package/src/connection-catalog.tsx +61 -39
- package/src/connection-discovery.tsx +235 -0
- package/src/connection-installed.tsx +2 -0
- package/src/connection-logo.tsx +18 -10
- package/src/connection-service-logo.tsx +55 -0
- package/src/connection-service-presentation.ts +46 -0
- package/src/connection-skeleton.tsx +16 -0
- package/src/conversation-timeline.ts +1 -0
- package/src/discovery-cache.ts +42 -0
- package/src/hooks/use-codex-accounts.ts +33 -14
- package/src/hooks/use-composer.ts +4 -5
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-session-events.ts +114 -1
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-workspace-sessions.ts +10 -0
- package/src/index.ts +22 -2
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-policy.ts +21 -0
- package/src/plugin-details.tsx +8 -5
- package/src/plugin-discovery.tsx +92 -72
- package/src/realtime/realtime-control.tsx +6 -8
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +83 -0
- package/src/session-ui.ts +2 -0
- package/src/session.ts +1 -0
- package/src/skill-discovery.tsx +101 -56
- package/src/timeline/compaction-copy.ts +40 -0
- package/src/timeline/platform-activity-row.tsx +8 -3
- package/src/timeline/projection.ts +74 -2
- package/src/timeline/shared.tsx +6 -1
- package/src/timeline/startup-timings.tsx +1 -1
- package/src/timeline/turn-summary.tsx +17 -3
- package/src/timeline/types.ts +22 -2
- package/styles/compiled.css +325 -27
- package/styles/connect.css +90 -15
- package/styles/index.css +46 -0
- package/styles/session-capability.css +42 -0
- package/dist/chunk-45XEUVTK.js.map +0 -1
- package/dist/chunk-HQV2I5HV.js +0 -124
- package/dist/chunk-HQV2I5HV.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-IHQII5WO.js +0 -886
- package/dist/chunk-IHQII5WO.js.map +0 -1
- package/dist/chunk-KANFU34B.js.map +0 -1
- package/dist/chunk-L2NBTKRX.js.map +0 -1
- package/dist/chunk-NYESIADM.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-TDSEILUH.js.map +0 -1
- package/dist/chunk-UMECRJRV.js.map +0 -1
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/platform-activity-row-DUE2W74J.js.map +0 -1
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{browser-viewer-NTD6UIPR.js.map → browser-viewer-LGH4IP47.js.map} +0 -0
- /package/dist/{chunk-NOSYBS2D.js.map → chunk-2VHARTBL.js.map} +0 -0
- /package/dist/{chunk-5M6VTFJ5.js.map → chunk-GBPCZH3Y.js.map} +0 -0
- /package/dist/{chunk-YBM6CQRU.js.map → chunk-GEN54DU5.js.map} +0 -0
- /package/dist/{chunk-R4KRSFGA.js.map → chunk-IP4BUBKH.js.map} +0 -0
- /package/dist/{chunk-NLTS7JHI.js.map → chunk-JPHDWKZE.js.map} +0 -0
- /package/dist/{chunk-24M4YBCL.js.map → chunk-PNAVEK2U.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-3R336DP2.js.map → model-policy-picker-menu-RVOQ4ZZG.js.map} +0 -0
- /package/dist/{queue-surface-implementation-LK3S4BIW.js.map → queue-surface-implementation-3RIFK2BS.js.map} +0 -0
- /package/dist/{timeline-annotation-markers-DRSTLEM6.js.map → timeline-annotation-markers-OY6OWR3V.js.map} +0 -0
- /package/dist/{timeline-annotation-selection-IHLUGVND.js.map → timeline-annotation-selection-BOXA2TIN.js.map} +0 -0
- /package/dist/{timeline-annotations-dialog-6Q53N5FG.js.map → timeline-annotations-dialog-RGY3MHL3.js.map} +0 -0
|
@@ -19,15 +19,19 @@ import ReactMarkdown, {
|
|
|
19
19
|
type UrlTransform,
|
|
20
20
|
} from "react-markdown";
|
|
21
21
|
import remarkGfm from "remark-gfm";
|
|
22
|
+
import { PanelsTopLeftIcon } from "lucide-react";
|
|
23
|
+
import { ActivityDisclosure } from "../timeline/shared";
|
|
22
24
|
import { cn } from "../lib/cn";
|
|
23
25
|
import { tableElementToTsv } from "../lib/clipboard";
|
|
24
26
|
import { prefersReducedMotion } from "../lib/motion";
|
|
25
27
|
import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
|
|
26
28
|
import { CopyButton } from "./copy-button";
|
|
29
|
+
import { PreviewLoading } from "./preview-loading";
|
|
27
30
|
import type { observeMarkdownTableLayout } from "./markdown-table-layout";
|
|
28
31
|
import { softenStreamingMarkdown } from "./soften-streaming-markdown";
|
|
29
32
|
import { createStreamReveal, rehypeStreamReveal, type StreamReveal } from "./stream-reveal";
|
|
30
33
|
import { TooltipProvider } from "./tooltip";
|
|
34
|
+
import { searchMatchOffset, type TimelineSearchTarget } from "./timeline-search";
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
37
|
* The default renderer for chat message bodies in {@link MessageTimeline}.
|
|
@@ -46,11 +50,14 @@ import { TooltipProvider } from "./tooltip";
|
|
|
46
50
|
export type MarkdownInteractiveBlock = { kind: "html" | "site"; content: string };
|
|
47
51
|
const InteractiveContext = createContext<{
|
|
48
52
|
source: string;
|
|
53
|
+
streaming?: boolean | undefined;
|
|
49
54
|
render?: (block: MarkdownInteractiveBlock) => ReactNode;
|
|
50
55
|
renderImage?: (image: { src: string; alt: string }) => ReactNode;
|
|
51
56
|
}>({ source: "" });
|
|
52
57
|
|
|
53
58
|
export type MarkdownProps = {
|
|
59
|
+
/** Reveals a bounded source excerpt; closing Find retains it until explicit restore. */
|
|
60
|
+
searchTarget?: TimelineSearchTarget | null | undefined;
|
|
54
61
|
renderImage?: ((image: { src: string; alt: string }) => ReactNode) | undefined;
|
|
55
62
|
/** Host opt-in for assistant-authored interactive fences. */
|
|
56
63
|
renderInteractiveBlock?: ((block: MarkdownInteractiveBlock) => ReactNode) | undefined;
|
|
@@ -406,7 +413,7 @@ function fenceLanguage(children: ReactNode): string | null {
|
|
|
406
413
|
}
|
|
407
414
|
|
|
408
415
|
function InteractiveCodeBlock({ children, node }: ComponentPropsWithoutRef<"pre"> & ExtraProps) {
|
|
409
|
-
const { source, render } = useContext(InteractiveContext);
|
|
416
|
+
const { source, render, streaming } = useContext(InteractiveContext);
|
|
410
417
|
const language = fenceLanguage(children);
|
|
411
418
|
if (render && (language === "opengeni-html" || language === "opengeni-site")) {
|
|
412
419
|
const start = node?.position?.start.offset;
|
|
@@ -428,7 +435,23 @@ function InteractiveCodeBlock({ children, node }: ComponentPropsWithoutRef<"pre"
|
|
|
428
435
|
closing &&
|
|
429
436
|
closing.length >= opening[1]!.length &&
|
|
430
437
|
[...closing].every((c) => c === opening[1]![0]);
|
|
431
|
-
if (!complete)
|
|
438
|
+
if (!complete) {
|
|
439
|
+
return (
|
|
440
|
+
<div role="status" aria-live="polite" aria-busy={streaming === true} className="my-3">
|
|
441
|
+
{streaming ? (
|
|
442
|
+
<PreviewLoading />
|
|
443
|
+
) : (
|
|
444
|
+
<ActivityDisclosure
|
|
445
|
+
icon={<PanelsTopLeftIcon aria-hidden className="size-3.5" />}
|
|
446
|
+
title="Preview incomplete"
|
|
447
|
+
running={false}
|
|
448
|
+
expandable={false}
|
|
449
|
+
preview="Generation stopped before the preview was ready."
|
|
450
|
+
/>
|
|
451
|
+
)}
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
432
455
|
return (
|
|
433
456
|
<>
|
|
434
457
|
{render({
|
|
@@ -622,10 +645,11 @@ function MarkdownImpl({
|
|
|
622
645
|
const interactiveContext = useMemo(
|
|
623
646
|
() => ({
|
|
624
647
|
source: children,
|
|
648
|
+
streaming,
|
|
625
649
|
...(renderInteractiveBlock ? { render: renderInteractiveBlock } : {}),
|
|
626
650
|
...(renderImage ? { renderImage } : {}),
|
|
627
651
|
}),
|
|
628
|
-
[children, renderInteractiveBlock, renderImage],
|
|
652
|
+
[children, streaming, renderInteractiveBlock, renderImage],
|
|
629
653
|
);
|
|
630
654
|
|
|
631
655
|
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
@@ -656,4 +680,62 @@ function MarkdownImpl({
|
|
|
656
680
|
}
|
|
657
681
|
|
|
658
682
|
/** Memoized so streaming re-renders of the parent don't re-parse settled bodies. */
|
|
659
|
-
export const Markdown = memo(
|
|
683
|
+
export const Markdown = memo(function SearchableMarkdown(props: MarkdownProps) {
|
|
684
|
+
const [retained, setRetained] = useState<{ text: string; target: TimelineSearchTarget } | null>(
|
|
685
|
+
null,
|
|
686
|
+
);
|
|
687
|
+
if (
|
|
688
|
+
props.searchTarget &&
|
|
689
|
+
(retained?.target !== props.searchTarget || retained.text !== props.children)
|
|
690
|
+
) {
|
|
691
|
+
setRetained({ text: props.children, target: props.searchTarget });
|
|
692
|
+
}
|
|
693
|
+
// Closing Find only removes the highlight. Retain the source window so a huge
|
|
694
|
+
// formatted message cannot replace it and unexpectedly move the reading point.
|
|
695
|
+
const target = props.searchTarget ?? (retained?.text === props.children ? retained.target : null);
|
|
696
|
+
if (!target) return <MarkdownImpl {...props} />;
|
|
697
|
+
const offset = searchMatchOffset(props.children, target);
|
|
698
|
+
if (offset < 0)
|
|
699
|
+
return props.searchTarget ? (
|
|
700
|
+
<div role="status">This match is no longer in the message source.</div>
|
|
701
|
+
) : (
|
|
702
|
+
<MarkdownImpl {...props} />
|
|
703
|
+
);
|
|
704
|
+
// Bound mounted text even for multi-megabyte messages. Do not split a UTF-16 pair
|
|
705
|
+
// at excerpt edges; the selected range itself is validated against the source.
|
|
706
|
+
let start = Math.max(0, offset - 240);
|
|
707
|
+
let end = Math.min(props.children.length, offset + target.query.length + 240);
|
|
708
|
+
if (start > 0 && /[\uDC00-\uDFFF]/.test(props.children[start]!)) start--;
|
|
709
|
+
if (end < props.children.length && /[\uDC00-\uDFFF]/.test(props.children[end]!)) end++;
|
|
710
|
+
const MatchTag = props.searchTarget ? "mark" : "span";
|
|
711
|
+
return (
|
|
712
|
+
<div className={cn("og-markdown-body min-w-0 break-words", props.className)}>
|
|
713
|
+
<div data-og-annotation-chrome="" className="text-xs opacity-70">
|
|
714
|
+
Match in message source · Excerpt stays in place when Find closes
|
|
715
|
+
<button
|
|
716
|
+
type="button"
|
|
717
|
+
disabled={!!props.searchTarget}
|
|
718
|
+
title={props.searchTarget ? "Close Find to show the formatted message" : undefined}
|
|
719
|
+
className="ml-2 inline-flex min-h-7 items-center rounded-og-sm px-1.5 text-og-xs font-medium text-og-fg-muted outline-hidden hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/45 pointer-coarse:min-h-11 disabled:opacity-50"
|
|
720
|
+
onClick={() => setRetained(null)}
|
|
721
|
+
>
|
|
722
|
+
Show formatted message
|
|
723
|
+
</button>
|
|
724
|
+
</div>
|
|
725
|
+
<div style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>
|
|
726
|
+
{start > 0 ? "…" : ""}
|
|
727
|
+
{props.children.slice(start, offset)}
|
|
728
|
+
<MatchTag
|
|
729
|
+
data-og-search-occurrence={target.occurrence ?? 0}
|
|
730
|
+
data-og-search-sequence={target.sequence}
|
|
731
|
+
data-og-search-query={target.query}
|
|
732
|
+
data-og-search-offset={offset}
|
|
733
|
+
>
|
|
734
|
+
{props.children.slice(offset, offset + target.query.length)}
|
|
735
|
+
</MatchTag>
|
|
736
|
+
{props.children.slice(offset + target.query.length, end)}
|
|
737
|
+
{end < props.children.length ? "…" : ""}
|
|
738
|
+
</div>
|
|
739
|
+
</div>
|
|
740
|
+
);
|
|
741
|
+
});
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { RollingActivity } from "../timeline/rolling-activity";
|
|
2
|
+
import {
|
|
3
|
+
isTimelineSearchTarget,
|
|
4
|
+
TimelineSearchRevealContext,
|
|
5
|
+
useTimelineSearchNavigation,
|
|
6
|
+
type TimelineSearchTarget,
|
|
7
|
+
} from "./timeline-search";
|
|
2
8
|
import { GenieLoadingOptionsContext, type GenieLoadingOptions } from "../timeline/genie-loading";
|
|
3
9
|
import { ChildSessionLink } from "./child-session-link";
|
|
4
10
|
import { useStartupDetails } from "../timeline/startup-preference";
|
|
5
11
|
import { parseSandboxFileArtifactReceipt } from "@opengeni/sdk";
|
|
6
12
|
import { unwrapMcpOutput } from "../timeline/parsers";
|
|
13
|
+
import { compactionSkipSubtitle } from "../timeline/compaction-copy";
|
|
7
14
|
import { isRetainedImageContentType } from "../timeline/retained-image";
|
|
8
15
|
import { mcpToolLeaf } from "../timeline/tool-display-name";
|
|
9
16
|
import type {
|
|
@@ -134,6 +141,8 @@ const TimelineAnnotationSelection = lazy(() => import("./timeline-annotation-sel
|
|
|
134
141
|
const TimelineAnnotationMarkers = lazy(() => import("./timeline-annotation-markers"));
|
|
135
142
|
|
|
136
143
|
export type MessageTimelineProps = {
|
|
144
|
+
/** Exact durable search hit; clearing it removes highlighting without moving the reader. */
|
|
145
|
+
searchTarget?: TimelineSearchTarget | null | undefined;
|
|
137
146
|
/** Localized user-message disclosure actions, including custom UserMessageBody renderers. */
|
|
138
147
|
userMessageDisclosureLabels?: UserMessageDisclosureLabels | undefined;
|
|
139
148
|
/** Raw session events (projected internally) … */
|
|
@@ -146,7 +155,11 @@ export type MessageTimelineProps = {
|
|
|
146
155
|
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
147
156
|
/** Plug a markdown renderer for message bodies (e.g. streamdown). */
|
|
148
157
|
renderMessageText?:
|
|
149
|
-
| ((
|
|
158
|
+
| ((
|
|
159
|
+
text: string,
|
|
160
|
+
item: AgentMessageItem | UserMessageItem,
|
|
161
|
+
context: { searchTarget: TimelineSearchTarget | null },
|
|
162
|
+
) => ReactNode)
|
|
150
163
|
| undefined;
|
|
151
164
|
/** Drill into a spawned worker session. */
|
|
152
165
|
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
@@ -413,6 +426,7 @@ function cssEscapeAttribute(value: string): string {
|
|
|
413
426
|
*/
|
|
414
427
|
export function MessageTimeline({
|
|
415
428
|
userMessageDisclosureLabels,
|
|
429
|
+
searchTarget,
|
|
416
430
|
events,
|
|
417
431
|
items,
|
|
418
432
|
status: _status,
|
|
@@ -457,6 +471,46 @@ export function MessageTimeline({
|
|
|
457
471
|
(item) => item.kind !== "auth-needed" || shouldRenderAuthNeeded(item),
|
|
458
472
|
);
|
|
459
473
|
}, [items, events, shouldRenderAuthNeeded]);
|
|
474
|
+
const searchItem = searchTarget
|
|
475
|
+
? resolvedItems.find((item) => isTimelineSearchTarget(item, searchTarget))
|
|
476
|
+
: undefined;
|
|
477
|
+
const searchRevealKey =
|
|
478
|
+
searchItem && searchTarget
|
|
479
|
+
? JSON.stringify([
|
|
480
|
+
searchItem.id,
|
|
481
|
+
searchTarget.query,
|
|
482
|
+
searchTarget.occurrence ?? 0,
|
|
483
|
+
searchTarget.offset ?? null,
|
|
484
|
+
])
|
|
485
|
+
: null;
|
|
486
|
+
const searchTargetRef = useRef(searchTarget);
|
|
487
|
+
searchTargetRef.current = searchTarget;
|
|
488
|
+
const renderSearchableMessageText = useMemo(
|
|
489
|
+
() =>
|
|
490
|
+
renderMessageText
|
|
491
|
+
? (text: string, item: AgentMessageItem | UserMessageItem) =>
|
|
492
|
+
renderMessageText(text, item, {
|
|
493
|
+
searchTarget: searchItem?.id === item.id ? (searchTarget ?? null) : null,
|
|
494
|
+
})
|
|
495
|
+
: (text: string, item: AgentMessageItem | UserMessageItem) => {
|
|
496
|
+
const body = (
|
|
497
|
+
<Markdown
|
|
498
|
+
searchTarget={searchItem?.id === item.id ? searchTarget : null}
|
|
499
|
+
streaming={item.kind === "agent-message" && item.streaming}
|
|
500
|
+
>
|
|
501
|
+
{text}
|
|
502
|
+
</Markdown>
|
|
503
|
+
);
|
|
504
|
+
return item.kind === "user-message" ? (
|
|
505
|
+
<UserMessageBody messageId={item.id} text={text}>
|
|
506
|
+
{body}
|
|
507
|
+
</UserMessageBody>
|
|
508
|
+
) : (
|
|
509
|
+
body
|
|
510
|
+
);
|
|
511
|
+
},
|
|
512
|
+
[renderMessageText, searchItem?.id, searchTarget],
|
|
513
|
+
);
|
|
460
514
|
// Event-window identity is independent of projected rows (partial messages
|
|
461
515
|
// can acquire a different first-delta id when older text arrives).
|
|
462
516
|
const sourceItems = events ?? items;
|
|
@@ -1005,7 +1059,7 @@ export function MessageTimeline({
|
|
|
1005
1059
|
userMessageDisclosureContext,
|
|
1006
1060
|
behavior: {
|
|
1007
1061
|
renderMessageActions,
|
|
1008
|
-
renderMessageText,
|
|
1062
|
+
renderMessageText: renderSearchableMessageText,
|
|
1009
1063
|
onOpenSession,
|
|
1010
1064
|
onMemoryClick,
|
|
1011
1065
|
onReconnect,
|
|
@@ -1028,7 +1082,7 @@ export function MessageTimeline({
|
|
|
1028
1082
|
onReconnect,
|
|
1029
1083
|
renderAuthNeeded,
|
|
1030
1084
|
renderMessageActions,
|
|
1031
|
-
|
|
1085
|
+
renderSearchableMessageText,
|
|
1032
1086
|
resolveProviderLogo,
|
|
1033
1087
|
toolRegistry,
|
|
1034
1088
|
genieLoading,
|
|
@@ -1037,6 +1091,19 @@ export function MessageTimeline({
|
|
|
1037
1091
|
],
|
|
1038
1092
|
);
|
|
1039
1093
|
|
|
1094
|
+
const releaseSearchPin = useCallback(() => {
|
|
1095
|
+
applyPinned(false);
|
|
1096
|
+
stopFollow();
|
|
1097
|
+
setRevealed(true);
|
|
1098
|
+
}, [applyPinned, stopFollow]);
|
|
1099
|
+
useTimelineSearchNavigation(
|
|
1100
|
+
scrollRef,
|
|
1101
|
+
searchItem?.id,
|
|
1102
|
+
searchTarget,
|
|
1103
|
+
releaseSearchPin,
|
|
1104
|
+
resolvedItems,
|
|
1105
|
+
);
|
|
1106
|
+
|
|
1040
1107
|
const requestOlderIfUnderfilled = useCallback(
|
|
1041
1108
|
(node: HTMLElement, retry?: OlderLoadAttempt) => {
|
|
1042
1109
|
let currentAttempt = olderLoadAttemptRef.current;
|
|
@@ -1707,6 +1774,8 @@ export function MessageTimeline({
|
|
|
1707
1774
|
// at what just became the live bottom — paging forward to the tip must not
|
|
1708
1775
|
// strand them unpinned watching new content grow below.
|
|
1709
1776
|
useEffect(() => {
|
|
1777
|
+
// Closing find is paint-only; it must not trigger this history-to-tip rule.
|
|
1778
|
+
if (searchTargetRef.current) return;
|
|
1710
1779
|
if (hasNewer) {
|
|
1711
1780
|
stopFollow();
|
|
1712
1781
|
applyPinned(false);
|
|
@@ -2100,23 +2169,31 @@ export function MessageTimeline({
|
|
|
2100
2169
|
) : null}
|
|
2101
2170
|
{groups.map(({ group, key, entranceEnabled }, index) => {
|
|
2102
2171
|
return (
|
|
2103
|
-
<
|
|
2172
|
+
<TimelineSearchRevealContext.Provider
|
|
2104
2173
|
key={key}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
group.kind === "activity" &&
|
|
2110
|
-
group.items.some(
|
|
2111
|
-
(item) => item.turnId && turnsWithOutput.has(item.turnId),
|
|
2112
|
-
)
|
|
2113
|
-
}
|
|
2114
|
-
entranceEnabled={entranceEnabled}
|
|
2115
|
-
liveEntranceEnabled={
|
|
2116
|
-
group.kind === "activity" ? !bulkRender : undefined
|
|
2174
|
+
value={
|
|
2175
|
+
searchItem && timelineGroupItemIds(group).includes(searchItem.id)
|
|
2176
|
+
? searchRevealKey
|
|
2177
|
+
: null
|
|
2117
2178
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2179
|
+
>
|
|
2180
|
+
<TimelineGroupEntry
|
|
2181
|
+
groupKey={key}
|
|
2182
|
+
group={group}
|
|
2183
|
+
nextGroup={groups[index + 1]?.group}
|
|
2184
|
+
startupDismissed={
|
|
2185
|
+
group.kind === "activity" &&
|
|
2186
|
+
group.items.some(
|
|
2187
|
+
(item) => item.turnId && turnsWithOutput.has(item.turnId),
|
|
2188
|
+
)
|
|
2189
|
+
}
|
|
2190
|
+
entranceEnabled={entranceEnabled}
|
|
2191
|
+
liveEntranceEnabled={
|
|
2192
|
+
group.kind === "activity" ? !bulkRender : undefined
|
|
2193
|
+
}
|
|
2194
|
+
context={timelineGroupEntryContext}
|
|
2195
|
+
/>
|
|
2196
|
+
</TimelineSearchRevealContext.Provider>
|
|
2120
2197
|
);
|
|
2121
2198
|
})}
|
|
2122
2199
|
{groups.length > 0 && trailingState ? (
|
|
@@ -2455,7 +2532,9 @@ type TimelineGroupEntryContext = {
|
|
|
2455
2532
|
|
|
2456
2533
|
type TimelineGroupBehaviorProps = {
|
|
2457
2534
|
renderMessageActions: MessageTimelineProps["renderMessageActions"];
|
|
2458
|
-
renderMessageText:
|
|
2535
|
+
renderMessageText:
|
|
2536
|
+
| ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode)
|
|
2537
|
+
| undefined;
|
|
2459
2538
|
onOpenSession: MessageTimelineProps["onOpenSession"];
|
|
2460
2539
|
onMemoryClick: MessageTimelineProps["onMemoryClick"];
|
|
2461
2540
|
onReconnect: MessageTimelineProps["onReconnect"];
|
|
@@ -3260,7 +3339,7 @@ function CompactionRow({ item }: { item: ContextCompactionItem }) {
|
|
|
3260
3339
|
item.phase === "compacted"
|
|
3261
3340
|
? "Chat history above is unchanged"
|
|
3262
3341
|
: item.phase === "skipped"
|
|
3263
|
-
? compactionSkipSubtitle(item.skipReason)
|
|
3342
|
+
? compactionSkipSubtitle(item.skipReason, item.providerRejection)
|
|
3264
3343
|
: null;
|
|
3265
3344
|
const pill =
|
|
3266
3345
|
item.phase === "skipped" && item.skipReason === "summarization_failed"
|
|
@@ -3290,21 +3369,6 @@ function CompactionRow({ item }: { item: ContextCompactionItem }) {
|
|
|
3290
3369
|
);
|
|
3291
3370
|
}
|
|
3292
3371
|
|
|
3293
|
-
function compactionSkipSubtitle(reason: string | null): string {
|
|
3294
|
-
switch (reason) {
|
|
3295
|
-
case "no_history":
|
|
3296
|
-
return "No active history to compact";
|
|
3297
|
-
case "replacement_not_smaller":
|
|
3298
|
-
return "Checkpoint would not reduce memory size";
|
|
3299
|
-
case "replacement_unchanged":
|
|
3300
|
-
return "Checkpoint made no progress";
|
|
3301
|
-
case "summarization_failed":
|
|
3302
|
-
return "Request it again to retry. Chat history is unchanged.";
|
|
3303
|
-
default:
|
|
3304
|
-
return "Compaction was not needed. Chat history is unchanged.";
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
3372
|
/** Hover-reveal clock beside the copy control (sent / finished). */
|
|
3309
3373
|
function MessageFooterTime({ occurredAt }: { occurredAt: string }) {
|
|
3310
3374
|
return (
|
|
@@ -3355,7 +3419,10 @@ function UserMessageRow({
|
|
|
3355
3419
|
>
|
|
3356
3420
|
<div className={MESSAGE_BUBBLE_CLASS}>
|
|
3357
3421
|
{item.text ? (
|
|
3358
|
-
<div
|
|
3422
|
+
<div
|
|
3423
|
+
data-og-search-item={item.id}
|
|
3424
|
+
data-og-annotation-source-key={item.annotationSource?.eventId}
|
|
3425
|
+
>
|
|
3359
3426
|
{renderMessageText ? (
|
|
3360
3427
|
renderMessageText(item.text, item)
|
|
3361
3428
|
) : (
|
|
@@ -3578,6 +3645,7 @@ function AgentMessageRow({
|
|
|
3578
3645
|
>
|
|
3579
3646
|
<div
|
|
3580
3647
|
data-og-wide-table-message=""
|
|
3648
|
+
data-og-search-item={item.id}
|
|
3581
3649
|
data-og-annotation-source-key={item.annotationSource?.eventId}
|
|
3582
3650
|
>
|
|
3583
3651
|
{body}
|
|
@@ -59,7 +59,14 @@ function providerGroups(models: ClientModel[], codexOnly: boolean) {
|
|
|
59
59
|
optionDisabled: codexOnly && !isCodexClientModel(model),
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
const groups = [...byProvider.values()];
|
|
63
|
+
if (codexOnly) {
|
|
64
|
+
const codexIndex = groups.findIndex((group) =>
|
|
65
|
+
group.models.some((model) => !model.optionDisabled && isCodexClientModel(model)),
|
|
66
|
+
);
|
|
67
|
+
if (codexIndex > 0) groups.unshift(...groups.splice(codexIndex, 1));
|
|
68
|
+
}
|
|
69
|
+
return groups;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
export function ModelPicker({
|
|
@@ -78,8 +85,9 @@ export function ModelPicker({
|
|
|
78
85
|
[rows, codexOnly],
|
|
79
86
|
);
|
|
80
87
|
const billingGroups = useMemo(
|
|
81
|
-
() =>
|
|
82
|
-
|
|
88
|
+
() =>
|
|
89
|
+
effectiveRows.length > 0 ? groupPickerRowsByBillingClass(effectiveRows, { codexOnly }) : [],
|
|
90
|
+
[effectiveRows, codexOnly],
|
|
83
91
|
);
|
|
84
92
|
const providerGroupList = useMemo(() => providerGroups(models, codexOnly), [models, codexOnly]);
|
|
85
93
|
const useBillingGroups = Boolean(effectiveRows.length);
|
|
@@ -34,7 +34,10 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
34
34
|
`${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${payerSummaryForModel(row.catalog)}`.toLowerCase();
|
|
35
35
|
return words.every((word) => text.includes(word));
|
|
36
36
|
});
|
|
37
|
-
const
|
|
37
|
+
const matchingIds = new Set(filtered.map((row) => row.id));
|
|
38
|
+
const groups = groupPickerRowsByBillingClass(rows, { codexOnly: props.codexOnly === true })
|
|
39
|
+
.map((group) => ({ ...group, rows: group.rows.filter((row) => matchingIds.has(row.id)) }))
|
|
40
|
+
.filter((group) => group.rows.length > 0);
|
|
38
41
|
const choose = (row: ClientPickerModelRow) => {
|
|
39
42
|
if (!row.selectable || props.disabled) return;
|
|
40
43
|
if (row.id !== props.model) props.onModelChange(row.id);
|
|
@@ -139,8 +142,12 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
139
142
|
) : (
|
|
140
143
|
<>
|
|
141
144
|
{groups.map((group) => (
|
|
142
|
-
<section
|
|
143
|
-
|
|
145
|
+
<section
|
|
146
|
+
key={group.billingClass}
|
|
147
|
+
aria-label={group.label}
|
|
148
|
+
className="py-2.5 first:pt-1 [&+section]:border-t [&+section]:border-og-border"
|
|
149
|
+
>
|
|
150
|
+
<div className="flex items-center gap-2 px-2.5 py-1.5 text-og-control font-semibold text-og-fg">
|
|
144
151
|
<BillingClassMark billingClass={group.billingClass} aria-label="" />
|
|
145
152
|
{group.label}
|
|
146
153
|
</div>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/** Private loading surface: never receives or executes the unfinished fence. */
|
|
4
|
+
export function PreviewLoading() {
|
|
5
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const canvas = ref.current;
|
|
8
|
+
if (!canvas) return;
|
|
9
|
+
let ctx: CanvasRenderingContext2D | null;
|
|
10
|
+
try {
|
|
11
|
+
ctx = canvas.getContext("2d");
|
|
12
|
+
} catch {
|
|
13
|
+
return; // The CSS grid and status remain when canvas is unavailable.
|
|
14
|
+
}
|
|
15
|
+
if (!ctx) return;
|
|
16
|
+
const context = ctx;
|
|
17
|
+
const motion = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
18
|
+
let frame: number | undefined;
|
|
19
|
+
let time = 2;
|
|
20
|
+
let last: number | undefined;
|
|
21
|
+
let disposed = false;
|
|
22
|
+
// Without visibility observation, keep a static surface rather than doing
|
|
23
|
+
// unbounded work for previews that may be outside the viewport.
|
|
24
|
+
let visible = false;
|
|
25
|
+
const canAnimate = () => !disposed && visible && !document.hidden && motion?.matches === false;
|
|
26
|
+
|
|
27
|
+
function draw() {
|
|
28
|
+
const w = canvas!.clientWidth;
|
|
29
|
+
const h = canvas!.clientHeight;
|
|
30
|
+
if (!w || !h || document.hidden || disposed) return;
|
|
31
|
+
const d = Math.min(window.devicePixelRatio || 1, 2);
|
|
32
|
+
if (canvas!.width !== Math.round(w * d) || canvas!.height !== Math.round(h * d)) {
|
|
33
|
+
canvas!.width = Math.round(w * d);
|
|
34
|
+
canvas!.height = Math.round(h * d);
|
|
35
|
+
}
|
|
36
|
+
const style = getComputedStyle(canvas!);
|
|
37
|
+
const ink = style.getPropertyValue("--_og-preview-ink").trim() || "#bca3ef";
|
|
38
|
+
const glowColor = style.getPropertyValue("--_og-preview-glow").trim() || "#8f6ccc";
|
|
39
|
+
context.setTransform(d, 0, 0, d, 0, 0);
|
|
40
|
+
context.clearRect(0, 0, w, h);
|
|
41
|
+
const cx = w * (0.5 + 0.13 * Math.sin(time * 0.21));
|
|
42
|
+
const cy = h * (0.5 + 0.12 * Math.cos(time * 0.27));
|
|
43
|
+
const glow = context.createRadialGradient(cx, cy, 0, cx, cy, Math.max(w, h) * 0.65);
|
|
44
|
+
glow.addColorStop(0, glowColor);
|
|
45
|
+
glow.addColorStop(1, "transparent");
|
|
46
|
+
context.globalAlpha = 0.09;
|
|
47
|
+
context.fillStyle = glow;
|
|
48
|
+
context.fillRect(0, 0, w, h);
|
|
49
|
+
const rows = Math.ceil(h / 18) + 7;
|
|
50
|
+
const cols = Math.ceil(w / 18) + 7;
|
|
51
|
+
const points = Array.from({ length: rows }, (_row, r) =>
|
|
52
|
+
Array.from({ length: cols }, (_col, c) => {
|
|
53
|
+
const x = (c - 3) * 18;
|
|
54
|
+
const y = (r - 3) * 18;
|
|
55
|
+
const dist = Math.hypot((x - cx) * 0.8, y - cy);
|
|
56
|
+
const wave = Math.sin(dist * 0.032 - time * 0.95);
|
|
57
|
+
return {
|
|
58
|
+
x: x + ((x - cx) / Math.max(dist, 1)) * wave * 4,
|
|
59
|
+
y: y + ((y - cy) / Math.max(dist, 1)) * wave * 4,
|
|
60
|
+
l: Math.pow((wave + 1) / 2, 5),
|
|
61
|
+
};
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
context.strokeStyle = ink;
|
|
65
|
+
context.fillStyle = ink;
|
|
66
|
+
context.lineWidth = 0.65;
|
|
67
|
+
points.forEach((row, r) =>
|
|
68
|
+
row.forEach((p, c) => {
|
|
69
|
+
context.globalAlpha = 0.035 + p.l * 0.055;
|
|
70
|
+
context.beginPath();
|
|
71
|
+
if (c + 1 < cols) {
|
|
72
|
+
context.moveTo(p.x, p.y);
|
|
73
|
+
context.lineTo(row[c + 1]!.x, row[c + 1]!.y);
|
|
74
|
+
}
|
|
75
|
+
if (r + 1 < rows) {
|
|
76
|
+
context.moveTo(p.x, p.y);
|
|
77
|
+
context.lineTo(points[r + 1]![c]!.x, points[r + 1]![c]!.y);
|
|
78
|
+
}
|
|
79
|
+
context.stroke();
|
|
80
|
+
context.globalAlpha = 0.15 + p.l * 0.55;
|
|
81
|
+
context.beginPath();
|
|
82
|
+
context.arc(p.x, p.y, 0.85 + p.l * 0.65, 0, Math.PI * 2);
|
|
83
|
+
context.fill();
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
context.globalAlpha = 1;
|
|
87
|
+
canvas!.dataset.painted = "true";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function stop() {
|
|
91
|
+
if (frame !== undefined) window.cancelAnimationFrame(frame);
|
|
92
|
+
frame = undefined;
|
|
93
|
+
last = undefined;
|
|
94
|
+
}
|
|
95
|
+
function tick(stamp: number) {
|
|
96
|
+
frame = undefined;
|
|
97
|
+
if (!canAnimate()) return;
|
|
98
|
+
if (last !== undefined) time += Math.min((stamp - last) / 1000, 0.05);
|
|
99
|
+
last = stamp;
|
|
100
|
+
draw();
|
|
101
|
+
frame = window.requestAnimationFrame(tick);
|
|
102
|
+
}
|
|
103
|
+
function refresh() {
|
|
104
|
+
stop();
|
|
105
|
+
if (disposed || document.hidden || (!visible && intersection)) return;
|
|
106
|
+
draw();
|
|
107
|
+
if (canAnimate() && typeof window.requestAnimationFrame === "function") {
|
|
108
|
+
frame = window.requestAnimationFrame(tick);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const intersection =
|
|
112
|
+
typeof IntersectionObserver === "function"
|
|
113
|
+
? new IntersectionObserver(([entry]) => {
|
|
114
|
+
visible = entry?.isIntersecting === true;
|
|
115
|
+
refresh();
|
|
116
|
+
})
|
|
117
|
+
: undefined;
|
|
118
|
+
intersection?.observe(canvas);
|
|
119
|
+
const resize = typeof ResizeObserver === "function" ? new ResizeObserver(refresh) : undefined;
|
|
120
|
+
resize?.observe(canvas);
|
|
121
|
+
// Observe only ancestors, not the document subtree. This also updates the
|
|
122
|
+
// static reduced-motion canvas when a host changes its local theme.
|
|
123
|
+
const theme =
|
|
124
|
+
typeof MutationObserver === "function" ? new MutationObserver(refresh) : undefined;
|
|
125
|
+
for (let ancestor = canvas.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
126
|
+
theme?.observe(ancestor, {
|
|
127
|
+
attributes: true,
|
|
128
|
+
attributeFilter: ["class", "style", "data-og-theme"],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
motion?.addEventListener?.("change", refresh);
|
|
132
|
+
document.addEventListener("visibilitychange", refresh);
|
|
133
|
+
window.addEventListener("resize", refresh);
|
|
134
|
+
// Old hosts get one still frame; observed surfaces wait until onscreen.
|
|
135
|
+
if (!intersection) draw();
|
|
136
|
+
return () => {
|
|
137
|
+
disposed = true;
|
|
138
|
+
stop();
|
|
139
|
+
intersection?.disconnect();
|
|
140
|
+
resize?.disconnect();
|
|
141
|
+
theme?.disconnect();
|
|
142
|
+
motion?.removeEventListener?.("change", refresh);
|
|
143
|
+
document.removeEventListener("visibilitychange", refresh);
|
|
144
|
+
window.removeEventListener("resize", refresh);
|
|
145
|
+
};
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className="og-preview-loading">
|
|
150
|
+
<canvas ref={ref} aria-hidden="true" />
|
|
151
|
+
<span className="og-preview-loading-label">Preparing preview…</span>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|