@opengeni/react 0.29.0 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/approvals.d.ts +14 -0
- package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
- package/dist/chunk-4QOQ2DSR.js.map +1 -0
- package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
- package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
- package/dist/{chunk-CSJBF3QJ.js → chunk-A2WITZTI.js} +377 -113
- package/dist/chunk-A2WITZTI.js.map +1 -0
- package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
- package/dist/{chunk-AVLMIWYO.js → chunk-KZRUYCL4.js} +12 -6
- package/dist/chunk-KZRUYCL4.js.map +1 -0
- package/dist/{chunk-IPVOVOW6.js → chunk-MS3FBZ6A.js} +2539 -2174
- package/dist/chunk-MS3FBZ6A.js.map +1 -0
- package/dist/{chunk-OKXCECLG.js → chunk-TCCE5ZU5.js} +171 -22
- package/dist/chunk-TCCE5ZU5.js.map +1 -0
- package/dist/chunk-U255M5EZ.js +178 -0
- package/dist/chunk-U255M5EZ.js.map +1 -0
- package/dist/chunk-ZNNE2VES.js +6413 -0
- package/dist/chunk-ZNNE2VES.js.map +1 -0
- package/dist/client.d.ts +33 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/registry.d.ts +41 -0
- package/dist/commands/types.d.ts +85 -0
- package/dist/components/approval-surface.d.ts +28 -0
- package/dist/components/chat-composer.d.ts +51 -0
- package/dist/components/code-editor.d.ts +43 -0
- package/dist/components/command-palette.d.ts +25 -0
- package/dist/components/composer-transcription-control.d.ts +36 -0
- package/dist/components/composer.d.ts +277 -0
- package/dist/components/copy-button.d.ts +26 -0
- package/dist/components/desktop-viewer.d.ts +85 -0
- package/dist/components/diff-view.d.ts +38 -0
- package/dist/components/enrollment-consent.d.ts +33 -0
- package/dist/components/enrollment-device-flow.d.ts +27 -0
- package/dist/components/file-browser.d.ts +47 -0
- package/dist/components/fleet-tile.d.ts +18 -0
- package/dist/components/human-input-form.d.ts +47 -0
- package/dist/components/machine-card.d.ts +21 -0
- package/dist/components/machine-dock-bar.d.ts +31 -0
- package/dist/components/machine-health-pill.d.ts +9 -0
- package/dist/components/machine-metrics.d.ts +15 -0
- package/dist/components/machine-status-pill.d.ts +54 -0
- package/dist/components/machines/health.d.ts +25 -0
- package/dist/components/machines/machine-detail.d.ts +14 -0
- package/dist/components/machines/metric-history-chart.d.ts +24 -0
- package/dist/components/machines/metric-sparkline.d.ts +9 -0
- package/dist/components/machines/series.d.ts +24 -0
- package/dist/components/machines-dashboard.d.ts +30 -0
- package/dist/components/markdown.d.ts +31 -0
- package/dist/components/message-timeline.d.ts +98 -0
- package/dist/components/model-picker.d.ts +19 -0
- package/dist/components/pierre-diff.d.ts +34 -0
- package/dist/components/pierre-file.d.ts +27 -0
- package/dist/components/queue-draft-policy.d.ts +7 -0
- package/dist/components/queue-surface-implementation.d.ts +20 -0
- package/dist/components/queue-surface-state.d.ts +12 -0
- package/dist/components/queue-surface.d.ts +12 -0
- package/dist/components/sandbox-files.d.ts +49 -0
- package/dist/components/sandbox-terminal.d.ts +123 -0
- package/dist/components/sandbox-workspace.d.ts +128 -0
- package/dist/components/session-chrome.d.ts +70 -0
- package/dist/components/session-status.d.ts +25 -0
- package/dist/components/soften-streaming-markdown.d.ts +13 -0
- package/dist/components/stream-reveal.d.ts +37 -0
- package/dist/components/tip-follow.d.ts +139 -0
- package/dist/components/tooltip.d.ts +16 -0
- package/dist/components/workbench-changes.d.ts +34 -0
- package/dist/components/workspace-dock.d.ts +35 -0
- package/dist/composer.d.ts +11 -7
- package/dist/composer.js +9 -6
- package/dist/hooks/internal.d.ts +46 -0
- package/dist/hooks/use-available-models.d.ts +37 -0
- package/dist/hooks/use-billing-usage.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +69 -0
- package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
- package/dist/hooks/use-desktop-stream.d.ts +43 -0
- package/dist/hooks/use-environments.d.ts +35 -0
- package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
- package/dist/hooks/use-goal.d.ts +46 -0
- package/dist/hooks/use-human-input.d.ts +26 -0
- package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
- package/dist/hooks/use-machine-chip.d.ts +54 -0
- package/dist/hooks/use-machines.d.ts +81 -0
- package/dist/hooks/use-packs.d.ts +27 -0
- package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
- package/dist/hooks/use-rigs.d.ts +87 -0
- package/dist/hooks/use-sandbox-files.d.ts +108 -0
- package/dist/hooks/use-sandbox-git.d.ts +58 -0
- package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
- package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
- package/dist/hooks/use-session-capabilities.d.ts +83 -0
- package/dist/hooks/use-session-control.d.ts +22 -0
- package/dist/hooks/use-session-events.d.ts +92 -0
- package/dist/hooks/use-session-lineage.d.ts +17 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
- package/dist/hooks/use-session.d.ts +23 -0
- package/dist/hooks/use-slash-commands.d.ts +66 -0
- package/dist/hooks/use-terminal-stream.d.ts +46 -0
- package/dist/hooks/use-transcription.d.ts +73 -0
- package/dist/hooks/use-turn-queue.d.ts +44 -0
- package/dist/hooks/use-voice-input.d.ts +25 -0
- package/dist/hooks/use-windowed-sections.d.ts +42 -0
- package/dist/hooks/use-workspace-capture.d.ts +49 -0
- package/dist/hooks/use-workspace-edit.d.ts +73 -0
- package/dist/hooks/use-workspace-sessions.d.ts +30 -0
- package/dist/hooks/use-workspaces.d.ts +22 -0
- package/dist/human-input.d.ts +18 -0
- package/dist/index.d.ts +144 -2119
- package/dist/index.js +421 -324
- package/dist/index.js.map +1 -1
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/format.d.ts +48 -0
- package/dist/lib/git-patch.d.ts +8 -0
- package/dist/lib/motion-inspect.d.ts +5 -0
- package/dist/lib/motion.d.ts +14 -0
- package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
- package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
- package/dist/lib/relay-wire.d.ts +30 -0
- package/dist/lib/use-portal-token-style.d.ts +9 -0
- package/dist/lib/use-theme-type.d.ts +5 -0
- package/dist/lib/use-unicode-fonts.d.ts +12 -0
- package/dist/lib/xterm-renderer.d.ts +19 -0
- package/dist/lib/xterm-theme.d.ts +75 -0
- package/dist/machines.d.ts +30 -413
- package/dist/machines.js +3 -3
- package/dist/model-policy.d.ts +32 -0
- package/dist/model-policy.js +33 -0
- package/dist/model-policy.js.map +1 -0
- package/dist/provider.d.ts +16 -0
- package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
- package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
- package/dist/session-context.d.ts +82 -0
- package/dist/session-ui.d.ts +10 -7
- package/dist/session-ui.js +9 -5
- package/dist/session.d.ts +28 -6
- package/dist/session.js +11 -11
- package/dist/timeline/activity-rail.d.ts +19 -0
- package/dist/timeline/disclosure-context.d.ts +14 -0
- package/dist/timeline/entrance.d.ts +14 -0
- package/dist/timeline/fleet-decision-projection.d.ts +9 -0
- package/dist/timeline/fleet-decision-row.d.ts +11 -0
- package/dist/timeline/fold-memory.d.ts +15 -0
- package/dist/timeline/index.d.ts +18 -0
- package/dist/timeline/parsers.d.ts +96 -0
- package/dist/timeline/projection.d.ts +38 -0
- package/dist/timeline/registry.d.ts +40 -0
- package/dist/timeline/screenshot-lightbox.d.ts +26 -0
- package/dist/timeline/seen-activity-ids.d.ts +2 -0
- package/dist/timeline/shared.d.ts +125 -0
- package/dist/timeline/tool-diff.d.ts +8 -0
- package/dist/timeline/tool-display-name.d.ts +10 -0
- package/dist/timeline/tool-renderers.d.ts +5 -0
- package/dist/timeline/turn-summary.d.ts +116 -0
- package/dist/{use-turn-queue-D_1a8W_l.d.ts → timeline/types.d.ts} +41 -69
- package/dist/types/machines.d.ts +11 -0
- package/package.json +8 -4
- package/src/client.ts +1 -0
- package/src/components/composer-transcription-control.tsx +278 -130
- package/src/components/composer.tsx +20 -15
- package/src/components/copy-button.tsx +127 -0
- package/src/components/markdown.tsx +231 -32
- package/src/components/message-timeline.tsx +1304 -378
- package/src/components/model-picker.tsx +83 -43
- package/src/components/queue-surface-implementation.tsx +1 -1
- package/src/components/session-chrome.tsx +942 -0
- package/src/components/soften-streaming-markdown.ts +205 -0
- package/src/components/stream-reveal.ts +195 -0
- package/src/components/tip-follow.ts +459 -0
- package/src/components/tooltip.tsx +103 -0
- package/src/composer.ts +6 -0
- package/src/hooks/internal.ts +30 -4
- package/src/hooks/use-available-models.ts +51 -4
- package/src/hooks/use-composer.ts +23 -3
- package/src/hooks/use-last-started-turn-policy.ts +95 -0
- package/src/hooks/use-session-events.ts +359 -5
- package/src/hooks/use-voice-input.ts +232 -0
- package/src/index.ts +47 -1
- package/src/lib/clipboard.ts +58 -0
- package/src/lib/format.ts +18 -0
- package/src/lib/motion-inspect.ts +5 -0
- package/src/lib/motion.ts +80 -0
- package/src/model-policy.ts +210 -0
- package/src/session-ui.ts +7 -0
- package/src/timeline/activity-rail.tsx +46 -7
- package/src/timeline/entrance.tsx +10 -0
- package/src/timeline/fold-memory.ts +52 -0
- package/src/timeline/index.ts +9 -1
- package/src/timeline/projection.ts +226 -41
- package/src/timeline/seen-activity-ids.ts +19 -0
- package/src/timeline/shared.tsx +1 -1
- package/src/timeline/tool-renderers.tsx +75 -6
- package/src/timeline/turn-summary.tsx +387 -97
- package/src/timeline/types.ts +21 -0
- package/styles/index.css +99 -7
- package/styles/tokens.css +44 -3
- package/dist/chunk-AVLMIWYO.js.map +0 -1
- package/dist/chunk-CSJBF3QJ.js.map +0 -1
- package/dist/chunk-IPVOVOW6.js.map +0 -1
- package/dist/chunk-M7X4JZOD.js.map +0 -1
- package/dist/chunk-OKXCECLG.js.map +0 -1
- package/dist/chunk-ZMNPFZWL.js +0 -3918
- package/dist/chunk-ZMNPFZWL.js.map +0 -1
- package/dist/composer-BMzimGTO.d.ts +0 -528
- package/dist/session-DaONt-dY.d.ts +0 -264
- package/dist/session-context-CAjlaeZb.d.ts +0 -84
- package/dist/session-ui-B5_roA95.d.ts +0 -259
- /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
2
|
+
import { useEffect, useRef, useState, type MouseEvent, type ReactNode } from "react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
import { copyTextToClipboard } from "../lib/clipboard";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
6
|
+
|
|
7
|
+
const COPIED_MS = 1600;
|
|
8
|
+
|
|
9
|
+
export type CopyButtonProps = {
|
|
10
|
+
/** Plain text to copy, or a lazy getter (tables / late DOM). */
|
|
11
|
+
text: string | (() => string);
|
|
12
|
+
/** Accessible name + tooltip while idle. */
|
|
13
|
+
label?: string | undefined;
|
|
14
|
+
className?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* `always` — visible control.
|
|
17
|
+
* `group-hover` — parent must use `group/copy`; fades in on hover/focus.
|
|
18
|
+
*/
|
|
19
|
+
reveal?: "always" | "group-hover" | undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ghost icon copy control — no chrome box, no "Copy code" label.
|
|
24
|
+
* Copy → check flash. Hover-reveal via `group/copy` on a parent.
|
|
25
|
+
*/
|
|
26
|
+
export function CopyButton({
|
|
27
|
+
text,
|
|
28
|
+
label = "Copy",
|
|
29
|
+
className,
|
|
30
|
+
reveal = "group-hover",
|
|
31
|
+
}: CopyButtonProps) {
|
|
32
|
+
const [copied, setCopied] = useState(false);
|
|
33
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
return () => {
|
|
37
|
+
if (timerRef.current !== null) {
|
|
38
|
+
clearTimeout(timerRef.current);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const onClick = async (event: MouseEvent<HTMLButtonElement>) => {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
const value = typeof text === "function" ? text() : text;
|
|
47
|
+
const ok = await copyTextToClipboard(value);
|
|
48
|
+
if (!ok) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setCopied(true);
|
|
52
|
+
if (timerRef.current !== null) {
|
|
53
|
+
clearTimeout(timerRef.current);
|
|
54
|
+
}
|
|
55
|
+
timerRef.current = setTimeout(() => {
|
|
56
|
+
timerRef.current = null;
|
|
57
|
+
setCopied(false);
|
|
58
|
+
}, COPIED_MS);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const tip = copied ? "Copied" : label;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Tooltip>
|
|
65
|
+
<TooltipTrigger asChild>
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
data-og-copy=""
|
|
69
|
+
data-state={copied ? "copied" : "idle"}
|
|
70
|
+
aria-label={tip}
|
|
71
|
+
onClick={onClick}
|
|
72
|
+
className={cn(
|
|
73
|
+
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
74
|
+
"text-og-fg-subtle transition-[opacity,color,background-color] duration-150",
|
|
75
|
+
"hover:bg-og-surface-2/80 hover:text-og-fg",
|
|
76
|
+
"focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent/40",
|
|
77
|
+
reveal === "group-hover" &&
|
|
78
|
+
"opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 pointer-coarse:opacity-70",
|
|
79
|
+
copied && "opacity-100 text-og-accent",
|
|
80
|
+
className,
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
{copied ? (
|
|
84
|
+
<CheckIcon className="size-3.5" aria-hidden />
|
|
85
|
+
) : (
|
|
86
|
+
<CopyIcon className="size-3.5" aria-hidden />
|
|
87
|
+
)}
|
|
88
|
+
</button>
|
|
89
|
+
</TooltipTrigger>
|
|
90
|
+
<TooltipContent side="top">{tip}</TooltipContent>
|
|
91
|
+
</Tooltip>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Hover-reveal chrome around a message / turn so {@link CopyButton} can fade in. */
|
|
96
|
+
export function CopyHoverFrame({
|
|
97
|
+
children,
|
|
98
|
+
className,
|
|
99
|
+
copyText,
|
|
100
|
+
label,
|
|
101
|
+
align = "end",
|
|
102
|
+
}: {
|
|
103
|
+
children: ReactNode;
|
|
104
|
+
className?: string | undefined;
|
|
105
|
+
copyText: string;
|
|
106
|
+
label: string;
|
|
107
|
+
align?: "start" | "end" | undefined;
|
|
108
|
+
}) {
|
|
109
|
+
if (copyText.trim().length === 0) {
|
|
110
|
+
return <div className={className}>{children}</div>;
|
|
111
|
+
}
|
|
112
|
+
return (
|
|
113
|
+
<div className={cn("group/copy relative", className)}>
|
|
114
|
+
{children}
|
|
115
|
+
<div
|
|
116
|
+
className={cn(
|
|
117
|
+
"pointer-events-none absolute top-1 z-10",
|
|
118
|
+
align === "end" ? "right-1" : "left-1",
|
|
119
|
+
)}
|
|
120
|
+
>
|
|
121
|
+
<div className="pointer-events-auto">
|
|
122
|
+
<CopyButton text={copyText} label={label} reveal="group-hover" />
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
isValidElement,
|
|
4
|
+
memo,
|
|
5
|
+
useEffect,
|
|
6
|
+
useReducer,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ComponentPropsWithoutRef,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react";
|
|
2
12
|
import ReactMarkdown, { type Components } from "react-markdown";
|
|
3
13
|
import remarkGfm from "remark-gfm";
|
|
4
14
|
import { cn } from "../lib/cn";
|
|
15
|
+
import { tableElementToTsv } from "../lib/clipboard";
|
|
16
|
+
import { prefersReducedMotion } from "../lib/motion";
|
|
17
|
+
import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
|
|
18
|
+
import { CopyButton } from "./copy-button";
|
|
19
|
+
import { softenStreamingMarkdown } from "./soften-streaming-markdown";
|
|
20
|
+
import { createStreamReveal, rehypeStreamReveal, type StreamReveal } from "./stream-reveal";
|
|
21
|
+
import { TooltipProvider } from "./tooltip";
|
|
5
22
|
|
|
6
23
|
/**
|
|
7
24
|
* The default renderer for chat message bodies in {@link MessageTimeline}.
|
|
@@ -20,6 +37,15 @@ import { cn } from "../lib/cn";
|
|
|
20
37
|
export type MarkdownProps = {
|
|
21
38
|
children: string;
|
|
22
39
|
className?: string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* While true, newly arrived source fades in through tip ink (`.og-stream-ink`):
|
|
42
|
+
* an age window over each append batch — fast streams keep a large soft band,
|
|
43
|
+
* slow streams a tight tip. Paint-only: the DOM always holds the full truthful
|
|
44
|
+
* text. Once streaming ends and trailing ink settles, the body re-renders as
|
|
45
|
+
* plain markdown with a short local settle breath (no page-wide view
|
|
46
|
+
* transition — that blinked the whole document).
|
|
47
|
+
*/
|
|
48
|
+
streaming?: boolean | undefined;
|
|
23
49
|
};
|
|
24
50
|
|
|
25
51
|
/* --- element renderers (themed to og-* tokens) ------------------------------ */
|
|
@@ -131,37 +157,20 @@ const components: Components = {
|
|
|
131
157
|
// the `pre` renderer), so a `code` reaching here is treated as inline.
|
|
132
158
|
code: ({ children, className: _className, ...props }) => (
|
|
133
159
|
<code
|
|
134
|
-
className="rounded-og-xs
|
|
160
|
+
className="rounded-og-xs bg-og-surface-1 px-1 py-0.5 font-og-mono text-og-sm text-og-fg"
|
|
135
161
|
{...props}
|
|
136
162
|
>
|
|
137
163
|
{children}
|
|
138
164
|
</code>
|
|
139
165
|
),
|
|
140
|
-
// Fenced code
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{...props}
|
|
146
|
-
>
|
|
147
|
-
{children}
|
|
148
|
-
</pre>
|
|
149
|
-
),
|
|
150
|
-
table: ({ children, ...props }) => (
|
|
151
|
-
<div className="my-3 max-w-full overflow-x-auto rounded-og-md border border-og-border first:mt-0 last:mb-0">
|
|
152
|
-
<table className="w-full border-collapse text-og-base" {...props}>
|
|
153
|
-
{children}
|
|
154
|
-
</table>
|
|
155
|
-
</div>
|
|
156
|
-
),
|
|
157
|
-
thead: ({ children, ...props }) => (
|
|
158
|
-
<thead className="bg-og-surface-1" {...props}>
|
|
159
|
-
{children}
|
|
160
|
-
</thead>
|
|
161
|
-
),
|
|
166
|
+
// Fenced code — quiet mono block (no card / no nested vertical scroll).
|
|
167
|
+
pre: ({ children }) => <MarkdownCodeBlock>{children}</MarkdownCodeBlock>,
|
|
168
|
+
// Tables stay unboxed (hairline rules); hover reveals a TSV copy control.
|
|
169
|
+
table: ({ children, ...props }) => <MarkdownTable {...props}>{children}</MarkdownTable>,
|
|
170
|
+
thead: ({ children, ...props }) => <thead {...props}>{children}</thead>,
|
|
162
171
|
th: ({ children, ...props }) => (
|
|
163
172
|
<th
|
|
164
|
-
className="border-b border-og-border px-
|
|
173
|
+
className="border-b border-og-border px-0 py-1.5 pr-4 text-left font-medium text-og-fg first:pl-0"
|
|
165
174
|
{...props}
|
|
166
175
|
>
|
|
167
176
|
{children}
|
|
@@ -169,7 +178,7 @@ const components: Components = {
|
|
|
169
178
|
),
|
|
170
179
|
td: ({ children, ...props }) => (
|
|
171
180
|
<td
|
|
172
|
-
className="border-b border-og-border px-
|
|
181
|
+
className="border-b border-og-border/70 px-0 py-1.5 pr-4 align-top text-og-fg-muted [tr:last-child>&]:border-b-0"
|
|
173
182
|
{...props}
|
|
174
183
|
>
|
|
175
184
|
{children}
|
|
@@ -184,17 +193,207 @@ const components: Components = {
|
|
|
184
193
|
),
|
|
185
194
|
};
|
|
186
195
|
|
|
187
|
-
|
|
196
|
+
/** How long after the stream ends the reveal pipeline stays for trailing animations. */
|
|
197
|
+
/** Trailing ink window after stream end — keep ≥ {@link INK_FADE_MS}. */
|
|
198
|
+
const REVEAL_LINGER_MS = 480 * MOTION_INSPECT_SCALE;
|
|
199
|
+
/** Keep in sync with `--og-duration-markdown-crystallize` / view-transition CSS. */
|
|
200
|
+
const MARKDOWN_SETTLE_MS = 480 * MOTION_INSPECT_SCALE;
|
|
201
|
+
|
|
202
|
+
function nodeText(node: ReactNode): string {
|
|
203
|
+
if (node == null || typeof node === "boolean") {
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
207
|
+
return String(node);
|
|
208
|
+
}
|
|
209
|
+
if (Array.isArray(node)) {
|
|
210
|
+
return node.map(nodeText).join("");
|
|
211
|
+
}
|
|
212
|
+
if (isValidElement<{ children?: ReactNode }>(node)) {
|
|
213
|
+
return nodeText(node.props.children);
|
|
214
|
+
}
|
|
215
|
+
return "";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function fenceLanguage(children: ReactNode): string | null {
|
|
219
|
+
let found: string | null = null;
|
|
220
|
+
Children.forEach(children, (child) => {
|
|
221
|
+
if (found || !isValidElement<{ className?: string }>(child)) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const match = /language-([a-zA-Z0-9_+-]+)/.exec(child.props.className ?? "");
|
|
225
|
+
if (match?.[1]) {
|
|
226
|
+
found = match[1];
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
return found;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function MarkdownCodeBlock({ children }: { children?: ReactNode }) {
|
|
233
|
+
const code = nodeText(children).replace(/\n$/, "");
|
|
234
|
+
const language = fenceLanguage(children);
|
|
235
|
+
// mt-only (no mb / last:mb-0): next sibling streaming in used to flip a
|
|
236
|
+
// previous block's bottom margin on and yank tip-follow. Copy is overlay-only.
|
|
237
|
+
// Fixed leading + overflow-x:auto with stable block chrome so wide ASCII lines
|
|
238
|
+
// don't pop a scrollbar gutters mid-stream (another tip bob).
|
|
188
239
|
return (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
240
|
+
<div className="group/copy relative mt-3 first:mt-0">
|
|
241
|
+
<div className="pointer-events-none absolute top-1.5 right-1.5 z-10 flex items-center gap-1">
|
|
242
|
+
{language ? (
|
|
243
|
+
<span className="rounded px-1 py-0.5 font-og-mono text-[10px] uppercase tracking-wide text-og-fg-subtle/80 opacity-0 transition-opacity group-hover/copy:opacity-100 pointer-coarse:opacity-70">
|
|
244
|
+
{language}
|
|
245
|
+
</span>
|
|
246
|
+
) : null}
|
|
247
|
+
<div className="pointer-events-auto">
|
|
248
|
+
<CopyButton text={code} label="Copy code" reveal="group-hover" />
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<pre className="overflow-x-auto overflow-y-hidden rounded-og-md bg-og-surface-1/70 px-3 py-2.5 font-og-mono text-og-sm leading-5 text-og-fg-muted [scrollbar-gutter:stable] [&>code]:block [&>code]:border-0 [&>code]:bg-transparent [&>code]:p-0 [&>code]:leading-5 [&>code]:text-inherit">
|
|
193
252
|
{children}
|
|
194
|
-
</
|
|
253
|
+
</pre>
|
|
195
254
|
</div>
|
|
196
255
|
);
|
|
197
256
|
}
|
|
198
257
|
|
|
258
|
+
function MarkdownTable({ children, className, ...props }: ComponentPropsWithoutRef<"table">) {
|
|
259
|
+
const tableRef = useRef<HTMLTableElement | null>(null);
|
|
260
|
+
return (
|
|
261
|
+
<div className="group/copy relative mt-3 max-w-full first:mt-0">
|
|
262
|
+
<div className="pointer-events-none absolute top-0 right-0 z-10">
|
|
263
|
+
<div className="pointer-events-auto">
|
|
264
|
+
<CopyButton
|
|
265
|
+
text={() => tableElementToTsv(tableRef.current)}
|
|
266
|
+
label="Copy table"
|
|
267
|
+
reveal="group-hover"
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
<div className="overflow-x-auto">
|
|
272
|
+
<table
|
|
273
|
+
ref={tableRef}
|
|
274
|
+
className={cn("w-full min-w-0 border-collapse text-og-base", className)}
|
|
275
|
+
{...props}
|
|
276
|
+
>
|
|
277
|
+
{children}
|
|
278
|
+
</table>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function MarkdownImpl({ children, className, streaming = false }: MarkdownProps) {
|
|
285
|
+
// Tip-ink engine for THIS body: created on the first streaming render, kept
|
|
286
|
+
// through a short linger after the stream ends (so the last age window can
|
|
287
|
+
// finish), then dropped so settled bodies pay zero cost. Observing during
|
|
288
|
+
// render is idempotent per text length — StrictMode double-renders are safe.
|
|
289
|
+
const revealRef = useRef<StreamReveal | null>(null);
|
|
290
|
+
const bodyRef = useRef<HTMLDivElement | null>(null);
|
|
291
|
+
const [, bump] = useReducer((n: number) => n + 1, 0);
|
|
292
|
+
const [settling, setSettling] = useState(false);
|
|
293
|
+
const hadRevealRef = useRef(false);
|
|
294
|
+
const now = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
295
|
+
|
|
296
|
+
// Local settle breath only — View Transitions with `::view-transition-*(*)`
|
|
297
|
+
// were fading the document root and felt like a full-page blink / focus loss
|
|
298
|
+
// right as a turn ended (just before the next user message in the seed loop).
|
|
299
|
+
const crystallize = (mutate: () => void) => {
|
|
300
|
+
mutate();
|
|
301
|
+
setSettling(true);
|
|
302
|
+
bump();
|
|
303
|
+
return setTimeout(() => setSettling(false), MARKDOWN_SETTLE_MS);
|
|
304
|
+
};
|
|
305
|
+
if (streaming && revealRef.current === null && !prefersReducedMotion()) {
|
|
306
|
+
revealRef.current = createStreamReveal();
|
|
307
|
+
}
|
|
308
|
+
const reveal = revealRef.current;
|
|
309
|
+
if (reveal !== null && streaming) {
|
|
310
|
+
reveal.observe(children, now);
|
|
311
|
+
}
|
|
312
|
+
if (reveal !== null) {
|
|
313
|
+
hadRevealRef.current = true;
|
|
314
|
+
}
|
|
315
|
+
const revealActive = reveal !== null && (streaming || reveal.hasActive(now));
|
|
316
|
+
// The plugin walks the standard hast tree; a structural local type keeps the
|
|
317
|
+
// module dependency-free, at the cost of this narrowing cast at the seam.
|
|
318
|
+
const rehypePlugins =
|
|
319
|
+
revealActive && reveal !== null
|
|
320
|
+
? ([[rehypeStreamReveal, { reveal, now }]] as unknown as NonNullable<
|
|
321
|
+
Parameters<typeof ReactMarkdown>[0]["rehypePlugins"]
|
|
322
|
+
>)
|
|
323
|
+
: undefined;
|
|
324
|
+
|
|
325
|
+
// Drop tip ink after trailing age windows finish. Same commit crystallizes:
|
|
326
|
+
// ink spans → final plain markdown (soften off) + a short local settle breath.
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (streaming || revealRef.current === null) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
let clearSettle: ReturnType<typeof setTimeout> | undefined;
|
|
332
|
+
const timer = setTimeout(() => {
|
|
333
|
+
const shouldSettle = hadRevealRef.current && !prefersReducedMotion();
|
|
334
|
+
if (shouldSettle) {
|
|
335
|
+
hadRevealRef.current = false;
|
|
336
|
+
}
|
|
337
|
+
if (shouldSettle) {
|
|
338
|
+
clearSettle = crystallize(() => {
|
|
339
|
+
revealRef.current = null;
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
revealRef.current = null;
|
|
343
|
+
bump();
|
|
344
|
+
}
|
|
345
|
+
}, REVEAL_LINGER_MS);
|
|
346
|
+
return () => {
|
|
347
|
+
clearTimeout(timer);
|
|
348
|
+
if (clearSettle !== undefined) {
|
|
349
|
+
clearTimeout(clearSettle);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}, [streaming]);
|
|
353
|
+
|
|
354
|
+
// No-reveal path (stream ended before any ink batch): soften already dropped
|
|
355
|
+
// with `streaming`; keep the same local settle breath.
|
|
356
|
+
const wasStreamingRef = useRef(streaming);
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
const ended = wasStreamingRef.current && !streaming;
|
|
359
|
+
wasStreamingRef.current = streaming;
|
|
360
|
+
if (!ended || revealRef.current !== null || prefersReducedMotion()) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
setSettling(true);
|
|
364
|
+
const timer = setTimeout(() => setSettling(false), MARKDOWN_SETTLE_MS);
|
|
365
|
+
return () => clearTimeout(timer);
|
|
366
|
+
}, [streaming]);
|
|
367
|
+
|
|
368
|
+
// Soften unfinished markers for DISPLAY while the reveal pipeline is alive
|
|
369
|
+
// (stream + linger) — not only while `streaming` — so task lists / tables /
|
|
370
|
+
// fences don't snap to final GFM one commit before the crystallize morph.
|
|
371
|
+
// Reveal identity still tracks the true source (`children`).
|
|
372
|
+
const parseText = streaming || revealActive ? softenStreamingMarkdown(children) : children;
|
|
373
|
+
|
|
374
|
+
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
375
|
+
// long links and code blocks wrap/scroll instead of forcing overflow.
|
|
376
|
+
return (
|
|
377
|
+
<TooltipProvider delayDuration={400}>
|
|
378
|
+
<div
|
|
379
|
+
ref={bodyRef}
|
|
380
|
+
className={cn(
|
|
381
|
+
"og-markdown-body min-w-0 break-words",
|
|
382
|
+
settling && "og-markdown-settle",
|
|
383
|
+
className,
|
|
384
|
+
)}
|
|
385
|
+
>
|
|
386
|
+
<ReactMarkdown
|
|
387
|
+
remarkPlugins={[remarkGfm]}
|
|
388
|
+
rehypePlugins={rehypePlugins}
|
|
389
|
+
components={components}
|
|
390
|
+
>
|
|
391
|
+
{parseText}
|
|
392
|
+
</ReactMarkdown>
|
|
393
|
+
</div>
|
|
394
|
+
</TooltipProvider>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
199
398
|
/** Memoized so streaming re-renders of the parent don't re-parse settled bodies. */
|
|
200
399
|
export const Markdown = memo(MarkdownImpl);
|